summaryrefslogtreecommitdiff
path: root/tools/aapt2/format/Container.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/aapt2/format/Container.cpp')
-rw-r--r--tools/aapt2/format/Container.cpp353
1 files changed, 353 insertions, 0 deletions
diff --git a/tools/aapt2/format/Container.cpp b/tools/aapt2/format/Container.cpp
new file mode 100644
index 000000000000..739555c5b15d
--- /dev/null
+++ b/tools/aapt2/format/Container.cpp
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "format/Container.h"
+
+#include "android-base/scopeguard.h"
+#include "android-base/stringprintf.h"
+
+using ::android::base::StringPrintf;
+using ::google::protobuf::io::CodedInputStream;
+using ::google::protobuf::io::CodedOutputStream;
+using ::google::protobuf::io::ZeroCopyOutputStream;
+
+namespace aapt {
+
+constexpr const static uint32_t kContainerFormatMagic = 0x54504141u;
+constexpr const static uint32_t kContainerFormatVersion = 1u;
+
+ContainerWriter::ContainerWriter(ZeroCopyOutputStream* out, size_t entry_count)
+ : out_(out), total_entry_count_(entry_count), current_entry_count_(0u) {
+ CodedOutputStream coded_out(out_);
+
+ // Write the magic.
+ coded_out.WriteLittleEndian32(kContainerFormatMagic);
+
+ // Write the version.
+ coded_out.WriteLittleEndian32(kContainerFormatVersion);
+
+ // Write the total number of entries.
+ coded_out.WriteLittleEndian32(static_cast<uint32_t>(total_entry_count_));
+
+ if (coded_out.HadError()) {
+ error_ = "failed writing container format header";
+ }
+}
+
+inline static void WritePadding(int padding, CodedOutputStream* out) {
+ if (padding < 4) {
+ const uint32_t zero = 0u;
+ out->WriteRaw(&zero, padding);
+ }
+}
+
+bool ContainerWriter::AddResTableEntry(const pb::ResourceTable& table) {
+ if (current_entry_count_ >= total_entry_count_) {
+ error_ = "too many entries being serialized";
+ return false;
+ }
+ current_entry_count_++;
+
+ CodedOutputStream coded_out(out_);
+
+ // Write the type.
+ coded_out.WriteLittleEndian32(kResTable);
+
+ // Write the aligned size.
+ const ::google::protobuf::uint64 size = table.ByteSize();
+ const int padding = 4 - (size % 4);
+ coded_out.WriteLittleEndian64(size);
+
+ // Write the table.
+ table.SerializeWithCachedSizes(&coded_out);
+
+ // Write the padding.
+ WritePadding(padding, &coded_out);
+
+ if (coded_out.HadError()) {
+ error_ = "failed writing to output";
+ return false;
+ }
+ return true;
+}
+
+bool ContainerWriter::AddResFileEntry(const pb::internal::CompiledFile& file,
+ io::KnownSizeInputStream* in) {
+ if (current_entry_count_ >= total_entry_count_) {
+ error_ = "too many entries being serialized";
+ return false;
+ }
+ current_entry_count_++;
+
+ constexpr const static int kResFileEntryHeaderSize = 12;
+
+ CodedOutputStream coded_out(out_);
+
+ // Write the type.
+ coded_out.WriteLittleEndian32(kResFile);
+
+ // Write the aligned size.
+ const ::google::protobuf::uint32 header_size = file.ByteSize();
+ const int header_padding = 4 - (header_size % 4);
+ const ::google::protobuf::uint64 data_size = in->TotalSize();
+ const int data_padding = 4 - (data_size % 4);
+ coded_out.WriteLittleEndian64(kResFileEntryHeaderSize + header_size + header_padding + data_size +
+ data_padding);
+
+ // Write the res file header size.
+ coded_out.WriteLittleEndian32(header_size);
+
+ // Write the data payload size.
+ coded_out.WriteLittleEndian64(data_size);
+
+ // Write the header.
+ file.SerializeToCodedStream(&coded_out);
+
+ WritePadding(header_padding, &coded_out);
+
+ // Write the data payload. We need to call Trim() since we are going to write to the underlying
+ // ZeroCopyOutputStream.
+ coded_out.Trim();
+
+ // Check at this point if there were any errors.
+ if (coded_out.HadError()) {
+ error_ = "failed writing to output";
+ return false;
+ }
+
+ if (!io::Copy(out_, in)) {
+ if (in->HadError()) {
+ std::ostringstream error;
+ error << "failed reading from input: " << in->GetError();
+ error_ = error.str();
+ } else {
+ error_ = "failed writing to output";
+ }
+ return false;
+ }
+ WritePadding(data_padding, &coded_out);
+
+ if (coded_out.HadError()) {
+ error_ = "failed writing to output";
+ return false;
+ }
+ return true;
+}
+
+bool ContainerWriter::HadError() const {
+ return !error_.empty();
+}
+
+std::string ContainerWriter::GetError() const {
+ return error_;
+}
+
+static bool AlignRead(CodedInputStream* in) {
+ const int padding = 4 - (in->CurrentPosition() % 4);
+ if (padding < 4) {
+ return in->Skip(padding);
+ }
+ return true;
+}
+
+ContainerReaderEntry::ContainerReaderEntry(ContainerReader* reader) : reader_(reader) {
+}
+
+ContainerEntryType ContainerReaderEntry::Type() const {
+ return type_;
+}
+
+bool ContainerReaderEntry::GetResTable(pb::ResourceTable* out_table) {
+ CHECK(type_ == ContainerEntryType::kResTable) << "reading a kResTable when the type is kResFile";
+ if (length_ > std::numeric_limits<int>::max()) {
+ reader_->error_ = StringPrintf("entry length %zu is too large", length_);
+ return false;
+ }
+
+ CodedInputStream& coded_in = reader_->coded_in_;
+
+ const CodedInputStream::Limit limit = coded_in.PushLimit(static_cast<int>(length_));
+ auto guard = ::android::base::make_scope_guard([&]() { coded_in.PopLimit(limit); });
+
+ if (!out_table->ParseFromCodedStream(&coded_in)) {
+ reader_->error_ = "failed to parse ResourceTable";
+ return false;
+ }
+ return true;
+}
+
+bool ContainerReaderEntry::GetResFileOffsets(pb::internal::CompiledFile* out_file,
+ off64_t* out_offset, size_t* out_len) {
+ CHECK(type_ == ContainerEntryType::kResFile) << "reading a kResFile when the type is kResTable";
+
+ CodedInputStream& coded_in = reader_->coded_in_;
+
+ // Read the ResFile header.
+ ::google::protobuf::uint32 header_length;
+ if (!coded_in.ReadLittleEndian32(&header_length)) {
+ std::ostringstream error;
+ error << "failed to read header length from input: " << reader_->in_->GetError();
+ reader_->error_ = error.str();
+ return false;
+ }
+
+ ::google::protobuf::uint64 data_length;
+ if (!coded_in.ReadLittleEndian64(&data_length)) {
+ std::ostringstream error;
+ error << "failed to read data length from input: " << reader_->in_->GetError();
+ reader_->error_ = error.str();
+ return false;
+ }
+
+ if (header_length > std::numeric_limits<int>::max()) {
+ std::ostringstream error;
+ error << "header length " << header_length << " is too large";
+ reader_->error_ = error.str();
+ return false;
+ }
+
+ if (data_length > std::numeric_limits<size_t>::max()) {
+ std::ostringstream error;
+ error << "data length " << data_length << " is too large";
+ reader_->error_ = error.str();
+ return false;
+ }
+
+ {
+ const CodedInputStream::Limit limit = coded_in.PushLimit(static_cast<int>(header_length));
+ auto guard = ::android::base::make_scope_guard([&]() { coded_in.PopLimit(limit); });
+
+ if (!out_file->ParseFromCodedStream(&coded_in)) {
+ reader_->error_ = "failed to parse CompiledFile header";
+ return false;
+ }
+ }
+
+ AlignRead(&coded_in);
+
+ *out_offset = coded_in.CurrentPosition();
+ *out_len = data_length;
+
+ coded_in.Skip(static_cast<int>(data_length));
+ AlignRead(&coded_in);
+ return true;
+}
+
+bool ContainerReaderEntry::HadError() const {
+ return reader_->HadError();
+}
+
+std::string ContainerReaderEntry::GetError() const {
+ return reader_->GetError();
+}
+
+ContainerReader::ContainerReader(io::InputStream* in)
+ : in_(in),
+ adaptor_(in),
+ coded_in_(&adaptor_),
+ total_entry_count_(0u),
+ current_entry_count_(0u),
+ entry_(this) {
+ ::google::protobuf::uint32 magic;
+ if (!coded_in_.ReadLittleEndian32(&magic)) {
+ std::ostringstream error;
+ error << "failed to read magic from input: " << in_->GetError();
+ error_ = error.str();
+ return;
+ }
+
+ if (magic != kContainerFormatMagic) {
+ error_ = "magic value doesn't match AAPT";
+ return;
+ }
+
+ ::google::protobuf::uint32 version;
+ if (!coded_in_.ReadLittleEndian32(&version)) {
+ std::ostringstream error;
+ error << "failed to read version from input: " << in_->GetError();
+ error_ = error.str();
+ return;
+ }
+
+ if (version != kContainerFormatVersion) {
+ error_ = StringPrintf("container version is 0x%08x but AAPT expects version 0x%08x", version,
+ kContainerFormatVersion);
+ return;
+ }
+
+ ::google::protobuf::uint32 total_entry_count;
+ if (!coded_in_.ReadLittleEndian32(&total_entry_count)) {
+ std::ostringstream error;
+ error << "failed to read entry count from input: " << in_->GetError();
+ error_ = error.str();
+ return;
+ }
+
+ total_entry_count_ = total_entry_count;
+}
+
+ContainerReaderEntry* ContainerReader::Next() {
+ if (current_entry_count_ >= total_entry_count_) {
+ return nullptr;
+ }
+ current_entry_count_++;
+
+ // Ensure the next read is aligned.
+ AlignRead(&coded_in_);
+
+ ::google::protobuf::uint32 entry_type;
+ if (!coded_in_.ReadLittleEndian32(&entry_type)) {
+ std::ostringstream error;
+ error << "failed reading entry type from input: " << in_->GetError();
+ error_ = error.str();
+ return nullptr;
+ }
+
+ ::google::protobuf::uint64 entry_length;
+ if (!coded_in_.ReadLittleEndian64(&entry_length)) {
+ std::ostringstream error;
+ error << "failed reading entry length from input: " << in_->GetError();
+ error_ = error.str();
+ return nullptr;
+ }
+
+ if (entry_type == ContainerEntryType::kResFile || entry_type == ContainerEntryType::kResTable) {
+ entry_.type_ = static_cast<ContainerEntryType>(entry_type);
+ } else {
+ error_ = StringPrintf("entry type 0x%08x is invalid", entry_type);
+ return nullptr;
+ }
+
+ if (entry_length > std::numeric_limits<size_t>::max()) {
+ std::ostringstream error;
+ error << "entry length " << entry_length << " is too large";
+ error_ = error.str();
+ return nullptr;
+ }
+
+ entry_.length_ = entry_length;
+ return &entry_;
+}
+
+bool ContainerReader::HadError() const {
+ return !error_.empty();
+}
+
+std::string ContainerReader::GetError() const {
+ return error_;
+}
+
+} // namespace aapt