Home | History | Annotate | Download | only in android
      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 
     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
     74   protected void finalize() {
     75     if (kDebugResXMLTree) {
     76       ALOGI("Destroying ResXMLTree in %s #%d\n", this, gCount.getAndDecrement()-1);
     77     }
     78     uninit();
     79   }
     80 
     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 
    196   public int getError()
    197   {
    198     return mError;
    199   }
    200 
    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 
    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 
    289   public ResStringPool getStrings() {
    290     return mParser.getStrings();
    291   }
    292 
    293   static class XmlBuffer {
    294     final ByteBuffer buf;
    295 
    296     public XmlBuffer(byte[] data) {
    297       this.buf = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
    298     }
    299   }
    300 }
    301