blob: 9318af9e74bf6ca5e99a3e20facc9c154aecac48 [file] [log] [blame]
package org.robolectric.res.android;
import static org.robolectric.res.android.Util.dtohl;
import static org.robolectric.res.android.Util.dtohs;
import static org.robolectric.res.android.Util.isTruthy;
import java.nio.ByteBuffer;
import org.robolectric.res.android.ResourceTypes.ResChunk_header;
import org.robolectric.res.android.ResourceTypes.ResStringPool_header;
import org.robolectric.res.android.ResourceTypes.ResTable_header;
import org.robolectric.res.android.ResourceTypes.ResTable_lib_entry;
import org.robolectric.res.android.ResourceTypes.ResTable_lib_header;
import org.robolectric.res.android.ResourceTypes.ResTable_package;
import org.robolectric.res.android.ResourceTypes.ResTable_type;
import org.robolectric.res.android.ResourceTypes.WithOffset;
// transliterated from
// https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/ChunkIterator.cpp and
// https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/include/androidfw/Chunk.h
// Helpful wrapper around a ResChunk_header that provides getter methods
// that handle endianness conversions and provide access to the data portion
// of the chunk.
class Chunk {
// public:
Chunk(ResChunk_header chunk) {
this.device_chunk_ = chunk;
}
// Returns the type of the chunk. Caller need not worry about endianness.
int type() {
return dtohs(device_chunk_.type);
}
// Returns the size of the entire chunk. This can be useful for skipping
// over the entire chunk. Caller need not worry about endianness.
int size() {
return dtohl(device_chunk_.size);
}
// Returns the size of the header. Caller need not worry about endianness.
int header_size() {
return dtohs(device_chunk_.headerSize);
}
// template <typename T, int MinSize = sizeof(T)>
// T* header() {
// if (header_size() >= MinSize) {
// return reinterpret_cast<T*>(device_chunk_);
// }
// return nullptr;
// }
ByteBuffer myBuf() {
return device_chunk_.myBuf();
}
int myOffset() {
return device_chunk_.myOffset();
}
public WithOffset data_ptr() {
return new WithOffset(device_chunk_.myBuf(), device_chunk_.myOffset() + header_size());
}
int data_size() {
return size() - header_size();
}
// private:
private ResChunk_header device_chunk_;
public ResTable_header asResTable_header() {
if (header_size() >= ResTable_header.SIZEOF) {
return new ResTable_header(device_chunk_.myBuf(), device_chunk_.myOffset());
} else {
return null;
}
}
public ResStringPool_header asResStringPool_header() {
if (header_size() >= ResStringPool_header.SIZEOF) {
return new ResStringPool_header(device_chunk_.myBuf(), device_chunk_.myOffset());
} else {
return null;
}
}
public ResTable_package asResTable_package(int size) {
if (header_size() >= size) {
return new ResTable_package(device_chunk_.myBuf(), device_chunk_.myOffset());
} else {
return null;
}
}
public ResTable_type asResTable_type(int size) {
if (header_size() >= size) {
return new ResTable_type(device_chunk_.myBuf(), device_chunk_.myOffset());
} else {
return null;
}
}
public ResTable_lib_header asResTable_lib_header() {
if (header_size() >= ResTable_lib_header.SIZEOF) {
return new ResTable_lib_header(device_chunk_.myBuf(), device_chunk_.myOffset());
} else {
return null;
}
}
public ResTable_lib_entry asResTable_lib_entry() {
if (header_size() >= ResTable_lib_entry.SIZEOF) {
return new ResTable_lib_entry(device_chunk_.myBuf(), device_chunk_.myOffset());
} else {
return null;
}
}
static class Iterator {
private ResChunk_header next_chunk_;
private int len_;
private String last_error_;
private boolean last_error_was_fatal_ = true;
public Iterator(WithOffset buf, int itemSize) {
this.next_chunk_ = new ResChunk_header(buf.myBuf(), buf.myOffset());
this.len_ = itemSize;
}
boolean HasNext() { return !HadError() && len_ != 0; };
// Returns whether there was an error and processing should stop
boolean HadError() { return last_error_ != null; }
String GetLastError() { return last_error_; }
// Returns whether there was an error and processing should stop. For legacy purposes,
// some errors are considered "non fatal". Fatal errors stop processing new chunks and
// throw away any chunks already processed. Non fatal errors also stop processing new
// chunks, but, will retain and use any valid chunks already processed.
boolean HadFatalError() { return HadError() && last_error_was_fatal_; }
Chunk Next() {
assert (len_ != 0) : "called Next() after last chunk";
ResChunk_header this_chunk = next_chunk_;
// We've already checked the values of this_chunk, so safely increment.
// next_chunk_ = reinterpret_cast<const ResChunk_header*>(
// reinterpret_cast<const uint8_t*>(this_chunk) + dtohl(this_chunk->size));
int remaining = len_ - dtohl(this_chunk.size);
if (remaining <= 0) {
next_chunk_ = null;
} else {
next_chunk_ = new ResChunk_header(
this_chunk.myBuf(), this_chunk.myOffset() + dtohl(this_chunk.size));
}
len_ -= dtohl(this_chunk.size);
if (len_ != 0) {
// Prepare the next chunk.
if (VerifyNextChunkNonFatal()) {
VerifyNextChunk();
}
}
return new Chunk(this_chunk);
}
// Returns false if there was an error. For legacy purposes.
boolean VerifyNextChunkNonFatal() {
if (len_ < ResChunk_header.SIZEOF) {
last_error_ = "not enough space for header";
last_error_was_fatal_ = false;
return false;
}
int size = dtohl(next_chunk_.size);
if (size > len_) {
last_error_ = "chunk size is bigger than given data";
last_error_was_fatal_ = false;
return false;
}
return true;
}
// Returns false if there was an error.
boolean VerifyNextChunk() {
// uintptr_t header_start = reinterpret_cast<uintptr_t>(next_chunk_);
int header_start = next_chunk_.myOffset();
// This data must be 4-byte aligned, since we directly
// access 32-bit words, which must be aligned on
// certain architectures.
if (isTruthy(header_start & 0x03)) {
last_error_ = "header not aligned on 4-byte boundary";
return false;
}
if (len_ < ResChunk_header.SIZEOF) {
last_error_ = "not enough space for header";
return false;
}
int header_size = dtohs(next_chunk_.headerSize);
int size = dtohl(next_chunk_.size);
if (header_size < ResChunk_header.SIZEOF) {
last_error_ = "header size too small";
return false;
}
if (header_size > size) {
last_error_ = "header size is larger than entire chunk";
return false;
}
if (size > len_) {
last_error_ = "chunk size is bigger than given data";
return false;
}
if (isTruthy((size | header_size) & 0x03)) {
last_error_ = "header sizes are not aligned on 4-byte boundary";
return false;
}
return true;
}
}
}