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