• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ContainerWriter(ZeroCopyOutputStream * out,size_t entry_count)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 
WritePadding(int padding,CodedOutputStream * out)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 
AddResTableEntry(const pb::ResourceTable & table)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 
AddResFileEntry(const pb::internal::CompiledFile & file,io::KnownSizeInputStream * in)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 
HadError() const150 bool ContainerWriter::HadError() const {
151   return !error_.empty();
152 }
153 
GetError() const154 std::string ContainerWriter::GetError() const {
155   return error_;
156 }
157 
AlignRead(CodedInputStream * in)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 
ContainerReaderEntry(ContainerReader * reader)166 ContainerReaderEntry::ContainerReaderEntry(ContainerReader* reader) : reader_(reader) {
167 }
168 
Type() const169 ContainerEntryType ContainerReaderEntry::Type() const {
170   return type_;
171 }
172 
GetResTable(pb::ResourceTable * out_table)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 
GetResFileOffsets(pb::internal::CompiledFile * out_file,off64_t * out_offset,size_t * out_len)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 
HadError() const249 bool ContainerReaderEntry::HadError() const {
250   return reader_->HadError();
251 }
252 
GetError() const253 std::string ContainerReaderEntry::GetError() const {
254   return reader_->GetError();
255 }
256 
ContainerReader(io::InputStream * in)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 
Next()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 
HadError() const345 bool ContainerReader::HadError() const {
346   return !error_.empty();
347 }
348 
GetError() const349 std::string ContainerReader::GetError() const {
350   return error_;
351 }
352 
353 }  // namespace aapt
354