• 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 (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