diff options
author | Adam Lesinski <adamlesinski@google.com> | 2017-10-20 19:15:54 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2017-10-20 19:15:54 +0000 |
commit | 7fd74b5ad1df53b92f08a9aa2f3d445bf7b786fe (patch) | |
tree | 03bbf941ec0fead1ad64ba2acd06113486a612c1 /tools/aapt2/format/Container.cpp | |
parent | 4bf7416a062af25574f548846e60e88df332cc55 (diff) | |
parent | e59f0d80ec19249f72c07ae191ad673d040443e3 (diff) |
Merge changes I1a4b3ce5,Id7216e5b
* changes:
AAPT2: Enable building proto artifacts
AAPT2: Define and Implement AAPT Container Format
Diffstat (limited to 'tools/aapt2/format/Container.cpp')
-rw-r--r-- | tools/aapt2/format/Container.cpp | 353 |
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 |