blob: 333148ab260cff9e430bd1c4f3217393af134b83 [file] [log] [blame]
Kelvin Zhang55624032021-12-20 12:13:24 -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 "lz4diff_compress.h"
18
19#include "update_engine/common/utils.h"
20#include "update_engine/common/hash_calculator.h"
21#include "update_engine/payload_generator/delta_diff_generator.h"
22#include "update_engine/payload_generator/payload_generation_config.h"
23
24#include <base/logging.h>
25#include <lz4.h>
26#include <lz4hc.h>
27
28namespace chromeos_update_engine {
29
30Blob TryCompressBlob(std::string_view blob,
31 const std::vector<CompressedBlock>& block_info,
32 const bool zero_padding_enabled,
33 const CompressionAlgorithm compression_algo) {
34 size_t uncompressed_size = 0;
35 size_t compressed_size = 0;
36 for (const auto& block : block_info) {
37 CHECK_EQ(uncompressed_size, block.uncompressed_offset)
38 << "Compressed block info is expected to be sorted.";
39 uncompressed_size += block.uncompressed_length;
40 compressed_size += block.compressed_length;
41 }
42 CHECK_EQ(uncompressed_size, blob.size());
43 Blob output;
44 output.resize(utils::RoundUp(compressed_size, kBlockSize));
45 auto hc = LZ4_createStreamHC();
46 DEFER {
47 if (hc) {
48 LZ4_freeStreamHC(hc);
49 hc = nullptr;
50 }
51 };
52 size_t compressed_offset = 0;
53 for (const auto& block : block_info) {
54 // Execute the increment at end of each loop
55 DEFER { compressed_offset += block.compressed_length; };
56 CHECK_LE(compressed_offset + block.compressed_length, output.size());
57
58 if (!block.IsCompressed()) {
59 std::memcpy(output.data() + compressed_offset,
60 blob.data() + block.uncompressed_offset,
61 block.compressed_length);
62 continue;
63 }
64 // LZ4 spec enforces that last op of a compressed block must be an insert op
65 // of at least 5 bytes. Compressors will try to conform to that requirement
66 // if the input size is just right. We don't want that. So always give a
67 // little bit more data.
68 int src_size = uncompressed_size - block.uncompressed_offset;
69 uint64_t bytes_written = 0;
70 switch (compression_algo.type()) {
71 case CompressionAlgorithm::LZ4HC:
72 bytes_written = LZ4_compress_HC_destSize(
73 hc,
74 blob.data() + block.uncompressed_offset,
75 reinterpret_cast<char*>(output.data()) + compressed_offset,
76 &src_size,
77 block.compressed_length,
78 compression_algo.level());
79 break;
80 case CompressionAlgorithm::LZ4:
81 bytes_written = LZ4_compress_destSize(
82 blob.data() + block.uncompressed_offset,
83 reinterpret_cast<char*>(output.data()) + compressed_offset,
84 &src_size,
85 block.compressed_length);
86 break;
87 default:
88 CHECK(false) << "Unrecognized compression algorithm: "
89 << compression_algo.type();
90 break;
91 }
92 // Last block may have trailing zeros
93 CHECK_LE(bytes_written, block.compressed_length);
94 if (bytes_written < block.compressed_length) {
95 if (zero_padding_enabled) {
96 const auto padding = block.compressed_length - bytes_written;
97 // LOG(INFO) << "Padding: " << padding;
98 CHECK_LE(compressed_offset + padding + bytes_written, output.size());
99 std::memmove(output.data() + compressed_offset + padding,
100 output.data() + compressed_offset,
101 bytes_written);
102 CHECK_LE(compressed_offset + padding, output.size());
103 std::fill(output.data() + compressed_offset,
104 output.data() + compressed_offset + padding,
105 0);
106
107 } else {
108 std::fill(output.data() + compressed_offset + bytes_written,
109 output.data() + compressed_offset + block.compressed_length,
110 0);
111 }
112 }
113 if (static_cast<uint64_t>(src_size) != block.uncompressed_length) {
114 LOG(WARNING) << "Recompress size mismatch: " << src_size << ", "
115 << block.uncompressed_length;
116 }
117 }
118 // Any trailing data will be copied to the output buffer.
119 output.insert(output.end(), blob.begin() + uncompressed_size, blob.end());
120 return output;
121}
122
123Blob TryDecompressBlob(std::string_view blob,
124 const std::vector<CompressedBlock>& block_info,
125 const bool zero_padding_enabled) {
126 if (block_info.empty()) {
127 return {};
128 }
129 size_t uncompressed_size = 0;
130 size_t compressed_size = 0;
131 for (const auto& block : block_info) {
132 CHECK_EQ(uncompressed_size, block.uncompressed_offset)
133 << " Compressed block info is expected to be sorted, expected offset "
134 << uncompressed_size << ", actual block " << block;
135 uncompressed_size += block.uncompressed_length;
136 compressed_size += block.compressed_length;
137 }
138 if (blob.size() < compressed_size) {
139 LOG(INFO) << "File is chunked. Skip lz4 decompress.Expected size : "
140 << compressed_size << ", actual size: " << blob.size();
141 return {};
142 }
143 Blob output;
144 output.reserve(uncompressed_size);
145 size_t compressed_offset = 0;
146 for (const auto& block : block_info) {
147 std::string_view cluster =
148 blob.substr(compressed_offset, block.compressed_length);
149 if (!block.IsCompressed()) {
150 CHECK_NE(cluster.size(), 0UL);
151 output.insert(output.end(), cluster.begin(), cluster.end());
152 compressed_offset += cluster.size();
153 continue;
154 }
155 size_t inputmargin = 0;
156 if (zero_padding_enabled) {
157 while (cluster[inputmargin] == 0 &&
158 inputmargin < std::min(kBlockSize, cluster.size())) {
159 inputmargin++;
160 }
161 }
162 output.resize(output.size() + block.uncompressed_length);
163
164 const auto bytes_decompressed = LZ4_decompress_safe_partial(
165 cluster.data() + inputmargin,
166 reinterpret_cast<char*>(output.data()) + output.size() -
167 block.uncompressed_length,
168 cluster.size() - inputmargin,
169 block.uncompressed_length,
170 block.uncompressed_length);
171 if (bytes_decompressed < 0) {
172 Blob cluster_hash;
173 HashCalculator::RawHashOfBytes(
174 cluster.data(), cluster.size(), &cluster_hash);
175 Blob blob_hash;
176 HashCalculator::RawHashOfBytes(blob.data(), blob.size(), &blob_hash);
177 LOG(FATAL) << "Failed to decompress, " << bytes_decompressed
178 << ", output_cursor = "
179 << output.size() - block.uncompressed_length
180 << ", input_cursor = " << compressed_offset
181 << ", blob.size() = " << blob.size()
182 << ", cluster_size = " << block.compressed_length
183 << ", dest capacity = " << block.uncompressed_length
184 << ", input margin = " << inputmargin << " "
185 << HexEncode(cluster_hash) << " " << HexEncode(blob_hash);
186 return {};
187 }
188 compressed_offset += block.compressed_length;
189 CHECK_EQ(static_cast<uint64_t>(bytes_decompressed),
190 block.uncompressed_length);
191 }
192 CHECK_EQ(output.size(), uncompressed_size);
193
194 // Trailing data not recorded by compressed block info will be treated as
195 // uncompressed, most of the time these are xattrs or trailing zeros.
196 CHECK_EQ(blob.size(), compressed_offset)
197 << " Unexpected data the end of compressed data ";
198 if (compressed_offset < blob.size()) {
199 output.insert(output.end(), blob.begin() + compressed_offset, blob.end());
200 }
201
202 return output;
203}
204
205[[nodiscard]] std::string_view ToStringView(const Blob& blob) noexcept {
206 return std::string_view{reinterpret_cast<const char*>(blob.data()),
207 blob.size()};
208}
209
210Blob TryDecompressBlob(const Blob& blob,
211 const std::vector<CompressedBlock>& block_info,
212 const bool zero_padding_enabled) {
213 return TryDecompressBlob(
214 ToStringView(blob), block_info, zero_padding_enabled);
215}
216
217std::ostream& operator<<(std::ostream& out, const CompressedBlock& block) {
218 out << "CompressedBlock{.uncompressed_offset = " << block.uncompressed_offset
219 << ", .compressed_length = " << block.compressed_length
220 << ", .uncompressed_length = " << block.uncompressed_length << "}";
221 return out;
222}
223
224[[nodiscard]] std::string_view ToStringView(const void* data,
225 size_t size) noexcept {
226 return std::string_view(reinterpret_cast<const char*>(data), size);
227}
228
229std::ostream& operator<<(std::ostream& out, const CompressedBlockInfo& info) {
230 out << "BlockInfo { compressed_length: " << info.compressed_length()
231 << ", uncompressed_length: " << info.uncompressed_length()
232 << ", uncompressed_offset: " << info.uncompressed_offset();
233 if (!info.sha256_hash().empty()) {
234 out << ", sha256_hash: " << HexEncode(info.sha256_hash());
235 }
236 if (!info.postfix_bspatch().empty()) {
237 out << ", postfix_bspatch: " << info.postfix_bspatch().size();
238 }
239 out << "}";
240 return out;
241}
242
243} // namespace chromeos_update_engine