forked from Project-Awaken/android_system_update_engine
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlz4patch.cc
374 lines (333 loc) · 12 KB
/
lz4patch.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
//
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "lz4patch.h"
#include <endian.h>
#include <unistd.h>
#include <fcntl.h>
#include <algorithm>
#include <string_view>
#include <bsdiff/bspatch.h>
#include <bsdiff/memory_file.h>
#include <bsdiff/file.h>
#include <puffin/memory_stream.h>
#include "android-base/strings.h"
#include "lz4diff/lz4diff.h"
#include "lz4diff/lz4diff.pb.h"
#include "lz4diff_compress.h"
#include "lz4diff_format.h"
#include "puffin/puffpatch.h"
#include "update_engine/common/hash_calculator.h"
#include "update_engine/common/utils.h"
namespace chromeos_update_engine {
namespace {
template <typename T>
constexpr void BigEndianToHost(T& t) {
static_assert(std::is_integral_v<T>);
static_assert(sizeof(t) == 4 || sizeof(t) == 8 || sizeof(t) == 2);
if constexpr (sizeof(t) == 4) {
t = be32toh(t);
} else if constexpr (sizeof(t) == 8) {
t = be64toh(t);
} else if constexpr (sizeof(t) == 2) {
t = be16toh(t);
}
}
// In memory representation of an LZ4Diff patch, it's not marked as packed
// because parsing isn't as simple as reinterpret_cast<> any way.
struct Lz4diffPatch {
char magic[kLz4diffMagic.size()];
uint32_t version;
uint32_t pb_header_size; // size of protobuf message
Lz4diffHeader pb_header;
std::string_view inner_patch;
};
// Utility class to interact with puffin API. C++ does not have standard
// Read/Write trait. So everybody invent their own file descriptor wrapper.
class StringViewStream : public puffin::StreamInterface {
public:
~StringViewStream() override = default;
bool GetSize(uint64_t* size) const override {
*size = read_memory_.size();
return true;
}
bool GetOffset(uint64_t* offset) const override {
*offset = offset_;
return true;
}
bool Seek(uint64_t offset) override {
TEST_AND_RETURN_FALSE(open_);
uint64_t size;
GetSize(&size);
TEST_AND_RETURN_FALSE(offset <= size);
offset_ = offset;
return true;
}
bool Read(void* buffer, size_t length) override {
TEST_AND_RETURN_FALSE(open_);
TEST_AND_RETURN_FALSE(offset_ + length <= read_memory_.size());
memcpy(buffer, read_memory_.data() + offset_, length);
offset_ += length;
return true;
}
bool Write(const void* buffer, size_t length) override {
LOG(ERROR) << "Unsupported operation " << __FUNCTION__;
return false;
}
bool Close() override {
open_ = false;
return true;
}
constexpr StringViewStream(std::string_view read_memory)
: read_memory_(read_memory) {
CHECK(!read_memory.empty());
}
private:
// The memory buffer for reading.
std::string_view read_memory_;
// The current offset.
uint64_t offset_{};
bool open_{true};
};
bool ParseLz4DifffPatch(std::string_view patch_data, Lz4diffPatch* output) {
CHECK_NE(output, nullptr);
if (!android::base::StartsWith(patch_data, kLz4diffMagic)) {
LOG(ERROR) << "Invalid lz4diff magic: "
<< HexEncode(patch_data.substr(0, kLz4diffMagic.size()))
<< ", expected: " << HexEncode(kLz4diffMagic);
return false;
}
Lz4diffPatch& patch = *output;
std::memcpy(patch.magic, patch_data.data(), kLz4diffMagic.size());
std::memcpy(&patch.version,
patch_data.data() + kLz4diffMagic.size(),
sizeof(patch.version));
BigEndianToHost(patch.version);
if (patch.version != kLz4diffVersion) {
LOG(ERROR) << "Unsupported lz4diff version: " << patch.version
<< ", supported version: " << kLz4diffVersion;
return false;
}
std::memcpy(&patch.pb_header_size,
patch_data.data() + kLz4diffMagic.size() + sizeof(patch.version),
sizeof(patch.pb_header_size));
BigEndianToHost(patch.pb_header_size);
TEST_AND_RETURN_FALSE(patch.pb_header.ParseFromArray(
patch_data.data() + kLz4diffHeaderSize, patch.pb_header_size));
patch.inner_patch =
patch_data.substr(kLz4diffHeaderSize + patch.pb_header_size);
return true;
}
bool bspatch(std::string_view input_data,
std::string_view patch_data,
Blob* output) {
CHECK_NE(output, nullptr);
output->clear();
CHECK_GT(patch_data.size(), 0UL);
int err =
bsdiff::bspatch(reinterpret_cast<const uint8_t*>(input_data.data()),
input_data.size(),
reinterpret_cast<const uint8_t*>(patch_data.data()),
patch_data.size(),
[output](const uint8_t* data, size_t size) -> size_t {
output->insert(output->end(), data, data + size);
return size;
});
return err == 0;
}
bool puffpatch(std::string_view input_data,
std::string_view patch_data,
Blob* output) {
// Cache size has a big impact on speed of puffpatch, use a default of 5MB to
// match update_engine behavior.
static constexpr size_t kPuffPatchCacheSize = 5 * 1024 * 1024;
return puffin::PuffPatch(std::make_unique<StringViewStream>(input_data),
puffin::MemoryStream::CreateForWrite(output),
reinterpret_cast<const uint8_t*>(patch_data.data()),
patch_data.size(),
kPuffPatchCacheSize);
}
std::vector<CompressedBlock> ToCompressedBlockVec(
const google::protobuf::RepeatedPtrField<CompressedBlockInfo>& rpf) {
std::vector<CompressedBlock> ret;
ret.reserve(rpf.size());
for (const auto& block : rpf) {
auto& info = ret.emplace_back();
info.compressed_length = block.compressed_length();
info.uncompressed_length = block.uncompressed_length();
info.uncompressed_offset = block.uncompressed_offset();
}
return ret;
}
bool HasPosfixPatches(const Lz4diffPatch& patch) {
for (const auto& info : patch.pb_header.dst_info().block_info()) {
if (!info.postfix_bspatch().empty()) {
return true;
}
}
return false;
}
size_t GetCompressedSize(
const google::protobuf::RepeatedPtrField<CompressedBlockInfo>& info) {
size_t compressed_size = 0;
for (const auto& block : info) {
compressed_size += block.compressed_length();
}
return compressed_size;
}
size_t GetDecompressedSize(
const google::protobuf::RepeatedPtrField<CompressedBlockInfo>& info) {
size_t decompressed_size = 0;
for (const auto& block : info) {
decompressed_size += block.uncompressed_length();
}
return decompressed_size;
}
bool ApplyInnerPatch(Blob decompressed_src,
const Lz4diffPatch& patch,
Blob* decompressed_dst) {
switch (patch.pb_header.inner_type()) {
case InnerPatchType::BSDIFF:
TEST_AND_RETURN_FALSE(bspatch(
ToStringView(decompressed_src), patch.inner_patch, decompressed_dst));
break;
case InnerPatchType::PUFFDIFF:
TEST_AND_RETURN_FALSE(puffpatch(
ToStringView(decompressed_src), patch.inner_patch, decompressed_dst));
break;
default:
LOG(ERROR) << "Unsupported patch type: " << patch.pb_header.inner_type();
return false;
}
return true;
}
// TODO(zhangkelvin) Rewrite this in C++ 20 coroutine once that's available.
// Hand coding CPS is not fun.
bool Lz4Patch(std::string_view src_data,
const Lz4diffPatch& patch,
const SinkFunc& sink) {
auto decompressed_src = TryDecompressBlob(
src_data,
ToCompressedBlockVec(patch.pb_header.src_info().block_info()),
patch.pb_header.src_info().zero_padding_enabled());
TEST_AND_RETURN_FALSE(!decompressed_src.empty());
Blob decompressed_dst;
const auto decompressed_dst_size =
GetDecompressedSize(patch.pb_header.dst_info().block_info());
decompressed_dst.reserve(decompressed_dst_size);
ApplyInnerPatch(std::move(decompressed_src), patch, &decompressed_dst);
if (!HasPosfixPatches(patch)) {
return TryCompressBlob(
ToStringView(decompressed_dst),
ToCompressedBlockVec(patch.pb_header.dst_info().block_info()),
patch.pb_header.dst_info().zero_padding_enabled(),
patch.pb_header.dst_info().algo(),
sink);
}
auto postfix_patcher =
[&sink,
block_idx = 0,
&dst_block_info = patch.pb_header.dst_info().block_info()](
const uint8_t* data, size_t size) mutable -> size_t {
if (block_idx >= dst_block_info.size()) {
return sink(data, size);
}
const auto& block_info = dst_block_info[block_idx];
TEST_EQ(size, block_info.compressed_length());
DEFER { block_idx++; };
if (block_info.postfix_bspatch().empty()) {
return sink(data, size);
}
if (!block_info.sha256_hash().empty()) {
Blob actual_hash;
TEST_AND_RETURN_FALSE(
HashCalculator::RawHashOfBytes(data, size, &actual_hash));
if (ToStringView(actual_hash) != block_info.sha256_hash()) {
LOG(ERROR) << "Block " << block_info
<< " is corrupted. This usually means the patch generator "
"used a different version of LZ4, or an incompatible LZ4 "
"patch generator was used, or LZ4 produces different "
"output on different platforms. Expected hash: "
<< HexEncode(block_info.sha256_hash())
<< ", actual hash: " << HexEncode(actual_hash);
return 0;
}
}
Blob fixed_block;
TEST_AND_RETURN_FALSE(
bspatch(std::string_view(reinterpret_cast<const char*>(data), size),
block_info.postfix_bspatch(),
&fixed_block));
return sink(fixed_block.data(), fixed_block.size());
};
return TryCompressBlob(
ToStringView(decompressed_dst),
ToCompressedBlockVec(patch.pb_header.dst_info().block_info()),
patch.pb_header.dst_info().zero_padding_enabled(),
patch.pb_header.dst_info().algo(),
postfix_patcher);
}
bool Lz4Patch(std::string_view src_data,
const Lz4diffPatch& patch,
Blob* output) {
Blob blob;
const auto output_size =
GetCompressedSize(patch.pb_header.dst_info().block_info());
blob.reserve(output_size);
TEST_AND_RETURN_FALSE(Lz4Patch(
src_data, patch, [&blob](const uint8_t* data, size_t size) -> size_t {
blob.insert(blob.end(), data, data + size);
return size;
}));
*output = std::move(blob);
return true;
}
} // namespace
bool Lz4Patch(std::string_view src_data,
std::string_view patch_data,
Blob* output) {
Lz4diffPatch patch;
TEST_AND_RETURN_FALSE(ParseLz4DifffPatch(patch_data, &patch));
return Lz4Patch(src_data, patch, output);
}
bool Lz4Patch(std::string_view src_data,
std::string_view patch_data,
const SinkFunc& sink) {
Lz4diffPatch patch;
TEST_AND_RETURN_FALSE(ParseLz4DifffPatch(patch_data, &patch));
return Lz4Patch(src_data, patch, sink);
}
bool Lz4Patch(const Blob& src_data, const Blob& patch_data, Blob* output) {
return Lz4Patch(ToStringView(src_data), ToStringView(patch_data), output);
}
std::ostream& operator<<(std::ostream& out, const CompressionAlgorithm& info) {
out << "Algo {type: " << info.Type_Name(info.type());
if (info.level() != 0) {
out << ", level: " << info.level();
}
out << "}";
return out;
}
std::ostream& operator<<(std::ostream& out, const CompressionInfo& info) {
out << "CompressionInfo {block_info: " << info.block_info()
<< ", algo: " << info.algo() << "}";
return out;
}
std::ostream& operator<<(std::ostream& out, const Lz4diffHeader& header) {
out << "Lz4diffHeader {src_info: " << header.src_info()
<< ", dst_info: " << header.dst_info() << "}";
return out;
}
} // namespace chromeos_update_engine