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 (header_size() >= ResTable_lib_entry.SIZEOF) { 118 return new ResTable_lib_entry(device_chunk_.myBuf(), device_chunk_.myOffset()); 119 } else { 120 return null; 121 } 122 } 123 asResTableStagedAliasHeader()124 public ResTableStagedAliasHeader asResTableStagedAliasHeader() { 125 if (header_size() >= ResTableStagedAliasHeader.SIZEOF) { 126 return new ResTableStagedAliasHeader(device_chunk_.myBuf(), device_chunk_.myOffset()); 127 } else { 128 return null; 129 } 130 } 131 asResTableStagedAliasEntry()132 public ResTableStagedAliasEntry asResTableStagedAliasEntry() { 133 if (data_size() >= ResTableStagedAliasEntry.SIZEOF) { 134 return new ResTableStagedAliasEntry( 135 device_chunk_.myBuf(), device_chunk_.myOffset() + header_size()); 136 } else { 137 return null; 138 } 139 } 140 141 static class Iterator { 142 private ResChunk_header next_chunk_; 143 private int len_; 144 private String last_error_; 145 private boolean last_error_was_fatal_ = true; 146 Iterator(WithOffset buf, int itemSize)147 public Iterator(WithOffset buf, int itemSize) { 148 this.next_chunk_ = new ResChunk_header(buf.myBuf(), buf.myOffset()); 149 this.len_ = itemSize; 150 } 151 HasNext()152 boolean HasNext() { 153 return !HadError() && len_ != 0; 154 } 155 ; 156 157 // Returns whether there was an error and processing should stop HadError()158 boolean HadError() { 159 return last_error_ != null; 160 } 161 GetLastError()162 String GetLastError() { 163 return last_error_; 164 } 165 166 // Returns whether there was an error and processing should stop. For legacy purposes, 167 // some errors are considered "non fatal". Fatal errors stop processing new chunks and 168 // throw away any chunks already processed. Non fatal errors also stop processing new 169 // chunks, but, will retain and use any valid chunks already processed. HadFatalError()170 boolean HadFatalError() { 171 return HadError() && last_error_was_fatal_; 172 } 173 Next()174 Chunk Next() { 175 assert (len_ != 0) : "called Next() after last chunk"; 176 177 ResChunk_header this_chunk = next_chunk_; 178 179 // We've already checked the values of this_chunk, so safely increment. 180 // next_chunk_ = reinterpret_cast<const ResChunk_header*>( 181 // reinterpret_cast<const uint8_t*>(this_chunk) + dtohl(this_chunk->size)); 182 int remaining = len_ - dtohl(this_chunk.size); 183 if (remaining <= 0) { 184 next_chunk_ = null; 185 } else { 186 next_chunk_ = 187 new ResChunk_header(this_chunk.myBuf(), this_chunk.myOffset() + dtohl(this_chunk.size)); 188 } 189 len_ -= dtohl(this_chunk.size); 190 191 if (len_ != 0) { 192 // Prepare the next chunk. 193 if (VerifyNextChunkNonFatal()) { 194 VerifyNextChunk(); 195 } 196 } 197 return new Chunk(this_chunk); 198 } 199 200 // Returns false if there was an error. For legacy purposes. VerifyNextChunkNonFatal()201 boolean VerifyNextChunkNonFatal() { 202 if (len_ < ResChunk_header.SIZEOF) { 203 last_error_ = "not enough space for header"; 204 last_error_was_fatal_ = false; 205 return false; 206 } 207 int size = dtohl(next_chunk_.size); 208 if (size > len_) { 209 last_error_ = "chunk size is bigger than given data"; 210 last_error_was_fatal_ = false; 211 return false; 212 } 213 return true; 214 } 215 216 // Returns false if there was an error. VerifyNextChunk()217 boolean VerifyNextChunk() { 218 // uintptr_t header_start = reinterpret_cast<uintptr_t>(next_chunk_); 219 int header_start = next_chunk_.myOffset(); 220 221 // This data must be 4-byte aligned, since we directly 222 // access 32-bit words, which must be aligned on 223 // certain architectures. 224 if (isTruthy(header_start & 0x03)) { 225 last_error_ = "header not aligned on 4-byte boundary"; 226 return false; 227 } 228 229 if (len_ < ResChunk_header.SIZEOF) { 230 last_error_ = "not enough space for header"; 231 return false; 232 } 233 234 int header_size = dtohs(next_chunk_.headerSize); 235 int size = dtohl(next_chunk_.size); 236 if (header_size < ResChunk_header.SIZEOF) { 237 last_error_ = "header size too small"; 238 return false; 239 } 240 241 if (header_size > size) { 242 last_error_ = "header size is larger than entire chunk"; 243 return false; 244 } 245 246 if (size > len_) { 247 last_error_ = "chunk size is bigger than given data"; 248 return false; 249 } 250 251 if (isTruthy((size | header_size) & 0x03)) { 252 last_error_ = "header sizes are not aligned on 4-byte boundary"; 253 return false; 254 } 255 return true; 256 } 257 } 258 } 259