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