Home | History | Annotate | Download | only in format
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include "format/Container.h"
     18 
     19 #include "android-base/scopeguard.h"
     20 #include "android-base/stringprintf.h"
     21 
     22 using ::android::base::StringPrintf;
     23 using ::google::protobuf::io::CodedInputStream;
     24 using ::google::protobuf::io::CodedOutputStream;
     25 using ::google::protobuf::io::ZeroCopyOutputStream;
     26 
     27 namespace aapt {
     28 
     29 constexpr const static uint32_t kContainerFormatMagic = 0x54504141u;
     30 constexpr const static uint32_t kContainerFormatVersion = 1u;
     31 
     32 ContainerWriter::ContainerWriter(ZeroCopyOutputStream* out, size_t entry_count)
     33     : out_(out), total_entry_count_(entry_count), current_entry_count_(0u) {
     34   CodedOutputStream coded_out(out_);
     35 
     36   // Write the magic.
     37   coded_out.WriteLittleEndian32(kContainerFormatMagic);
     38 
     39   // Write the version.
     40   coded_out.WriteLittleEndian32(kContainerFormatVersion);
     41 
     42   // Write the total number of entries.
     43   coded_out.WriteLittleEndian32(static_cast<uint32_t>(total_entry_count_));
     44 
     45   if (coded_out.HadError()) {
     46     error_ = "failed writing container format header";
     47   }
     48 }
     49 
     50 inline static void WritePadding(int padding, CodedOutputStream* out) {
     51   if (padding < 4) {
     52     const uint32_t zero = 0u;
     53     out->WriteRaw(&zero, padding);
     54   }
     55 }
     56 
     57 bool ContainerWriter::AddResTableEntry(const pb::ResourceTable& table) {
     58   if (current_entry_count_ >= total_entry_count_) {
     59     error_ = "too many entries being serialized";
     60     return false;
     61   }
     62   current_entry_count_++;
     63 
     64   CodedOutputStream coded_out(out_);
     65 
     66   // Write the type.
     67   coded_out.WriteLittleEndian32(kResTable);
     68 
     69   // Write the aligned size.
     70   const ::google::protobuf::uint64 size = table.ByteSize();
     71   const int padding = 4 - (size % 4);
     72   coded_out.WriteLittleEndian64(size);
     73 
     74   // Write the table.
     75   table.SerializeWithCachedSizes(&coded_out);
     76 
     77   // Write the padding.
     78   WritePadding(padding, &coded_out);
     79 
     80   if (coded_out.HadError()) {
     81     error_ = "failed writing to output";
     82     return false;
     83   }
     84   return true;
     85 }
     86 
     87 bool ContainerWriter::AddResFileEntry(const pb::internal::CompiledFile& file,
     88                                       io::KnownSizeInputStream* in) {
     89   if (current_entry_count_ >= total_entry_count_) {
     90     error_ = "too many entries being serialized";
     91     return false;
     92   }
     93   current_entry_count_++;
     94 
     95   constexpr const static int kResFileEntryHeaderSize = 12;
     96 
     97   CodedOutputStream coded_out(out_);
     98 
     99   // Write the type.
    100   coded_out.WriteLittleEndian32(kResFile);
    101 
    102   // Write the aligned size.
    103   const ::google::protobuf::uint32 header_size = file.ByteSize();
    104   const int header_padding = 4 - (header_size % 4);
    105   const ::google::protobuf::uint64 data_size = in->TotalSize();
    106   const int data_padding = 4 - (data_size % 4);
    107   coded_out.WriteLittleEndian64(kResFileEntryHeaderSize + header_size + header_padding + data_size +
    108                                 data_padding);
    109 
    110   // Write the res file header size.
    111   coded_out.WriteLittleEndian32(header_size);
    112 
    113   // Write the data payload size.
    114   coded_out.WriteLittleEndian64(data_size);
    115 
    116   // Write the header.
    117   file.SerializeToCodedStream(&coded_out);
    118 
    119   WritePadding(header_padding, &coded_out);
    120 
    121   // Write the data payload. We need to call Trim() since we are going to write to the underlying
    122   // ZeroCopyOutputStream.
    123   coded_out.Trim();
    124 
    125   // Check at this point if there were any errors.
    126   if (coded_out.HadError()) {
    127     error_ = "failed writing to output";
    128     return false;
    129   }
    130 
    131   if (!io::Copy(out_, in)) {
    132     if (in->HadError()) {
    133       std::ostringstream error;
    134       error << "failed reading from input: " << in->GetError();
    135       error_ = error.str();
    136     } else {
    137       error_ = "failed writing to output";
    138     }
    139     return false;
    140   }
    141   WritePadding(data_padding, &coded_out);
    142 
    143   if (coded_out.HadError()) {
    144     error_ = "failed writing to output";
    145     return false;
    146   }
    147   return true;
    148 }
    149 
    150 bool ContainerWriter::HadError() const {
    151   return !error_.empty();
    152 }
    153 
    154 std::string ContainerWriter::GetError() const {
    155   return error_;
    156 }
    157 
    158 static bool AlignRead(CodedInputStream* in) {
    159   const int padding = 4 - (in->CurrentPosition() % 4);
    160   if (padding < 4) {
    161     return in->Skip(padding);
    162   }
    163   return true;
    164 }
    165 
    166 ContainerReaderEntry::ContainerReaderEntry(ContainerReader* reader) : reader_(reader) {
    167 }
    168 
    169 ContainerEntryType ContainerReaderEntry::Type() const {
    170   return type_;
    171 }
    172 
    173 bool ContainerReaderEntry::GetResTable(pb::ResourceTable* out_table) {
    174   CHECK(type_ == ContainerEntryType::kResTable) << "reading a kResTable when the type is kResFile";
    175   if (length_ > std::numeric_limits<int>::max()) {
    176     reader_->error_ = StringPrintf("entry length %zu is too large", length_);
    177     return false;
    178   }
    179 
    180   CodedInputStream& coded_in = reader_->coded_in_;
    181 
    182   const CodedInputStream::Limit limit = coded_in.PushLimit(static_cast<int>(length_));
    183   auto guard = ::android::base::make_scope_guard([&]() { coded_in.PopLimit(limit); });
    184 
    185   if (!out_table->ParseFromCodedStream(&coded_in)) {
    186     reader_->error_ = "failed to parse ResourceTable";
    187     return false;
    188   }
    189   return true;
    190 }
    191 
    192 bool ContainerReaderEntry::GetResFileOffsets(pb::internal::CompiledFile* out_file,
    193                                              off64_t* out_offset, size_t* out_len) {
    194   CHECK(type_ == ContainerEntryType::kResFile) << "reading a kResFile when the type is kResTable";
    195 
    196   CodedInputStream& coded_in = reader_->coded_in_;
    197 
    198   // Read the ResFile header.
    199   ::google::protobuf::uint32 header_length;
    200   if (!coded_in.ReadLittleEndian32(&header_length)) {
    201     std::ostringstream error;
    202     error << "failed to read header length from input: " << reader_->in_->GetError();
    203     reader_->error_ = error.str();
    204     return false;
    205   }
    206 
    207   ::google::protobuf::uint64 data_length;
    208   if (!coded_in.ReadLittleEndian64(&data_length)) {
    209     std::ostringstream error;
    210     error << "failed to read data length from input: " << reader_->in_->GetError();
    211     reader_->error_ = error.str();
    212     return false;
    213   }
    214 
    215   if (header_length > std::numeric_limits<int>::max()) {
    216     std::ostringstream error;
    217     error << "header length " << header_length << " is too large";
    218     reader_->error_ = error.str();
    219     return false;
    220   }
    221 
    222   if (data_length > std::numeric_limits<size_t>::max()) {
    223     std::ostringstream error;
    224     error << "data length " << data_length << " is too large";
    225     reader_->error_ = error.str();
    226     return false;
    227   }
    228 
    229   {
    230     const CodedInputStream::Limit limit = coded_in.PushLimit(static_cast<int>(header_length));
    231     auto guard = ::android::base::make_scope_guard([&]() { coded_in.PopLimit(limit); });
    232 
    233     if (!out_file->ParseFromCodedStream(&coded_in)) {
    234       reader_->error_ = "failed to parse CompiledFile header";
    235       return false;
    236     }
    237   }
    238 
    239   AlignRead(&coded_in);
    240 
    241   *out_offset = coded_in.CurrentPosition();
    242   *out_len = data_length;
    243 
    244   coded_in.Skip(static_cast<int>(data_length));
    245   AlignRead(&coded_in);
    246   return true;
    247 }
    248 
    249 bool ContainerReaderEntry::HadError() const {
    250   return reader_->HadError();
    251 }
    252 
    253 std::string ContainerReaderEntry::GetError() const {
    254   return reader_->GetError();
    255 }
    256 
    257 ContainerReader::ContainerReader(io::InputStream* in)
    258     : in_(in),
    259       adaptor_(in),
    260       coded_in_(&adaptor_),
    261       total_entry_count_(0u),
    262       current_entry_count_(0u),
    263       entry_(this) {
    264   ::google::protobuf::uint32 magic;
    265   if (!coded_in_.ReadLittleEndian32(&magic)) {
    266     std::ostringstream error;
    267     error << "failed to read magic from input: " << in_->GetError();
    268     error_ = error.str();
    269     return;
    270   }
    271 
    272   if (magic != kContainerFormatMagic) {
    273     error_ = "magic value doesn't match AAPT";
    274     return;
    275   }
    276 
    277   ::google::protobuf::uint32 version;
    278   if (!coded_in_.ReadLittleEndian32(&version)) {
    279     std::ostringstream error;
    280     error << "failed to read version from input: " << in_->GetError();
    281     error_ = error.str();
    282     return;
    283   }
    284 
    285   if (version != kContainerFormatVersion) {
    286     error_ = StringPrintf("container version is 0x%08x but AAPT expects version 0x%08x", version,
    287                           kContainerFormatVersion);
    288     return;
    289   }
    290 
    291   ::google::protobuf::uint32 total_entry_count;
    292   if (!coded_in_.ReadLittleEndian32(&total_entry_count)) {
    293     std::ostringstream error;
    294     error << "failed to read entry count from input: " << in_->GetError();
    295     error_ = error.str();
    296     return;
    297   }
    298 
    299   total_entry_count_ = total_entry_count;
    300 }
    301 
    302 ContainerReaderEntry* ContainerReader::Next() {
    303   if (current_entry_count_ >= total_entry_count_) {
    304     return nullptr;
    305   }
    306   current_entry_count_++;
    307 
    308   // Ensure the next read is aligned.
    309   AlignRead(&coded_in_);
    310 
    311   ::google::protobuf::uint32 entry_type;
    312   if (!coded_in_.ReadLittleEndian32(&entry_type)) {
    313     std::ostringstream error;
    314     error << "failed reading entry type from input: " << in_->GetError();
    315     error_ = error.str();
    316     return nullptr;
    317   }
    318 
    319   ::google::protobuf::uint64 entry_length;
    320   if (!coded_in_.ReadLittleEndian64(&entry_length)) {
    321     std::ostringstream error;
    322     error << "failed reading entry length from input: " << in_->GetError();
    323     error_ = error.str();
    324     return nullptr;
    325   }
    326 
    327   if (entry_type == ContainerEntryType::kResFile || entry_type == ContainerEntryType::kResTable) {
    328     entry_.type_ = static_cast<ContainerEntryType>(entry_type);
    329   } else {
    330     error_ = StringPrintf("entry type 0x%08x is invalid", entry_type);
    331     return nullptr;
    332   }
    333 
    334   if (entry_length > std::numeric_limits<size_t>::max()) {
    335     std::ostringstream error;
    336     error << "entry length " << entry_length << " is too large";
    337     error_ = error.str();
    338     return nullptr;
    339   }
    340 
    341   entry_.length_ = entry_length;
    342   return &entry_;
    343 }
    344 
    345 bool ContainerReader::HadError() const {
    346   return !error_.empty();
    347 }
    348 
    349 std::string ContainerReader::GetError() const {
    350   return error_;
    351 }
    352 
    353 }  // namespace aapt
    354