• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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