• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.res.android;
2 
3 import static org.robolectric.res.android.Errors.BAD_TYPE;
4 import static org.robolectric.res.android.Errors.NO_ERROR;
5 import static org.robolectric.res.android.Errors.NO_INIT;
6 import static org.robolectric.res.android.ResTable.kDebugResXMLTree;
7 import static org.robolectric.res.android.ResTable.kDebugXMLNoisy;
8 import static org.robolectric.res.android.ResXMLParser.SIZEOF_RESXMLTREE_ATTR_EXT;
9 import static org.robolectric.res.android.ResXMLParser.SIZEOF_RESXMLTREE_NODE;
10 import static org.robolectric.res.android.ResXMLParser.event_code_t.BAD_DOCUMENT;
11 import static org.robolectric.res.android.ResXMLParser.event_code_t.START_DOCUMENT;
12 import static org.robolectric.res.android.ResourceTypes.RES_STRING_POOL_TYPE;
13 import static org.robolectric.res.android.ResourceTypes.RES_XML_FIRST_CHUNK_TYPE;
14 import static org.robolectric.res.android.ResourceTypes.RES_XML_LAST_CHUNK_TYPE;
15 import static org.robolectric.res.android.ResourceTypes.RES_XML_RESOURCE_MAP_TYPE;
16 import static org.robolectric.res.android.ResourceTypes.RES_XML_START_ELEMENT_TYPE;
17 import static org.robolectric.res.android.ResourceTypes.validate_chunk;
18 import static org.robolectric.res.android.Util.ALOGI;
19 import static org.robolectric.res.android.Util.ALOGW;
20 import static org.robolectric.res.android.Util.SIZEOF_INT;
21 import static org.robolectric.res.android.Util.dtohl;
22 import static org.robolectric.res.android.Util.dtohs;
23 import static org.robolectric.res.android.Util.isTruthy;
24 
25 import java.nio.ByteBuffer;
26 import java.nio.ByteOrder;
27 import java.util.concurrent.atomic.AtomicInteger;
28 import org.robolectric.res.android.ResourceTypes.ResChunk_header;
29 import org.robolectric.res.android.ResourceTypes.ResXMLTree_attrExt;
30 import org.robolectric.res.android.ResourceTypes.ResXMLTree_header;
31 import org.robolectric.res.android.ResourceTypes.ResXMLTree_node;
32 
33 public class ResXMLTree {
34 
35   final DynamicRefTable mDynamicRefTable;
36   public final ResXMLParser mParser;
37 
38   int                    mError;
39   byte[]                       mOwnedData;
40   XmlBuffer mBuffer;
41     ResXMLTree_header mHeader;
42   int                      mSize;
43   //    final uint8_t*              mDataEnd;
44   int mDataLen;
45   ResStringPool               mStrings = new ResStringPool();
46     int[]             mResIds;
47   int                      mNumResIds;
48     ResXMLTree_node mRootNode;
49     int                 mRootExt;
50   int                mRootCode;
51 
52   static volatile AtomicInteger gCount = new AtomicInteger(0);
53 
ResXMLTree(DynamicRefTable dynamicRefTable)54   public ResXMLTree(DynamicRefTable dynamicRefTable) {
55     mParser = new ResXMLParser(this);
56 
57     mDynamicRefTable = dynamicRefTable;
58     mError = NO_INIT;
59     mOwnedData = null;
60 
61     if (kDebugResXMLTree) {
62       ALOGI("Creating ResXMLTree %s #%d\n", this, gCount.getAndIncrement()+1);
63     }
64     mParser.restart();
65   }
66 
67 //  ResXMLTree() {
68 //    this(null);
69 //  }
70 
71 //  ~ResXMLTree()
72 //  {
73   @Override
finalize()74   protected void finalize() {
75     if (kDebugResXMLTree) {
76       ALOGI("Destroying ResXMLTree in %s #%d\n", this, gCount.getAndDecrement()-1);
77     }
78     uninit();
79   }
80 
setTo(byte[] data, int size, boolean copyData)81   public int setTo(byte[] data, int size, boolean copyData)
82   {
83     uninit();
84     mParser.mEventCode = START_DOCUMENT;
85 
86     if (!isTruthy(data) || !isTruthy(size)) {
87       return (mError=BAD_TYPE);
88     }
89 
90     if (copyData) {
91       mOwnedData = new byte[size];
92 //      if (mOwnedData == null) {
93 //        return (mError=NO_MEMORY);
94 //      }
95 //      memcpy(mOwnedData, data, size);
96       System.arraycopy(data, 0, mOwnedData, 0, size);
97       data = mOwnedData;
98     }
99 
100     mBuffer = new XmlBuffer(data);
101     mHeader = new ResXMLTree_header(mBuffer.buf, 0);
102     mSize = dtohl(mHeader.header.size);
103     if (dtohs(mHeader.header.headerSize) > mSize || mSize > size) {
104       ALOGW("Bad XML block: header size %d or total size %d is larger than data size %d\n",
105           (int)dtohs(mHeader.header.headerSize),
106           (int)dtohl(mHeader.header.size), (int)size);
107       mError = BAD_TYPE;
108       mParser.restart();
109       return mError;
110     }
111 //    mDataEnd = ((final uint8_t*)mHeader) + mSize;
112     mDataLen = mSize;
113 
114     mStrings.uninit();
115     mRootNode = null;
116     mResIds = null;
117     mNumResIds = 0;
118 
119     // First look for a couple interesting chunks: the string block
120     // and first XML node.
121     ResChunk_header chunk =
122 //      (final ResChunk_header*)(((final uint8_t*)mHeader) + dtohs(mHeader.header.headerSize));
123         new ResChunk_header(mBuffer.buf, mHeader.header.headerSize);
124 
125     ResChunk_header lastChunk = chunk;
126     while (chunk.myOffset() /*((final uint8_t*)chunk)*/ < (mDataLen- ResChunk_header.SIZEOF /*sizeof(ResChunk_header)*/) &&
127         chunk.myOffset() /*((final uint8_t*)chunk)*/ < (mDataLen-dtohl(chunk.size))) {
128       int err = validate_chunk(chunk, ResChunk_header.SIZEOF /*sizeof(ResChunk_header)*/, mDataLen, "XML");
129       if (err != NO_ERROR) {
130         mError = err;
131 //          goto done;
132         mParser.restart();
133         return mError;
134       }
135       final short type = dtohs(chunk.type);
136       final int size1 = dtohl(chunk.size);
137       if (kDebugXMLNoisy) {
138 //        System.out.println(String.format("Scanning @ %s: type=0x%x, size=0x%zx\n",
139 //            (void*)(((uintptr_t)chunk)-((uintptr_t)mHeader)), type, size1);
140       }
141       if (type == RES_STRING_POOL_TYPE) {
142         mStrings.setTo(mBuffer.buf, chunk.myOffset(), size, false);
143       } else if (type == RES_XML_RESOURCE_MAP_TYPE) {
144 //        mResIds = (final int*)
145 //        (((final uint8_t*)chunk)+dtohs(chunk.headerSize()));
146         mNumResIds = (dtohl(chunk.size)-dtohs(chunk.headerSize))/SIZEOF_INT /*sizeof(int)*/;
147         mResIds = new int[mNumResIds];
148         for (int i = 0; i < mNumResIds; i++) {
149           mResIds[i] = mBuffer.buf.getInt(chunk.myOffset() + chunk.headerSize + i * SIZEOF_INT);
150         }
151       } else if (type >= RES_XML_FIRST_CHUNK_TYPE
152           && type <= RES_XML_LAST_CHUNK_TYPE) {
153         if (validateNode(new ResXMLTree_node(mBuffer.buf, chunk)) != NO_ERROR) {
154           mError = BAD_TYPE;
155 //          goto done;
156           mParser.restart();
157           return mError;
158         }
159         mParser.mCurNode = new ResXMLTree_node(mBuffer.buf, lastChunk.myOffset());
160         if (mParser.nextNode() == BAD_DOCUMENT) {
161           mError = BAD_TYPE;
162 //          goto done;
163           mParser.restart();
164           return mError;
165         }
166         mRootNode = mParser.mCurNode;
167         mRootExt = mParser.mCurExt;
168         mRootCode = mParser.mEventCode;
169         break;
170       } else {
171         if (kDebugXMLNoisy) {
172           System.out.println("Skipping unknown chunk!\n");
173         }
174       }
175       lastChunk = chunk;
176 //      chunk = (final ResChunk_header*)
177 //      (((final uint8_t*)chunk) + size1);
178       chunk = new ResChunk_header(mBuffer.buf, chunk.myOffset() + size1);
179   }
180 
181     if (mRootNode == null) {
182       ALOGW("Bad XML block: no root element node found\n");
183       mError = BAD_TYPE;
184 //          goto done;
185       mParser.restart();
186       return mError;
187     }
188 
189     mError = mStrings.getError();
190 
191   done:
192     mParser.restart();
193     return mError;
194   }
195 
getError()196   public int getError()
197   {
198     return mError;
199   }
200 
uninit()201   void uninit()
202   {
203     mError = NO_INIT;
204     mStrings.uninit();
205     if (isTruthy(mOwnedData)) {
206 //      free(mOwnedData);
207       mOwnedData = null;
208     }
209     mParser.restart();
210   }
211 
validateNode(final ResXMLTree_node node)212   int validateNode(final ResXMLTree_node node)
213   {
214     final short eventCode = dtohs(node.header.type);
215 
216     int err = validate_chunk(
217         node.header, SIZEOF_RESXMLTREE_NODE /*sizeof(ResXMLTree_node)*/,
218       mDataLen, "ResXMLTree_node");
219 
220     if (err >= NO_ERROR) {
221       // Only perform additional validation on START nodes
222       if (eventCode != RES_XML_START_ELEMENT_TYPE) {
223         return NO_ERROR;
224       }
225 
226         final short headerSize = dtohs(node.header.headerSize);
227         final int size = dtohl(node.header.size);
228 //        final ResXMLTree_attrExt attrExt = (final ResXMLTree_attrExt*)
229 //      (((final uint8_t*)node) + headerSize);
230       ResXMLTree_attrExt attrExt = new ResXMLTree_attrExt(mBuffer.buf, node.myOffset() + headerSize);
231       // check for sensical values pulled out of the stream so far...
232       if ((size >= headerSize + SIZEOF_RESXMLTREE_ATTR_EXT /*sizeof(ResXMLTree_attrExt)*/)
233           && (attrExt.myOffset() > node.myOffset())) {
234             final int attrSize = ((int)dtohs(attrExt.attributeSize))
235             * dtohs(attrExt.attributeCount);
236         if ((dtohs(attrExt.attributeStart)+attrSize) <= (size-headerSize)) {
237           return NO_ERROR;
238         }
239         ALOGW("Bad XML block: node attributes use 0x%x bytes, only have 0x%x bytes\n",
240             (int)(dtohs(attrExt.attributeStart)+attrSize),
241             (int)(size-headerSize));
242       }
243         else {
244         ALOGW("Bad XML start block: node header size 0x%x, size 0x%x\n",
245             (int)headerSize, (int)size);
246       }
247       return BAD_TYPE;
248     }
249 
250     return err;
251 
252 //    if (false) {
253 //      final boolean isStart = dtohs(node.header().type()) == RES_XML_START_ELEMENT_TYPE;
254 //
255 //      final short headerSize = dtohs(node.header().headerSize());
256 //      final int size = dtohl(node.header().size());
257 //
258 //      if (headerSize >= (isStart ? sizeof(ResXMLTree_attrNode) : sizeof(ResXMLTree_node))) {
259 //        if (size >= headerSize) {
260 //          if ((( final uint8_t*)node) <=(mDataEnd - size)){
261 //            if (!isStart) {
262 //              return NO_ERROR;
263 //            }
264 //            if ((((int) dtohs(node.attributeSize)) * dtohs(node.attributeCount))
265 //                <= (size - headerSize)) {
266 //              return NO_ERROR;
267 //            }
268 //            ALOGW("Bad XML block: node attributes use 0x%x bytes, only have 0x%x bytes\n",
269 //                ((int) dtohs(node.attributeSize)) * dtohs(node.attributeCount),
270 //                (int) (size - headerSize));
271 //            return BAD_TYPE;
272 //          }
273 //          ALOGW("Bad XML block: node at 0x%x extends beyond data end 0x%x\n",
274 //              (int) ((( final uint8_t*)node)-(( final uint8_t*)mHeader)),(int) mSize);
275 //          return BAD_TYPE;
276 //        }
277 //        ALOGW("Bad XML block: node at 0x%x header size 0x%x smaller than total size 0x%x\n",
278 //            (int) ((( final uint8_t*)node)-(( final uint8_t*)mHeader)),
279 //        (int) headerSize, (int) size);
280 //        return BAD_TYPE;
281 //      }
282 //      ALOGW("Bad XML block: node at 0x%x header size 0x%x too small\n",
283 //          (int) ((( final uint8_t*)node)-(( final uint8_t*)mHeader)),
284 //      (int) headerSize);
285 //      return BAD_TYPE;
286 //    }
287   }
288 
getStrings()289   public ResStringPool getStrings() {
290     return mParser.getStrings();
291   }
292 
293   static class XmlBuffer {
294     final ByteBuffer buf;
295 
XmlBuffer(byte[] data)296     public XmlBuffer(byte[] data) {
297       this.buf = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
298     }
299   }
300 }
301