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