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() { return !HadError() && len_ != 0; }; 153 // Returns whether there was an error and processing should stop HadError()154 boolean HadError() { return last_error_ != null; } GetLastError()155 String GetLastError() { return last_error_; } 156 // Returns whether there was an error and processing should stop. For legacy purposes, 157 // some errors are considered "non fatal". Fatal errors stop processing new chunks and 158 // throw away any chunks already processed. Non fatal errors also stop processing new 159 // chunks, but, will retain and use any valid chunks already processed. HadFatalError()160 boolean HadFatalError() { return HadError() && last_error_was_fatal_; } 161 Next()162 Chunk Next() { 163 assert (len_ != 0) : "called Next() after last chunk"; 164 165 ResChunk_header this_chunk = next_chunk_; 166 167 // We've already checked the values of this_chunk, so safely increment. 168 // next_chunk_ = reinterpret_cast<const ResChunk_header*>( 169 // reinterpret_cast<const uint8_t*>(this_chunk) + dtohl(this_chunk->size)); 170 int remaining = len_ - dtohl(this_chunk.size); 171 if (remaining <= 0) { 172 next_chunk_ = null; 173 } else { 174 next_chunk_ = new ResChunk_header( 175 this_chunk.myBuf(), this_chunk.myOffset() + dtohl(this_chunk.size)); 176 } 177 len_ -= dtohl(this_chunk.size); 178 179 if (len_ != 0) { 180 // Prepare the next chunk. 181 if (VerifyNextChunkNonFatal()) { 182 VerifyNextChunk(); 183 } 184 } 185 return new Chunk(this_chunk); 186 } 187 188 // Returns false if there was an error. For legacy purposes. VerifyNextChunkNonFatal()189 boolean VerifyNextChunkNonFatal() { 190 if (len_ < ResChunk_header.SIZEOF) { 191 last_error_ = "not enough space for header"; 192 last_error_was_fatal_ = false; 193 return false; 194 } 195 int size = dtohl(next_chunk_.size); 196 if (size > len_) { 197 last_error_ = "chunk size is bigger than given data"; 198 last_error_was_fatal_ = false; 199 return false; 200 } 201 return true; 202 } 203 204 // Returns false if there was an error. VerifyNextChunk()205 boolean VerifyNextChunk() { 206 // uintptr_t header_start = reinterpret_cast<uintptr_t>(next_chunk_); 207 int header_start = next_chunk_.myOffset(); 208 209 // This data must be 4-byte aligned, since we directly 210 // access 32-bit words, which must be aligned on 211 // certain architectures. 212 if (isTruthy(header_start & 0x03)) { 213 last_error_ = "header not aligned on 4-byte boundary"; 214 return false; 215 } 216 217 if (len_ < ResChunk_header.SIZEOF) { 218 last_error_ = "not enough space for header"; 219 return false; 220 } 221 222 int header_size = dtohs(next_chunk_.headerSize); 223 int size = dtohl(next_chunk_.size); 224 if (header_size < ResChunk_header.SIZEOF) { 225 last_error_ = "header size too small"; 226 return false; 227 } 228 229 if (header_size > size) { 230 last_error_ = "header size is larger than entire chunk"; 231 return false; 232 } 233 234 if (size > len_) { 235 last_error_ = "chunk size is bigger than given data"; 236 return false; 237 } 238 239 if (isTruthy((size | header_size) & 0x03)) { 240 last_error_ = "header sizes are not aligned on 4-byte boundary"; 241 return false; 242 } 243 return true; 244 } 245 } 246 } 247