xref: /aosp_15_r20/system/update_engine/lz4diff/lz4diff_compress.cc (revision 5a9231315b4521097b8dc3750bc806fcafe0c72f)
1*5a923131SAndroid Build Coastguard Worker //
2*5a923131SAndroid Build Coastguard Worker // Copyright (C) 2021 The Android Open Source Project
3*5a923131SAndroid Build Coastguard Worker //
4*5a923131SAndroid Build Coastguard Worker // Licensed under the Apache License, Version 2.0 (the "License");
5*5a923131SAndroid Build Coastguard Worker // you may not use this file except in compliance with the License.
6*5a923131SAndroid Build Coastguard Worker // You may obtain a copy of the License at
7*5a923131SAndroid Build Coastguard Worker //
8*5a923131SAndroid Build Coastguard Worker //      http://www.apache.org/licenses/LICENSE-2.0
9*5a923131SAndroid Build Coastguard Worker //
10*5a923131SAndroid Build Coastguard Worker // Unless required by applicable law or agreed to in writing, software
11*5a923131SAndroid Build Coastguard Worker // distributed under the License is distributed on an "AS IS" BASIS,
12*5a923131SAndroid Build Coastguard Worker // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*5a923131SAndroid Build Coastguard Worker // See the License for the specific language governing permissions and
14*5a923131SAndroid Build Coastguard Worker // limitations under the License.
15*5a923131SAndroid Build Coastguard Worker //
16*5a923131SAndroid Build Coastguard Worker 
17*5a923131SAndroid Build Coastguard Worker #include "lz4diff_compress.h"
18*5a923131SAndroid Build Coastguard Worker 
19*5a923131SAndroid Build Coastguard Worker #include "update_engine/common/utils.h"
20*5a923131SAndroid Build Coastguard Worker #include "update_engine/common/hash_calculator.h"
21*5a923131SAndroid Build Coastguard Worker #include "update_engine/payload_generator/delta_diff_generator.h"
22*5a923131SAndroid Build Coastguard Worker #include "update_engine/payload_generator/payload_generation_config.h"
23*5a923131SAndroid Build Coastguard Worker 
24*5a923131SAndroid Build Coastguard Worker #include <base/logging.h>
25*5a923131SAndroid Build Coastguard Worker #include <lz4.h>
26*5a923131SAndroid Build Coastguard Worker #include <lz4hc.h>
27*5a923131SAndroid Build Coastguard Worker 
28*5a923131SAndroid Build Coastguard Worker namespace chromeos_update_engine {
29*5a923131SAndroid Build Coastguard Worker 
TryCompressBlob(std::string_view blob,const std::vector<CompressedBlock> & block_info,const bool zero_padding_enabled,const CompressionAlgorithm compression_algo,const SinkFunc & sink)30*5a923131SAndroid Build Coastguard Worker bool TryCompressBlob(std::string_view blob,
31*5a923131SAndroid Build Coastguard Worker                      const std::vector<CompressedBlock>& block_info,
32*5a923131SAndroid Build Coastguard Worker                      const bool zero_padding_enabled,
33*5a923131SAndroid Build Coastguard Worker                      const CompressionAlgorithm compression_algo,
34*5a923131SAndroid Build Coastguard Worker                      const SinkFunc& sink) {
35*5a923131SAndroid Build Coastguard Worker   size_t uncompressed_size = 0;
36*5a923131SAndroid Build Coastguard Worker   for (const auto& block : block_info) {
37*5a923131SAndroid Build Coastguard Worker     CHECK_EQ(uncompressed_size, block.uncompressed_offset)
38*5a923131SAndroid Build Coastguard Worker         << "Compressed block info is expected to be sorted.";
39*5a923131SAndroid Build Coastguard Worker     uncompressed_size += block.uncompressed_length;
40*5a923131SAndroid Build Coastguard Worker   }
41*5a923131SAndroid Build Coastguard Worker   auto hc = LZ4_createStreamHC();
42*5a923131SAndroid Build Coastguard Worker   DEFER {
43*5a923131SAndroid Build Coastguard Worker     if (hc) {
44*5a923131SAndroid Build Coastguard Worker       LZ4_freeStreamHC(hc);
45*5a923131SAndroid Build Coastguard Worker       hc = nullptr;
46*5a923131SAndroid Build Coastguard Worker     }
47*5a923131SAndroid Build Coastguard Worker   };
48*5a923131SAndroid Build Coastguard Worker   size_t compressed_offset = 0;
49*5a923131SAndroid Build Coastguard Worker   Blob block_buffer;
50*5a923131SAndroid Build Coastguard Worker   for (const auto& block : block_info) {
51*5a923131SAndroid Build Coastguard Worker     const auto uncompressed_block =
52*5a923131SAndroid Build Coastguard Worker         blob.substr(block.uncompressed_offset, block.uncompressed_length);
53*5a923131SAndroid Build Coastguard Worker     if (!block.IsCompressed()) {
54*5a923131SAndroid Build Coastguard Worker       TEST_EQ(sink(reinterpret_cast<const uint8_t*>(uncompressed_block.data()),
55*5a923131SAndroid Build Coastguard Worker                    uncompressed_block.size()),
56*5a923131SAndroid Build Coastguard Worker               uncompressed_block.size());
57*5a923131SAndroid Build Coastguard Worker       continue;
58*5a923131SAndroid Build Coastguard Worker     }
59*5a923131SAndroid Build Coastguard Worker     block_buffer.resize(block.compressed_length);
60*5a923131SAndroid Build Coastguard Worker     // Execute the increment at end of each loop
61*5a923131SAndroid Build Coastguard Worker     DEFER {
62*5a923131SAndroid Build Coastguard Worker       compressed_offset += block.compressed_length;
63*5a923131SAndroid Build Coastguard Worker       block_buffer.clear();
64*5a923131SAndroid Build Coastguard Worker     };
65*5a923131SAndroid Build Coastguard Worker 
66*5a923131SAndroid Build Coastguard Worker     int ret = 0;
67*5a923131SAndroid Build Coastguard Worker     // LZ4 spec enforces that last op of a compressed block must be an insert op
68*5a923131SAndroid Build Coastguard Worker     // of at least 5 bytes. Compressors will try to conform to that requirement
69*5a923131SAndroid Build Coastguard Worker     // if the input size is just right. We don't want that. So always give a
70*5a923131SAndroid Build Coastguard Worker     // little bit more data.
71*5a923131SAndroid Build Coastguard Worker     switch (int src_size = uncompressed_size - block.uncompressed_offset;
72*5a923131SAndroid Build Coastguard Worker             compression_algo.type()) {
73*5a923131SAndroid Build Coastguard Worker       case CompressionAlgorithm::LZ4HC:
74*5a923131SAndroid Build Coastguard Worker         ret = LZ4_compress_HC_destSize(
75*5a923131SAndroid Build Coastguard Worker             hc,
76*5a923131SAndroid Build Coastguard Worker             uncompressed_block.data(),
77*5a923131SAndroid Build Coastguard Worker             reinterpret_cast<char*>(block_buffer.data()),
78*5a923131SAndroid Build Coastguard Worker             &src_size,
79*5a923131SAndroid Build Coastguard Worker             block.compressed_length,
80*5a923131SAndroid Build Coastguard Worker             compression_algo.level());
81*5a923131SAndroid Build Coastguard Worker         break;
82*5a923131SAndroid Build Coastguard Worker       case CompressionAlgorithm::LZ4:
83*5a923131SAndroid Build Coastguard Worker         ret =
84*5a923131SAndroid Build Coastguard Worker             LZ4_compress_destSize(uncompressed_block.data(),
85*5a923131SAndroid Build Coastguard Worker                                   reinterpret_cast<char*>(block_buffer.data()),
86*5a923131SAndroid Build Coastguard Worker                                   &src_size,
87*5a923131SAndroid Build Coastguard Worker                                   block.compressed_length);
88*5a923131SAndroid Build Coastguard Worker         break;
89*5a923131SAndroid Build Coastguard Worker       default:
90*5a923131SAndroid Build Coastguard Worker         LOG(ERROR) << "Unrecognized compression algorithm: "
91*5a923131SAndroid Build Coastguard Worker                    << compression_algo.type();
92*5a923131SAndroid Build Coastguard Worker         return {};
93*5a923131SAndroid Build Coastguard Worker     }
94*5a923131SAndroid Build Coastguard Worker     TEST_GT(ret, 0);
95*5a923131SAndroid Build Coastguard Worker     const uint64_t bytes_written = ret;
96*5a923131SAndroid Build Coastguard Worker     // Last block may have trailing zeros
97*5a923131SAndroid Build Coastguard Worker     TEST_LE(bytes_written, block.compressed_length);
98*5a923131SAndroid Build Coastguard Worker     if (bytes_written < block.compressed_length) {
99*5a923131SAndroid Build Coastguard Worker       if (zero_padding_enabled) {
100*5a923131SAndroid Build Coastguard Worker         const auto padding = block.compressed_length - bytes_written;
101*5a923131SAndroid Build Coastguard Worker         std::memmove(
102*5a923131SAndroid Build Coastguard Worker             block_buffer.data() + padding, block_buffer.data(), bytes_written);
103*5a923131SAndroid Build Coastguard Worker         std::fill(block_buffer.data(), block_buffer.data() + padding, 0);
104*5a923131SAndroid Build Coastguard Worker 
105*5a923131SAndroid Build Coastguard Worker       } else {
106*5a923131SAndroid Build Coastguard Worker         std::fill(block_buffer.data() + bytes_written,
107*5a923131SAndroid Build Coastguard Worker                   block_buffer.data() + block.compressed_length,
108*5a923131SAndroid Build Coastguard Worker                   0);
109*5a923131SAndroid Build Coastguard Worker       }
110*5a923131SAndroid Build Coastguard Worker     }
111*5a923131SAndroid Build Coastguard Worker     TEST_EQ(sink(block_buffer.data(), block_buffer.size()),
112*5a923131SAndroid Build Coastguard Worker             block_buffer.size());
113*5a923131SAndroid Build Coastguard Worker   }
114*5a923131SAndroid Build Coastguard Worker   // Any trailing data will be copied to the output buffer.
115*5a923131SAndroid Build Coastguard Worker   TEST_EQ(
116*5a923131SAndroid Build Coastguard Worker       sink(reinterpret_cast<const uint8_t*>(blob.data()) + uncompressed_size,
117*5a923131SAndroid Build Coastguard Worker            blob.size() - uncompressed_size),
118*5a923131SAndroid Build Coastguard Worker       blob.size() - uncompressed_size);
119*5a923131SAndroid Build Coastguard Worker   return true;
120*5a923131SAndroid Build Coastguard Worker }
121*5a923131SAndroid Build Coastguard Worker 
TryCompressBlob(std::string_view blob,const std::vector<CompressedBlock> & block_info,const bool zero_padding_enabled,const CompressionAlgorithm compression_algo)122*5a923131SAndroid Build Coastguard Worker Blob TryCompressBlob(std::string_view blob,
123*5a923131SAndroid Build Coastguard Worker                      const std::vector<CompressedBlock>& block_info,
124*5a923131SAndroid Build Coastguard Worker                      const bool zero_padding_enabled,
125*5a923131SAndroid Build Coastguard Worker                      const CompressionAlgorithm compression_algo) {
126*5a923131SAndroid Build Coastguard Worker   size_t uncompressed_size = 0;
127*5a923131SAndroid Build Coastguard Worker   size_t compressed_size = 0;
128*5a923131SAndroid Build Coastguard Worker   for (const auto& block : block_info) {
129*5a923131SAndroid Build Coastguard Worker     CHECK_EQ(uncompressed_size, block.uncompressed_offset)
130*5a923131SAndroid Build Coastguard Worker         << "Compressed block info is expected to be sorted.";
131*5a923131SAndroid Build Coastguard Worker     uncompressed_size += block.uncompressed_length;
132*5a923131SAndroid Build Coastguard Worker     compressed_size += block.compressed_length;
133*5a923131SAndroid Build Coastguard Worker   }
134*5a923131SAndroid Build Coastguard Worker   TEST_EQ(uncompressed_size, blob.size());
135*5a923131SAndroid Build Coastguard Worker   Blob output;
136*5a923131SAndroid Build Coastguard Worker   output.reserve(utils::RoundUp(compressed_size, kBlockSize));
137*5a923131SAndroid Build Coastguard Worker   if (!TryCompressBlob(blob,
138*5a923131SAndroid Build Coastguard Worker                        block_info,
139*5a923131SAndroid Build Coastguard Worker                        zero_padding_enabled,
140*5a923131SAndroid Build Coastguard Worker                        compression_algo,
141*5a923131SAndroid Build Coastguard Worker                        [&output](const uint8_t* data, size_t size) {
142*5a923131SAndroid Build Coastguard Worker                          output.insert(output.end(), data, data + size);
143*5a923131SAndroid Build Coastguard Worker                          return size;
144*5a923131SAndroid Build Coastguard Worker                        })) {
145*5a923131SAndroid Build Coastguard Worker     return {};
146*5a923131SAndroid Build Coastguard Worker   }
147*5a923131SAndroid Build Coastguard Worker 
148*5a923131SAndroid Build Coastguard Worker   return output;
149*5a923131SAndroid Build Coastguard Worker }
150*5a923131SAndroid Build Coastguard Worker 
TryDecompressBlob(std::string_view blob,const std::vector<CompressedBlock> & block_info,const bool zero_padding_enabled)151*5a923131SAndroid Build Coastguard Worker Blob TryDecompressBlob(std::string_view blob,
152*5a923131SAndroid Build Coastguard Worker                        const std::vector<CompressedBlock>& block_info,
153*5a923131SAndroid Build Coastguard Worker                        const bool zero_padding_enabled) {
154*5a923131SAndroid Build Coastguard Worker   if (block_info.empty()) {
155*5a923131SAndroid Build Coastguard Worker     return {};
156*5a923131SAndroid Build Coastguard Worker   }
157*5a923131SAndroid Build Coastguard Worker   size_t uncompressed_size = 0;
158*5a923131SAndroid Build Coastguard Worker   size_t compressed_size = 0;
159*5a923131SAndroid Build Coastguard Worker   for (const auto& block : block_info) {
160*5a923131SAndroid Build Coastguard Worker     CHECK_EQ(uncompressed_size, block.uncompressed_offset)
161*5a923131SAndroid Build Coastguard Worker         << " Compressed block info is expected to be sorted, expected offset "
162*5a923131SAndroid Build Coastguard Worker         << uncompressed_size << ", actual block " << block;
163*5a923131SAndroid Build Coastguard Worker     uncompressed_size += block.uncompressed_length;
164*5a923131SAndroid Build Coastguard Worker     compressed_size += block.compressed_length;
165*5a923131SAndroid Build Coastguard Worker   }
166*5a923131SAndroid Build Coastguard Worker   if (blob.size() < compressed_size) {
167*5a923131SAndroid Build Coastguard Worker     LOG(INFO) << "File is chunked. Skip lz4 decompress. Expected size: "
168*5a923131SAndroid Build Coastguard Worker               << compressed_size << ", actual size: " << blob.size();
169*5a923131SAndroid Build Coastguard Worker     return {};
170*5a923131SAndroid Build Coastguard Worker   }
171*5a923131SAndroid Build Coastguard Worker   Blob output;
172*5a923131SAndroid Build Coastguard Worker   output.reserve(uncompressed_size);
173*5a923131SAndroid Build Coastguard Worker   size_t compressed_offset = 0;
174*5a923131SAndroid Build Coastguard Worker   for (const auto& block : block_info) {
175*5a923131SAndroid Build Coastguard Worker     std::string_view cluster =
176*5a923131SAndroid Build Coastguard Worker         blob.substr(compressed_offset, block.compressed_length);
177*5a923131SAndroid Build Coastguard Worker     if (!block.IsCompressed()) {
178*5a923131SAndroid Build Coastguard Worker       CHECK_NE(cluster.size(), 0UL);
179*5a923131SAndroid Build Coastguard Worker       output.insert(output.end(), cluster.begin(), cluster.end());
180*5a923131SAndroid Build Coastguard Worker       compressed_offset += cluster.size();
181*5a923131SAndroid Build Coastguard Worker       continue;
182*5a923131SAndroid Build Coastguard Worker     }
183*5a923131SAndroid Build Coastguard Worker     size_t inputmargin = 0;
184*5a923131SAndroid Build Coastguard Worker     if (zero_padding_enabled) {
185*5a923131SAndroid Build Coastguard Worker       while (inputmargin < std::min(kBlockSize, cluster.size()) &&
186*5a923131SAndroid Build Coastguard Worker              cluster[inputmargin] == 0) {
187*5a923131SAndroid Build Coastguard Worker         inputmargin++;
188*5a923131SAndroid Build Coastguard Worker       }
189*5a923131SAndroid Build Coastguard Worker     }
190*5a923131SAndroid Build Coastguard Worker     output.resize(output.size() + block.uncompressed_length);
191*5a923131SAndroid Build Coastguard Worker 
192*5a923131SAndroid Build Coastguard Worker     const auto bytes_decompressed = LZ4_decompress_safe_partial(
193*5a923131SAndroid Build Coastguard Worker         cluster.data() + inputmargin,
194*5a923131SAndroid Build Coastguard Worker         reinterpret_cast<char*>(output.data()) + output.size() -
195*5a923131SAndroid Build Coastguard Worker             block.uncompressed_length,
196*5a923131SAndroid Build Coastguard Worker         cluster.size() - inputmargin,
197*5a923131SAndroid Build Coastguard Worker         block.uncompressed_length,
198*5a923131SAndroid Build Coastguard Worker         block.uncompressed_length);
199*5a923131SAndroid Build Coastguard Worker     if (bytes_decompressed < 0) {
200*5a923131SAndroid Build Coastguard Worker       LOG(FATAL) << "Failed to decompress, " << bytes_decompressed
201*5a923131SAndroid Build Coastguard Worker                  << ", output_cursor = "
202*5a923131SAndroid Build Coastguard Worker                  << output.size() - block.uncompressed_length
203*5a923131SAndroid Build Coastguard Worker                  << ", input_cursor = " << compressed_offset
204*5a923131SAndroid Build Coastguard Worker                  << ", blob.size() = " << blob.size()
205*5a923131SAndroid Build Coastguard Worker                  << ", cluster_size = " << block.compressed_length
206*5a923131SAndroid Build Coastguard Worker                  << ", dest capacity = " << block.uncompressed_length
207*5a923131SAndroid Build Coastguard Worker                  << ", input margin = " << inputmargin << " "
208*5a923131SAndroid Build Coastguard Worker                  << HashCalculator::SHA256Digest(cluster) << " "
209*5a923131SAndroid Build Coastguard Worker                  << HashCalculator::SHA256Digest(blob);
210*5a923131SAndroid Build Coastguard Worker       return {};
211*5a923131SAndroid Build Coastguard Worker     }
212*5a923131SAndroid Build Coastguard Worker     compressed_offset += block.compressed_length;
213*5a923131SAndroid Build Coastguard Worker     CHECK_EQ(static_cast<uint64_t>(bytes_decompressed),
214*5a923131SAndroid Build Coastguard Worker              block.uncompressed_length);
215*5a923131SAndroid Build Coastguard Worker   }
216*5a923131SAndroid Build Coastguard Worker   CHECK_EQ(output.size(), uncompressed_size);
217*5a923131SAndroid Build Coastguard Worker 
218*5a923131SAndroid Build Coastguard Worker   // Trailing data not recorded by compressed block info will be treated as
219*5a923131SAndroid Build Coastguard Worker   // uncompressed, most of the time these are xattrs or trailing zeros.
220*5a923131SAndroid Build Coastguard Worker   CHECK_EQ(blob.size(), compressed_offset)
221*5a923131SAndroid Build Coastguard Worker       << " Unexpected data the end of compressed data ";
222*5a923131SAndroid Build Coastguard Worker   if (compressed_offset < blob.size()) {
223*5a923131SAndroid Build Coastguard Worker     output.insert(output.end(), blob.begin() + compressed_offset, blob.end());
224*5a923131SAndroid Build Coastguard Worker   }
225*5a923131SAndroid Build Coastguard Worker 
226*5a923131SAndroid Build Coastguard Worker   return output;
227*5a923131SAndroid Build Coastguard Worker }
228*5a923131SAndroid Build Coastguard Worker 
TryDecompressBlob(const Blob & blob,const std::vector<CompressedBlock> & block_info,const bool zero_padding_enabled)229*5a923131SAndroid Build Coastguard Worker Blob TryDecompressBlob(const Blob& blob,
230*5a923131SAndroid Build Coastguard Worker                        const std::vector<CompressedBlock>& block_info,
231*5a923131SAndroid Build Coastguard Worker                        const bool zero_padding_enabled) {
232*5a923131SAndroid Build Coastguard Worker   return TryDecompressBlob(
233*5a923131SAndroid Build Coastguard Worker       ToStringView(blob), block_info, zero_padding_enabled);
234*5a923131SAndroid Build Coastguard Worker }
235*5a923131SAndroid Build Coastguard Worker 
operator <<(std::ostream & out,const CompressedBlock & block)236*5a923131SAndroid Build Coastguard Worker std::ostream& operator<<(std::ostream& out, const CompressedBlock& block) {
237*5a923131SAndroid Build Coastguard Worker   out << "CompressedBlock{.uncompressed_offset = " << block.uncompressed_offset
238*5a923131SAndroid Build Coastguard Worker       << ", .compressed_length = " << block.compressed_length
239*5a923131SAndroid Build Coastguard Worker       << ", .uncompressed_length = " << block.uncompressed_length << "}";
240*5a923131SAndroid Build Coastguard Worker   return out;
241*5a923131SAndroid Build Coastguard Worker }
242*5a923131SAndroid Build Coastguard Worker 
operator <<(std::ostream & out,const CompressedBlockInfo & info)243*5a923131SAndroid Build Coastguard Worker std::ostream& operator<<(std::ostream& out, const CompressedBlockInfo& info) {
244*5a923131SAndroid Build Coastguard Worker   out << "BlockInfo { compressed_length: " << info.compressed_length()
245*5a923131SAndroid Build Coastguard Worker       << ", uncompressed_length: " << info.uncompressed_length()
246*5a923131SAndroid Build Coastguard Worker       << ", uncompressed_offset: " << info.uncompressed_offset();
247*5a923131SAndroid Build Coastguard Worker   if (!info.sha256_hash().empty()) {
248*5a923131SAndroid Build Coastguard Worker     out << ", sha256_hash: " << HexEncode(info.sha256_hash());
249*5a923131SAndroid Build Coastguard Worker   }
250*5a923131SAndroid Build Coastguard Worker   if (!info.postfix_bspatch().empty()) {
251*5a923131SAndroid Build Coastguard Worker     out << ", postfix_bspatch: " << info.postfix_bspatch().size();
252*5a923131SAndroid Build Coastguard Worker   }
253*5a923131SAndroid Build Coastguard Worker   out << "}";
254*5a923131SAndroid Build Coastguard Worker   return out;
255*5a923131SAndroid Build Coastguard Worker }
256*5a923131SAndroid Build Coastguard Worker 
257*5a923131SAndroid Build Coastguard Worker }  // namespace chromeos_update_engine
258