CCSDS 124.0-B-1 C++ 1.0.0
CCSDS 124.0-B-1 Lossless Compression
Loading...
Searching...
No Matches
compressor.hpp
Go to the documentation of this file.
1
27#ifndef CCSDS124_COMPRESSOR_HPP
28#define CCSDS124_COMPRESSOR_HPP
29
30#include "bitbuffer.hpp"
31#include "bitvector.hpp"
32#include "config.hpp"
33#include "encoder.hpp"
34#include "error.hpp"
35#include "mask.hpp"
36
37namespace ccsds124 {
38
43 std::uint8_t min_robustness = 0;
44 bool new_mask_flag = false;
45 bool send_mask_flag = false;
46 bool uncompressed_flag = false;
47};
48
58template <std::size_t N, std::size_t MaxOutputBytes = N * 6> class Compressor {
59public:
68 explicit Compressor(std::uint8_t robustness = 0, int pt_limit = 0, int ft_limit = 0,
69 int rt_limit = 0) noexcept
71 pt_limit_(pt_limit),
72 ft_limit_(ft_limit),
73 rt_limit_(rt_limit),
74 pt_counter_(pt_limit),
75 ft_counter_(ft_limit),
76 rt_counter_(rt_limit) {
77 reset();
78 }
79
85 void set_initial_mask(const BitVector<N>& initial_mask) noexcept {
86 initial_mask_ = initial_mask;
87 mask_ = initial_mask;
88 }
89
96 void reset() noexcept {
97 t_ = 0;
98 history_index_ = 0;
99 flag_history_index_ = 0;
100
101 // Reset mask to initial
102 mask_ = initial_mask_;
103 prev_mask_.zero();
104 build_.zero();
105 prev_input_.zero();
106
107 // Clear change history and weight cache
108 for (std::size_t i = 0; i < MAX_HISTORY; ++i) {
109 change_history_[i].zero();
110 change_weight_history_[i] = 0;
111 }
112
113 // Clear flag history
114 for (std::size_t i = 0; i < MAX_VT_HISTORY; ++i) {
115 new_mask_flag_history_[i] = 0;
116 }
117
118 // Reset countdown counters
119 pt_counter_ = pt_limit_;
120 ft_counter_ = ft_limit_;
121 rt_counter_ = rt_limit_;
122 }
123
133 template <std::size_t OutputSize>
135 const CompressParams* params = nullptr) noexcept {
136 // Get parameters (use defaults if nullptr)
137 CompressParams effective_params;
138 if (params != nullptr) {
139 effective_params = *params;
140 } else {
141 effective_params.min_robustness = robustness_;
142 }
143
144 // Clear output buffer
145 output.clear();
146
147 // ====================================================================
148 // STEP 1: Update Mask and Build Vectors (CCSDS Section 4)
149 // ====================================================================
150
151 // Save previous mask
152 prev_mask_ = mask_;
153
154 // Update build and mask vectors (only when t > 0)
155 if (t_ > 0) {
156 // Save previous build (only needed when updating)
157 work_prev_build_ = build_;
158
159 // Update build vector (Equation 6)
160 update_build(build_, input, prev_input_, effective_params.new_mask_flag, t_);
161
162 // Update mask vector (Equation 7)
163 update_mask(mask_, input, prev_input_, work_prev_build_,
164 effective_params.new_mask_flag);
165 }
166
167 // Compute change vector (Equation 8)
168 compute_change(work_change_, mask_, prev_mask_, t_);
169
170 // Store change in history (circular buffer) with cached weight
171 change_history_[history_index_] = work_change_;
172 change_weight_history_[history_index_] = work_change_.hamming_weight();
173
174 // ====================================================================
175 // STEP 2: Encode Output Packet (CCSDS Section 5.3)
176 // o_t = h_t || q_t || u_t
177 // ====================================================================
178
179 // Calculate X_t (robustness window)
180 compute_robustness_window(work_Xt_, work_change_);
181
182 // Calculate V_t (effective robustness)
183 std::uint8_t Vt = compute_effective_robustness();
184
185 // Calculate c_t flag (cached - used twice below)
186 bool ct = compute_ct_flag(Vt, effective_params.new_mask_flag);
187
188 // Calculate d_t flag
189 bool dt = !effective_params.send_mask_flag && !effective_params.uncompressed_flag;
190
191 // ====================================================================
192 // Component h_t: Mask change information
193 // h_t = RLE(X_t) || BIT_4(V_t) || e_t || k_t || c_t || d_t
194 // ====================================================================
195
196 // 1. RLE(X_t) - Run-length encode the robustness window
197 auto rle_result = rle_encode(output, work_Xt_);
198 if (rle_result != Error::Ok)
199 return rle_result;
200
201 // 2. BIT_4(V_t) - 4-bit effective robustness level (optimized: single append)
202 auto vt_result = output.append_value(Vt & 0x0FU, 4);
203 if (vt_result != Error::Ok)
204 return vt_result;
205
206 // Cache hamming weight (avoid recomputing)
207 std::size_t xt_weight = work_Xt_.hamming_weight();
208
209 // 3. e_t, k_t, c_t - Only if V_t > 0 and there are mask changes
210 if (Vt > 0 && xt_weight > 0) {
211 // Calculate e_t
212 bool et = has_positive_updates(work_Xt_, mask_);
213
214 auto result = output.append_bit(et ? 1 : 0);
215 if (result != Error::Ok)
216 return result;
217
218 if (et) {
219 // k_t - Output '1' for positive updates (mask=0), '0' for negative (mask=1)
220 // Extract INVERTED mask values
221 work_inverted_.not_of(mask_);
222
223 auto kt_result = bit_extract_forward(output, work_inverted_, work_Xt_);
224 if (kt_result != Error::Ok)
225 return kt_result;
226
227 // Encode c_t (already computed above)
228 result = output.append_bit(ct ? 1 : 0);
229 if (result != Error::Ok)
230 return result;
231 }
232 }
233
234 // 4. d_t - Flag indicating if both f_t and r_t are zero
235 auto dt_result = output.append_bit(dt ? 1 : 0);
236 if (dt_result != Error::Ok)
237 return dt_result;
238
239 // ====================================================================
240 // Component q_t: Optional full mask
241 // q_t = empty if d_t=1, '1' || RLE(<(M_t XOR (M_t<<))>) if f_t=1, '0' otherwise
242 // ====================================================================
243
244 if (!dt) { // Only if d_t = 0
245 if (effective_params.send_mask_flag) {
246 auto result = output.append_bit(1); // Flag: mask follows
247 if (result != Error::Ok)
248 return result;
249
250 // Encode mask as RLE(M XOR (M<<))
251 work_shifted_.left_shift(mask_);
252 work_diff_.xor_with(mask_, work_shifted_);
253
254 auto rle_mask_result = rle_encode(output, work_diff_);
255 if (rle_mask_result != Error::Ok)
256 return rle_mask_result;
257 } else {
258 auto result = output.append_bit(0); // Flag: no mask
259 if (result != Error::Ok)
260 return result;
261 }
262 }
263
264 // ====================================================================
265 // Component u_t: Unpredictable bits or full input
266 // ====================================================================
267
268 if (effective_params.uncompressed_flag) {
269 // '1' || COUNT(F) || I_t
270 auto result = output.append_bit(1); // Flag: full input follows
271 if (result != Error::Ok)
272 return result;
273
274 auto count_result = count_encode(output, static_cast<std::uint32_t>(N));
275 if (count_result != Error::Ok)
276 return count_result;
277
278 auto append_result = output.append_bitvector(input);
279 if (append_result != Error::Ok)
280 return append_result;
281 } else {
282 if (!dt) {
283 // '0' || BE(...)
284 auto result = output.append_bit(0); // Flag: compressed
285 if (result != Error::Ok)
286 return result;
287 }
288
289 // Use extraction mask based on c_t (already computed above)
290 if (ct && Vt > 0) {
291 // BE(I_t, (X_t OR M_t)) - extract bits where mask OR changes are set
292 work_diff_.or_with(mask_, work_Xt_); // M_t OR X_t
293 auto be_result = bit_extract(output, input, work_diff_);
294 if (be_result != Error::Ok)
295 return be_result;
296 } else {
297 // BE(I_t, M_t) - extract only unpredictable bits
298 auto be_result = bit_extract(output, input, mask_);
299 if (be_result != Error::Ok)
300 return be_result;
301 }
302 }
303
304 // ====================================================================
305 // STEP 3: Update State for Next Cycle
306 // ====================================================================
307
308 // Save current input for next iteration
309 prev_input_ = input;
310
311 // Track new_mask_flag for c_t calculation
312 new_mask_flag_history_[flag_history_index_] = effective_params.new_mask_flag ? 1 : 0;
313 flag_history_index_ = (flag_history_index_ + 1) & (MAX_VT_HISTORY - 1);
314
315 // Advance time
316 ++t_;
317
318 // Advance history index (circular buffer)
319 history_index_ = (history_index_ + 1) & (MAX_HISTORY - 1);
320
321 return Error::Ok;
322 }
323
327 std::size_t time_index() const noexcept {
328 return t_;
329 }
330
334 std::uint8_t robustness() const noexcept {
335 return robustness_;
336 }
337
341 const BitVector<N>& mask() const noexcept {
342 return mask_;
343 }
344
351 CompressParams compute_auto_params(std::size_t packet_index) noexcept {
352 CompressParams params;
353 params.min_robustness = robustness_;
354
355 if (pt_limit_ > 0 && ft_limit_ > 0 && rt_limit_ > 0) {
356 if (packet_index == 0) {
357 // First packet: fixed init values
358 params.send_mask_flag = true;
359 params.uncompressed_flag = true;
360 params.new_mask_flag = false;
361 } else {
362 // Packets 1+: check and update countdown counters
363 // ft counter
364 if (ft_counter_ == 1) {
365 params.send_mask_flag = true;
366 ft_counter_ = ft_limit_;
367 } else {
368 --ft_counter_;
369 params.send_mask_flag = false;
370 }
371
372 // pt counter
373 if (pt_counter_ == 1) {
374 params.new_mask_flag = true;
375 pt_counter_ = pt_limit_;
376 } else {
377 --pt_counter_;
378 params.new_mask_flag = false;
379 }
380
381 // rt counter
382 if (rt_counter_ == 1) {
383 params.uncompressed_flag = true;
384 rt_counter_ = rt_limit_;
385 } else {
386 --rt_counter_;
387 params.uncompressed_flag = false;
388 }
389
390 // Override for remaining init packets: CCSDS requires first R_t+1 packets
391 // to have f_t=1, r_t=1, p_t=0
392 if (packet_index <= static_cast<std::size_t>(robustness_)) {
393 params.send_mask_flag = true;
394 params.uncompressed_flag = true;
395 params.new_mask_flag = false;
396 }
397 }
398 }
399
400 return params;
401 }
402
403private:
409 void compute_robustness_window(BitVector<N>& Xt, const BitVector<N>& current_change) noexcept {
410 if (robustness_ == 0 || t_ == 0) {
411 // X_t = D_t
412 Xt = current_change;
413 } else {
414 // Start with current change
415 Xt = current_change;
416
417 // Determine how many historical changes to include
418 std::size_t num_changes = (t_ < robustness_) ? t_ : robustness_;
419 constexpr std::size_t history_mask = MAX_HISTORY - 1; // For bitwise AND
420
421 // OR with historical changes (going backwards from current)
422 for (std::size_t i = 1; i <= num_changes; ++i) {
423 std::size_t hist_idx = (history_index_ + MAX_HISTORY - i) & history_mask;
424 Xt.or_in_place(change_history_[hist_idx]);
425 }
426 }
427 }
428
434 std::uint8_t compute_effective_robustness() const noexcept {
435 // Early exit for common case: V_t = R_t when t <= R_t
436 if (t_ <= robustness_) [[likely]] {
437 return robustness_;
438 }
439
440 // For t > R_t, compute C_t (consecutive iterations without changes)
441 std::uint8_t Vt = robustness_;
442 constexpr std::uint8_t max_Vt = 15;
443 constexpr std::size_t history_mask = MAX_HISTORY - 1; // For bitwise AND (16 -> 15)
444 const std::size_t max_check = (t_ < 15) ? t_ : 15;
445
446 for (std::size_t i = robustness_ + 1; i <= max_check; ++i) {
447 std::size_t hist_idx = (history_index_ + MAX_HISTORY - i) & history_mask;
448 // Use cached weight instead of computing hamming_weight()
449 if (change_weight_history_[hist_idx] > 0) {
450 break; // Found a change, stop counting
451 }
452 ++Vt;
453 if (Vt >= max_Vt) {
454 break; // Cap at 15
455 }
456 }
457
458 return Vt;
459 }
460
466 static bool has_positive_updates(const BitVector<N>& Xt, const BitVector<N>& mask) noexcept {
467 // e_t = 1 if (Xt AND (NOT mask)) != 0
468 for (std::size_t w = 0; w < BitVector<N>::NUM_WORDS; ++w) {
469 if ((Xt.data()[w] & ~mask.data()[w]) != 0) {
470 return true;
471 }
472 }
473 return false;
474 }
475
481 bool compute_ct_flag(std::uint8_t Vt, bool current_new_mask_flag) const noexcept {
482 if (Vt == 0) {
483 return false;
484 }
485
486 int count = 0;
487
488 // Include current packet's flag
489 if (current_new_mask_flag) {
490 ++count;
491 }
492
493 // Check history for V_t previous entries
494 std::size_t iterations_to_check = (Vt <= t_) ? Vt : t_;
495 constexpr std::size_t vt_history_mask = MAX_VT_HISTORY - 1; // For bitwise AND
496
497 for (std::size_t i = 0; i < iterations_to_check; ++i) {
498 std::size_t hist_idx = (flag_history_index_ + MAX_VT_HISTORY - 1 - i) & vt_history_mask;
499 if (new_mask_flag_history_[hist_idx] != 0) {
500 ++count;
501 }
502 }
503
504 return count >= 2;
505 }
506
507 // Configuration (immutable after construction)
508 std::uint8_t robustness_;
509 int pt_limit_;
510 int ft_limit_;
511 int rt_limit_;
512
513 // State (updated each cycle)
514 BitVector<N> initial_mask_;
515 BitVector<N> mask_;
516 BitVector<N> prev_mask_;
517 BitVector<N> build_;
518 BitVector<N> prev_input_;
519 BitVector<N> change_history_[MAX_HISTORY];
520 std::size_t history_index_ = 0;
521
522 // History for c_t calculation
523 std::uint8_t new_mask_flag_history_[MAX_VT_HISTORY] = {0};
524 std::size_t flag_history_index_ = 0;
525
526 // Cached hamming weights for change history (avoids recomputing in
527 // compute_effective_robustness)
528 std::size_t change_weight_history_[MAX_HISTORY] = {0};
529
530 // Cycle counter
531 std::size_t t_ = 0;
532
533 // Parameter management (automatic mode)
534 int pt_counter_;
535 int ft_counter_;
536 int rt_counter_;
537
538 // Pre-allocated work buffers
539 BitVector<N> work_prev_build_;
540 BitVector<N> work_change_;
541 BitVector<N> work_Xt_;
542 BitVector<N> work_inverted_;
543 BitVector<N> work_shifted_;
544 BitVector<N> work_diff_;
545};
546
547} // namespace ccsds124
548
549#endif // CCSDS124_COMPRESSOR_HPP
Variable-length bit buffer for building compressed output.
Fixed-length bit vector with static allocation.
Variable-length bit buffer with static allocation.
Definition bitbuffer.hpp:51
void clear() noexcept
Clear buffer to empty state.
Definition bitbuffer.hpp:61
Error append_bit(int bit) noexcept
Append a single bit.
Definition bitbuffer.hpp:82
Error append_bitvector(const BitVector< N > &bv, std::size_t num_bits) noexcept
Append bits from a BitVector.
Error append_value(std::uint32_t value, std::size_t num_bits) noexcept
Append multiple bits from a value.
Fixed-length bit vector with compile-time size.
Definition bitvector.hpp:90
CCSDS 124.0-B-1 compressor with static memory allocation.
std::size_t time_index() const noexcept
Get current time index.
std::uint8_t robustness() const noexcept
Get robustness level.
const BitVector< N > & mask() const noexcept
Get current mask.
CompressParams compute_auto_params(std::size_t packet_index) noexcept
Compute parameters for automatic mode.
void reset() noexcept
Reset compressor to initial state.
Error compress_packet(const BitVector< N > &input, BitBuffer< OutputSize > &output, const CompressParams *params=nullptr) noexcept
Compress a single input packet.
Compressor(std::uint8_t robustness=0, int pt_limit=0, int ft_limit=0, int rt_limit=0) noexcept
Construct compressor with configuration.
void set_initial_mask(const BitVector< N > &initial_mask) noexcept
Set the initial mask.
CCSDS 124.0-B-1 compile-time configuration.
CCSDS 124.0-B-1 encoding functions (COUNT, RLE, BE).
CCSDS 124.0-B-1 error handling.
constexpr std::size_t MAX_ROBUSTNESS
Definition config.hpp:53
constexpr std::size_t MAX_VT_HISTORY
Definition config.hpp:55
constexpr std::size_t MAX_HISTORY
Definition config.hpp:54
CCSDS 124.0-B-1 mask update logic.
Error count_encode(BitBuffer< MaxBytes > &output, std::uint32_t A) noexcept
Counter encoding (CCSDS Section 5.2.2, Equation 9).
Definition encoder.hpp:71
Error bit_extract(BitBuffer< MaxBytes > &output, const BitVector< N > &data, const BitVector< N > &mask) noexcept
Bit extraction in reverse order (CCSDS Section 5.2.4, Equation 11).
Definition encoder.hpp:167
Error
Error codes for error-code-based error handling.
Definition error.hpp:29
void compute_change(BitVector< N > &change, const BitVector< N > &mask, const BitVector< N > &prev_mask, std::size_t t) noexcept
Compute change vector (CCSDS Equation 8).
Definition mask.hpp:115
Error bit_extract_forward(BitBuffer< MaxBytes > &output, const BitVector< N > &data, const BitVector< N > &mask) noexcept
Bit extraction in forward order.
Definition encoder.hpp:234
void update_mask(BitVector< N > &mask, const BitVector< N > &input, const BitVector< N > &prev_input, const BitVector< N > &build_prev, bool new_mask_flag) noexcept
Update mask vector (CCSDS Equation 7).
Definition mask.hpp:84
Error rle_encode(BitBuffer< MaxBytes > &output, const BitVector< N > &input) noexcept
Run-length encoding (CCSDS Section 5.2.3, Equation 10).
Definition encoder.hpp:114
void update_build(BitVector< N > &build, const BitVector< N > &input, const BitVector< N > &prev_input, bool new_mask_flag, std::size_t t) noexcept
Update build vector (CCSDS Equation 6).
Definition mask.hpp:53
Compression parameters for a single packet.
bool new_mask_flag
p_t: Update mask from build vector
bool uncompressed_flag
r_t: Send uncompressed
bool send_mask_flag
f_t: Include mask in output
std::uint8_t min_robustness
R_t: Minimum robustness level (0-7)