blob: a8cafdad56a1ff73359bf611be2b83cc99726af5 [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
8// Returns if `status` is not an error code, otherwise logs the appropriate
9// error message and crashes.
10void CheckLzmaCodeIsOk(lzma_ret status) {
11 switch (status) {
12 case LZMA_OK:
13 case LZMA_STREAM_END:
14 return;
15 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:
34 LOG(FATAL) << "Compressed file is corrupt: " << status;
35 case LZMA_BUF_ERROR:
36 LOG(FATAL) << "Compressed file is truncated or corrupt: " << status;
37 default:
38 LOG(FATAL) << "Unexpected return value: " << status;
39 }
40}
41
42} // namespace
43
44LzmaEncoder::LzmaEncoder(const uint32_t compression_preset)
45 : stream_(LZMA_STREAM_INIT), compression_preset_(compression_preset) {
46 CHECK_GE(compression_preset_, 0u)
47 << ": Compression preset must be in the range [0, 9].";
48 CHECK_LE(compression_preset_, 9u)
49 << ": Compression preset must be in the range [0, 9].";
50
51 lzma_ret status =
52 lzma_easy_encoder(&stream_, compression_preset_, LZMA_CHECK_CRC64);
53 CheckLzmaCodeIsOk(status);
54 stream_.avail_out = 0;
55 VLOG(2) << "LzmaEncoder: Initialization succeeded.";
56}
57
58LzmaEncoder::~LzmaEncoder() { lzma_end(&stream_); }
59
60void LzmaEncoder::Encode(flatbuffers::DetachedBuffer &&in) {
61 CHECK(in.data()) << ": Encode called with nullptr.";
62
63 stream_.next_in = in.data();
64 stream_.avail_in = in.size();
65
66 RunLzmaCode(LZMA_RUN);
67}
68
69void LzmaEncoder::Finish() { RunLzmaCode(LZMA_FINISH); }
70
71void LzmaEncoder::Clear(const int n) {
72 CHECK_GE(n, 0);
73 CHECK_LE(static_cast<size_t>(n), queue_size());
74 queue_.erase(queue_.begin(), queue_.begin() + n);
75 if (queue_.empty()) {
76 stream_.next_out = nullptr;
77 stream_.avail_out = 0;
78 }
79}
80
81std::vector<absl::Span<const uint8_t>> LzmaEncoder::queue() const {
82 std::vector<absl::Span<const uint8_t>> queue;
83 if (queue_.empty()) {
84 return queue;
85 }
86 for (size_t i = 0; i < queue_.size() - 1; ++i) {
87 queue.emplace_back(
88 absl::MakeConstSpan(queue_.at(i).data(), queue_.at(i).size()));
89 }
90 // For the last buffer in the queue, we must account for the possibility that
91 // the buffer isn't full yet.
92 queue.emplace_back(absl::MakeConstSpan(
93 queue_.back().data(), queue_.back().size() - stream_.avail_out));
94 return queue;
95}
96
97size_t LzmaEncoder::queued_bytes() const {
98 size_t bytes = queue_size() * kEncodedBufferSizeBytes;
99 // Subtract the bytes that the encoder hasn't filled yet.
100 bytes -= stream_.avail_out;
101 return bytes;
102}
103
104void LzmaEncoder::RunLzmaCode(lzma_action action) {
105 CHECK(!finished_);
106
107 // This is to keep track of how many bytes resulted from encoding this input
108 // buffer.
109 size_t last_avail_out = stream_.avail_out;
110
111 while (stream_.avail_in > 0 || action == LZMA_FINISH) {
112 // If output buffer is full, create a new one, queue it up, and resume
113 // encoding. This could happen in the first call to Encode after
114 // construction or a Reset, or when an input buffer is large enough to fill
115 // more than one output buffer.
116 if (stream_.avail_out == 0) {
117 queue_.emplace_back();
118 queue_.back().resize(kEncodedBufferSizeBytes);
119 stream_.next_out = queue_.back().data();
120 stream_.avail_out = kEncodedBufferSizeBytes;
121 // Update the byte count.
122 total_bytes_ += last_avail_out;
123 last_avail_out = stream_.avail_out;
124 }
125
126 // Encode the data.
127 lzma_ret status = lzma_code(&stream_, action);
128 CheckLzmaCodeIsOk(status);
129 if (action == LZMA_FINISH) {
130 if (status == LZMA_STREAM_END) {
131 // This is returned when lzma_code is all done.
132 finished_ = true;
133 break;
134 }
135 } else {
136 CHECK(status != LZMA_STREAM_END);
137 }
138 VLOG(2) << "LzmaEncoder: Encoded chunk.";
139 }
140
141 // Update the number of resulting encoded bytes.
142 total_bytes_ += last_avail_out - stream_.avail_out;
143}
144
145LzmaDecoder::LzmaDecoder(std::string_view filename)
146 : dummy_decoder_(filename), stream_(LZMA_STREAM_INIT) {
147 compressed_data_.resize(kBufSize);
148
149 lzma_ret status =
150 lzma_stream_decoder(&stream_, UINT64_MAX, LZMA_CONCATENATED);
151 CheckLzmaCodeIsOk(status);
152 stream_.avail_out = 0;
153 VLOG(2) << "LzmaDecoder: Initialization succeeded.";
154}
155
156LzmaDecoder::~LzmaDecoder() { lzma_end(&stream_); }
157
158size_t LzmaDecoder::Read(uint8_t *begin, uint8_t *end) {
159 if (finished_) {
160 return 0;
161 }
162
163 // Write into the given range.
164 stream_.next_out = begin;
165 stream_.avail_out = end - begin;
166 // Keep decompressing until we run out of buffer space.
167 while (stream_.avail_out > 0) {
168 if (action_ == LZMA_RUN && stream_.avail_in == 0) {
169 // Read more bytes from the file if we're all out.
170 const size_t count =
171 dummy_decoder_.Read(compressed_data_.begin(), compressed_data_.end());
172 if (count == 0) {
173 // No more data to read in the file, begin the finishing operation.
174 action_ = LZMA_FINISH;
175 } else {
176 stream_.next_in = compressed_data_.data();
177 stream_.avail_in = count;
178 }
179 }
180 // Decompress the data.
181 const lzma_ret status = lzma_code(&stream_, action_);
182 // Return if we're done.
183 if (status == LZMA_STREAM_END) {
184 CHECK_EQ(action_, LZMA_FINISH)
185 << ": Got LZMA_STREAM_END when action wasn't LZMA_FINISH";
186 finished_ = true;
187 return (end - begin) - stream_.avail_out;
188 }
189 CheckLzmaCodeIsOk(status);
190 }
191 return end - begin;
192}
193
194} // namespace aos::logger