blob: d4b45717e0150ea51f836f0e0bf8f391ee89ba74 [file] [log] [blame]
Adam Lesinski00451162017-10-03 07:44:08 -07001/*
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
22using ::android::base::StringPrintf;
23using ::google::protobuf::io::CodedInputStream;
24using ::google::protobuf::io::CodedOutputStream;
25using ::google::protobuf::io::ZeroCopyOutputStream;
26
27namespace aapt {
28
29constexpr const static uint32_t kContainerFormatMagic = 0x54504141u;
30constexpr const static uint32_t kContainerFormatVersion = 1u;
31
32ContainerWriter::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
50inline 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
57bool 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
87bool 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
150bool ContainerWriter::HadError() const {
151 return !error_.empty();
152}
153
154std::string ContainerWriter::GetError() const {
155 return error_;
156}
157
158static 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
166ContainerReaderEntry::ContainerReaderEntry(ContainerReader* reader) : reader_(reader) {
167}
168
169ContainerEntryType ContainerReaderEntry::Type() const {
170 return type_;
171}
172
173bool 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
192bool 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
249bool ContainerReaderEntry::HadError() const {
250 return reader_->HadError();
251}
252
253std::string ContainerReaderEntry::GetError() const {
254 return reader_->GetError();
255}
256
257ContainerReader::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) {
Donald Chai9f191112018-08-13 17:12:42 -0700273 error_ =
274 StringPrintf("magic value is 0x%08x but AAPT expects 0x%08x", magic, kContainerFormatMagic);
Adam Lesinski00451162017-10-03 07:44:08 -0700275 return;
276 }
277
278 ::google::protobuf::uint32 version;
279 if (!coded_in_.ReadLittleEndian32(&version)) {
280 std::ostringstream error;
281 error << "failed to read version from input: " << in_->GetError();
282 error_ = error.str();
283 return;
284 }
285
286 if (version != kContainerFormatVersion) {
287 error_ = StringPrintf("container version is 0x%08x but AAPT expects version 0x%08x", version,
288 kContainerFormatVersion);
289 return;
290 }
291
292 ::google::protobuf::uint32 total_entry_count;
293 if (!coded_in_.ReadLittleEndian32(&total_entry_count)) {
294 std::ostringstream error;
295 error << "failed to read entry count from input: " << in_->GetError();
296 error_ = error.str();
297 return;
298 }
299
300 total_entry_count_ = total_entry_count;
301}
302
303ContainerReaderEntry* ContainerReader::Next() {
304 if (current_entry_count_ >= total_entry_count_) {
305 return nullptr;
306 }
307 current_entry_count_++;
308
309 // Ensure the next read is aligned.
310 AlignRead(&coded_in_);
311
312 ::google::protobuf::uint32 entry_type;
313 if (!coded_in_.ReadLittleEndian32(&entry_type)) {
314 std::ostringstream error;
315 error << "failed reading entry type from input: " << in_->GetError();
316 error_ = error.str();
317 return nullptr;
318 }
319
320 ::google::protobuf::uint64 entry_length;
321 if (!coded_in_.ReadLittleEndian64(&entry_length)) {
322 std::ostringstream error;
323 error << "failed reading entry length from input: " << in_->GetError();
324 error_ = error.str();
325 return nullptr;
326 }
327
328 if (entry_type == ContainerEntryType::kResFile || entry_type == ContainerEntryType::kResTable) {
329 entry_.type_ = static_cast<ContainerEntryType>(entry_type);
330 } else {
331 error_ = StringPrintf("entry type 0x%08x is invalid", entry_type);
332 return nullptr;
333 }
334
335 if (entry_length > std::numeric_limits<size_t>::max()) {
336 std::ostringstream error;
337 error << "entry length " << entry_length << " is too large";
338 error_ = error.str();
339 return nullptr;
340 }
341
342 entry_.length_ = entry_length;
343 return &entry_;
344}
345
346bool ContainerReader::HadError() const {
347 return !error_.empty();
348}
349
350std::string ContainerReader::GetError() const {
351 return error_;
352}
353
354} // namespace aapt