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