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