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