1 package org.robolectric.res.android; 2 3 import static org.robolectric.res.android.Util.dtohl; 4 import static org.robolectric.res.android.Util.dtohs; 5 import static org.robolectric.res.android.Util.isTruthy; 6 7 import java.nio.ByteBuffer; 8 import org.robolectric.res.android.ResourceTypes.ResChunk_header; 9 import org.robolectric.res.android.ResourceTypes.ResStringPool_header; 10 import org.robolectric.res.android.ResourceTypes.ResTableStagedAliasEntry; 11 import org.robolectric.res.android.ResourceTypes.ResTableStagedAliasHeader; 12 import org.robolectric.res.android.ResourceTypes.ResTable_header; 13 import org.robolectric.res.android.ResourceTypes.ResTable_lib_entry; 14 import org.robolectric.res.android.ResourceTypes.ResTable_lib_header; 15 import org.robolectric.res.android.ResourceTypes.ResTable_package; 16 import org.robolectric.res.android.ResourceTypes.ResTable_type; 17 import org.robolectric.res.android.ResourceTypes.WithOffset; 18 19 // transliterated from 20 // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/ChunkIterator.cpp and 21 // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/include/androidfw/Chunk.h 22 23 // Helpful wrapper around a ResChunk_header that provides getter methods 24 // that handle endianness conversions and provide access to the data portion 25 // of the chunk. 26 class Chunk { 27 28 // public: Chunk(ResChunk_header chunk)29 Chunk(ResChunk_header chunk) { 30 this.device_chunk_ = chunk; 31 } 32 33 // Returns the type of the chunk. Caller need not worry about endianness. type()34 int type() { 35 return dtohs(device_chunk_.type); 36 } 37 38 // Returns the size of the entire chunk. This can be useful for skipping 39 // over the entire chunk. Caller need not worry about endianness. size()40 int size() { 41 return dtohl(device_chunk_.size); 42 } 43 44 // Returns the size of the header. Caller need not worry about endianness. header_size()45 int header_size() { 46 return dtohs(device_chunk_.headerSize); 47 } 48 49 // template <typename T, int MinSize = sizeof(T)> 50 // T* header() { 51 // if (header_size() >= MinSize) { 52 // return reinterpret_cast<T*>(device_chunk_); 53 // } 54 // return nullptr; 55 // } 56 myBuf()57 ByteBuffer myBuf() { 58 return device_chunk_.myBuf(); 59 } 60 myOffset()61 int myOffset() { 62 return device_chunk_.myOffset(); 63 } 64 data_ptr()65 public WithOffset data_ptr() { 66 return new WithOffset(device_chunk_.myBuf(), device_chunk_.myOffset() + header_size()); 67 } 68 data_size()69 int data_size() { 70 return size() - header_size(); 71 } 72 73 // private: 74 private ResChunk_header device_chunk_; 75 asResTable_header()76 public ResTable_header asResTable_header() { 77 if (header_size() >= ResTable_header.SIZEOF) { 78 return new ResTable_header(device_chunk_.myBuf(), device_chunk_.myOffset()); 79 } else { 80 return null; 81 } 82 } 83 asResStringPool_header()84 public ResStringPool_header asResStringPool_header() { 85 if (header_size() >= ResStringPool_header.SIZEOF) { 86 return new ResStringPool_header(device_chunk_.myBuf(), device_chunk_.myOffset()); 87 } else { 88 return null; 89 } 90 } 91 asResTable_package(int size)92 public ResTable_package asResTable_package(int size) { 93 if (header_size() >= size) { 94 return new ResTable_package(device_chunk_.myBuf(), device_chunk_.myOffset()); 95 } else { 96 return null; 97 } 98 } 99 asResTable_type(int size)100 public ResTable_type asResTable_type(int size) { 101 if (header_size() >= size) { 102 return new ResTable_type(device_chunk_.myBuf(), device_chunk_.myOffset()); 103 } else { 104 return null; 105 } 106 } 107 asResTable_lib_header()108 public ResTable_lib_header asResTable_lib_header() { 109 if (header_size() >= ResTable_lib_header.SIZEOF) { 110 return new ResTable_lib_header(device_chunk_.myBuf(), device_chunk_.myOffset()); 111 } else { 112 return null; 113 } 114 } 115 asResTable_lib_entry()116 public ResTable_lib_entry asResTable_lib_entry() { 117 if (data_size() >= ResTable_lib_entry.SIZEOF) { 118 return new ResTable_lib_entry( 119 device_chunk_.myBuf(), device_chunk_.myOffset() + header_size()); 120 } else { 121 return null; 122 } 123 } 124 asResTableStagedAliasHeader()125 public ResTableStagedAliasHeader asResTableStagedAliasHeader() { 126 if (header_size() >= ResTableStagedAliasHeader.SIZEOF) { 127 return new ResTableStagedAliasHeader(device_chunk_.myBuf(), device_chunk_.myOffset()); 128 } else { 129 return null; 130 } 131 } 132 asResTableStagedAliasEntry()133 public ResTableStagedAliasEntry asResTableStagedAliasEntry() { 134 if (data_size() >= ResTableStagedAliasEntry.SIZEOF) { 135 return new ResTableStagedAliasEntry( 136 device_chunk_.myBuf(), device_chunk_.myOffset() + header_size()); 137 } else { 138 return null; 139 } 140 } 141 142 static class Iterator { 143 private ResChunk_header next_chunk_; 144 private int len_; 145 private String last_error_; 146 private boolean last_error_was_fatal_ = true; 147 Iterator(WithOffset buf, int itemSize)148 public Iterator(WithOffset buf, int itemSize) { 149 this.next_chunk_ = new ResChunk_header(buf.myBuf(), buf.myOffset()); 150 this.len_ = itemSize; 151 } 152 HasNext()153 boolean HasNext() { 154 return !HadError() && len_ != 0; 155 } 156 ; 157 158 // Returns whether there was an error and processing should stop HadError()159 boolean HadError() { 160 return last_error_ != null; 161 } 162 GetLastError()163 String GetLastError() { 164 return last_error_; 165 } 166 167 // Returns whether there was an error and processing should stop. For legacy purposes, 168 // some errors are considered "non fatal". Fatal errors stop processing new chunks and 169 // throw away any chunks already processed. Non fatal errors also stop processing new 170 // chunks, but, will retain and use any valid chunks already processed. HadFatalError()171 boolean HadFatalError() { 172 return HadError() && last_error_was_fatal_; 173 } 174 Next()175 Chunk Next() { 176 assert (len_ != 0) : "called Next() after last chunk"; 177 178 ResChunk_header this_chunk = next_chunk_; 179 180 // We've already checked the values of this_chunk, so safely increment. 181 // next_chunk_ = reinterpret_cast<const ResChunk_header*>( 182 // reinterpret_cast<const uint8_t*>(this_chunk) + dtohl(this_chunk->size)); 183 int remaining = len_ - dtohl(this_chunk.size); 184 if (remaining <= 0) { 185 next_chunk_ = null; 186 } else { 187 next_chunk_ = 188 new ResChunk_header(this_chunk.myBuf(), this_chunk.myOffset() + dtohl(this_chunk.size)); 189 } 190 len_ -= dtohl(this_chunk.size); 191 192 if (len_ != 0) { 193 // Prepare the next chunk. 194 if (VerifyNextChunkNonFatal()) { 195 VerifyNextChunk(); 196 } 197 } 198 return new Chunk(this_chunk); 199 } 200 201 // Returns false if there was an error. For legacy purposes. VerifyNextChunkNonFatal()202 boolean VerifyNextChunkNonFatal() { 203 if (len_ < ResChunk_header.SIZEOF) { 204 last_error_ = "not enough space for header"; 205 last_error_was_fatal_ = false; 206 return false; 207 } 208 int size = dtohl(next_chunk_.size); 209 if (size > len_) { 210 last_error_ = "chunk size is bigger than given data"; 211 last_error_was_fatal_ = false; 212 return false; 213 } 214 return true; 215 } 216 217 // Returns false if there was an error. VerifyNextChunk()218 boolean VerifyNextChunk() { 219 // uintptr_t header_start = reinterpret_cast<uintptr_t>(next_chunk_); 220 int header_start = next_chunk_.myOffset(); 221 222 // This data must be 4-byte aligned, since we directly 223 // access 32-bit words, which must be aligned on 224 // certain architectures. 225 if (isTruthy(header_start & 0x03)) { 226 last_error_ = "header not aligned on 4-byte boundary"; 227 return false; 228 } 229 230 if (len_ < ResChunk_header.SIZEOF) { 231 last_error_ = "not enough space for header"; 232 return false; 233 } 234 235 int header_size = dtohs(next_chunk_.headerSize); 236 int size = dtohl(next_chunk_.size); 237 if (header_size < ResChunk_header.SIZEOF) { 238 last_error_ = "header size too small"; 239 return false; 240 } 241 242 if (header_size > size) { 243 last_error_ = "header size is larger than entire chunk"; 244 return false; 245 } 246 247 if (size > len_) { 248 last_error_ = "chunk size is bigger than given data"; 249 return false; 250 } 251 252 if (isTruthy((size | header_size) & 0x03)) { 253 last_error_ = "header sizes are not aligned on 4-byte boundary"; 254 return false; 255 } 256 return true; 257 } 258 } 259 } 260