blob: d3f2e11b74dcd82b67388545eea67a37a5c62ccd [file] [log] [blame]
Brian Silvermanf59fe3f2020-09-22 21:04:09 -07001#include "aos/events/logging/lzma_encoder.h"
2
3#include "glog/logging.h"
4
5namespace aos::logger {
6namespace {
7
Austin Schuh3bd4c402020-11-06 18:19:06 -08008// Returns true if `status` is not an error code, false if it is recoverable, or
9// otherwise logs the appropriate error message and crashes.
10bool LzmaCodeIsOk(lzma_ret status) {
Brian Silvermanf59fe3f2020-09-22 21:04:09 -070011 switch (status) {
12 case LZMA_OK:
13 case LZMA_STREAM_END:
Austin Schuh3bd4c402020-11-06 18:19:06 -080014 return true;
Brian Silvermanf59fe3f2020-09-22 21:04:09 -070015 case LZMA_MEM_ERROR:
16 LOG(FATAL) << "Memory allocation failed:" << status;
17 case LZMA_OPTIONS_ERROR:
18 LOG(FATAL) << "The given compression preset or decompression options are "
19 "not supported: "
20 << status;
21 case LZMA_UNSUPPORTED_CHECK:
22 LOG(FATAL) << "The given check type is not supported: " << status;
23 case LZMA_PROG_ERROR:
24 LOG(FATAL) << "One or more of the parameters have values that will never "
25 "be valid: "
26 << status;
27 case LZMA_MEMLIMIT_ERROR:
28 LOG(FATAL) << "Decoder needs more memory than allowed by the specified "
29 "memory usage limit: "
30 << status;
31 case LZMA_FORMAT_ERROR:
32 LOG(FATAL) << "File format not recognized: " << status;
33 case LZMA_DATA_ERROR:
Austin Schuh3bd4c402020-11-06 18:19:06 -080034 LOG(WARNING) << "Compressed file is corrupt: " << status;
35 return false;
Brian Silvermanf59fe3f2020-09-22 21:04:09 -070036 case LZMA_BUF_ERROR:
Austin Schuh3bd4c402020-11-06 18:19:06 -080037 LOG(WARNING) << "Compressed file is truncated or corrupt: " << status;
38 return false;
Brian Silvermanf59fe3f2020-09-22 21:04:09 -070039 default:
40 LOG(FATAL) << "Unexpected return value: " << status;
41 }
42}
43
44} // namespace
45
46LzmaEncoder::LzmaEncoder(const uint32_t compression_preset)
47 : stream_(LZMA_STREAM_INIT), compression_preset_(compression_preset) {
48 CHECK_GE(compression_preset_, 0u)
49 << ": Compression preset must be in the range [0, 9].";
50 CHECK_LE(compression_preset_, 9u)
51 << ": Compression preset must be in the range [0, 9].";
52
53 lzma_ret status =
54 lzma_easy_encoder(&stream_, compression_preset_, LZMA_CHECK_CRC64);
Austin Schuh3bd4c402020-11-06 18:19:06 -080055 CHECK(LzmaCodeIsOk(status));
Brian Silvermanf59fe3f2020-09-22 21:04:09 -070056 stream_.avail_out = 0;
57 VLOG(2) << "LzmaEncoder: Initialization succeeded.";
58}
59
60LzmaEncoder::~LzmaEncoder() { lzma_end(&stream_); }
61
62void LzmaEncoder::Encode(flatbuffers::DetachedBuffer &&in) {
63 CHECK(in.data()) << ": Encode called with nullptr.";
64
65 stream_.next_in = in.data();
66 stream_.avail_in = in.size();
67
68 RunLzmaCode(LZMA_RUN);
69}
70
71void LzmaEncoder::Finish() { RunLzmaCode(LZMA_FINISH); }
72
73void LzmaEncoder::Clear(const int n) {
74 CHECK_GE(n, 0);
75 CHECK_LE(static_cast<size_t>(n), queue_size());
76 queue_.erase(queue_.begin(), queue_.begin() + n);
77 if (queue_.empty()) {
78 stream_.next_out = nullptr;
79 stream_.avail_out = 0;
80 }
81}
82
83std::vector<absl::Span<const uint8_t>> LzmaEncoder::queue() const {
84 std::vector<absl::Span<const uint8_t>> queue;
85 if (queue_.empty()) {
86 return queue;
87 }
88 for (size_t i = 0; i < queue_.size() - 1; ++i) {
89 queue.emplace_back(
90 absl::MakeConstSpan(queue_.at(i).data(), queue_.at(i).size()));
91 }
92 // For the last buffer in the queue, we must account for the possibility that
93 // the buffer isn't full yet.
94 queue.emplace_back(absl::MakeConstSpan(
95 queue_.back().data(), queue_.back().size() - stream_.avail_out));
96 return queue;
97}
98
99size_t LzmaEncoder::queued_bytes() const {
100 size_t bytes = queue_size() * kEncodedBufferSizeBytes;
101 // Subtract the bytes that the encoder hasn't filled yet.
102 bytes -= stream_.avail_out;
103 return bytes;
104}
105
106void LzmaEncoder::RunLzmaCode(lzma_action action) {
107 CHECK(!finished_);
108
109 // This is to keep track of how many bytes resulted from encoding this input
110 // buffer.
111 size_t last_avail_out = stream_.avail_out;
112
113 while (stream_.avail_in > 0 || action == LZMA_FINISH) {
114 // If output buffer is full, create a new one, queue it up, and resume
115 // encoding. This could happen in the first call to Encode after
116 // construction or a Reset, or when an input buffer is large enough to fill
117 // more than one output buffer.
118 if (stream_.avail_out == 0) {
119 queue_.emplace_back();
120 queue_.back().resize(kEncodedBufferSizeBytes);
121 stream_.next_out = queue_.back().data();
122 stream_.avail_out = kEncodedBufferSizeBytes;
123 // Update the byte count.
124 total_bytes_ += last_avail_out;
125 last_avail_out = stream_.avail_out;
126 }
127
128 // Encode the data.
129 lzma_ret status = lzma_code(&stream_, action);
Austin Schuh3bd4c402020-11-06 18:19:06 -0800130 CHECK(LzmaCodeIsOk(status));
Brian Silvermanf59fe3f2020-09-22 21:04:09 -0700131 if (action == LZMA_FINISH) {
132 if (status == LZMA_STREAM_END) {
133 // This is returned when lzma_code is all done.
134 finished_ = true;
135 break;
136 }
137 } else {
138 CHECK(status != LZMA_STREAM_END);
139 }
140 VLOG(2) << "LzmaEncoder: Encoded chunk.";
141 }
142
143 // Update the number of resulting encoded bytes.
144 total_bytes_ += last_avail_out - stream_.avail_out;
145}
146
147LzmaDecoder::LzmaDecoder(std::string_view filename)
Austin Schuh3bd4c402020-11-06 18:19:06 -0800148 : dummy_decoder_(filename), stream_(LZMA_STREAM_INIT), filename_(filename) {
Brian Silvermanf59fe3f2020-09-22 21:04:09 -0700149 compressed_data_.resize(kBufSize);
150
151 lzma_ret status =
152 lzma_stream_decoder(&stream_, UINT64_MAX, LZMA_CONCATENATED);
Austin Schuh3bd4c402020-11-06 18:19:06 -0800153 CHECK(LzmaCodeIsOk(status)) << "Failed initializing LZMA stream decoder.";
Brian Silvermanf59fe3f2020-09-22 21:04:09 -0700154 stream_.avail_out = 0;
155 VLOG(2) << "LzmaDecoder: Initialization succeeded.";
156}
157
158LzmaDecoder::~LzmaDecoder() { lzma_end(&stream_); }
159
160size_t LzmaDecoder::Read(uint8_t *begin, uint8_t *end) {
161 if (finished_) {
162 return 0;
163 }
164
165 // Write into the given range.
166 stream_.next_out = begin;
167 stream_.avail_out = end - begin;
168 // Keep decompressing until we run out of buffer space.
169 while (stream_.avail_out > 0) {
170 if (action_ == LZMA_RUN && stream_.avail_in == 0) {
171 // Read more bytes from the file if we're all out.
172 const size_t count =
173 dummy_decoder_.Read(compressed_data_.begin(), compressed_data_.end());
174 if (count == 0) {
175 // No more data to read in the file, begin the finishing operation.
176 action_ = LZMA_FINISH;
177 } else {
178 stream_.next_in = compressed_data_.data();
179 stream_.avail_in = count;
180 }
181 }
182 // Decompress the data.
183 const lzma_ret status = lzma_code(&stream_, action_);
184 // Return if we're done.
185 if (status == LZMA_STREAM_END) {
186 CHECK_EQ(action_, LZMA_FINISH)
187 << ": Got LZMA_STREAM_END when action wasn't LZMA_FINISH";
188 finished_ = true;
189 return (end - begin) - stream_.avail_out;
190 }
Austin Schuh3bd4c402020-11-06 18:19:06 -0800191
192 // If we fail to decompress, give up. Return everything that has been
193 // produced so far.
194 if (!LzmaCodeIsOk(status)) {
195 finished_ = true;
196 LOG(WARNING) << filename_ << " is truncated or corrupted.";
197 return (end - begin) - stream_.avail_out;
198 }
Brian Silvermanf59fe3f2020-09-22 21:04:09 -0700199 }
200 return end - begin;
201}
202
203} // namespace aos::logger