keramics_compression/
adc.rs

1/* Copyright 2024-2025 Joachim Metz <joachim.metz@gmail.com>
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may
5 * obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software
8 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 * License for the specific language governing permissions and limitations
11 * under the License.
12 */
13
14//! ADC decompression.
15//!
16//! Provides decompression support for ADC compressed data.
17
18use keramics_core::ErrorTrace;
19use keramics_core::mediator::{Mediator, MediatorReference};
20use keramics_types::bytes_to_u16_be;
21
22/// Context for decompressing ADC compressed data.
23pub struct AdcContext {
24    /// Mediator.
25    mediator: MediatorReference,
26
27    /// Uncompressed data size.
28    pub uncompressed_data_size: usize,
29}
30
31impl AdcContext {
32    /// Creates a new context.
33    pub fn new() -> Self {
34        Self {
35            mediator: Mediator::current(),
36            uncompressed_data_size: 0,
37        }
38    }
39
40    /// Decompress data.
41    pub fn decompress(
42        &mut self,
43        compressed_data: &[u8],
44        uncompressed_data: &mut [u8],
45    ) -> Result<(), ErrorTrace> {
46        let mut compressed_data_offset: usize = 0;
47        let compressed_data_size: usize = compressed_data.len();
48
49        let mut uncompressed_data_offset: usize = 0;
50        let uncompressed_data_size: usize = uncompressed_data.len();
51
52        if self.mediator.debug_output {
53            self.mediator.debug_print(format!("AdcContext {{\n",));
54        }
55        while compressed_data_offset < compressed_data_size {
56            if uncompressed_data_offset >= uncompressed_data_size {
57                break;
58            }
59            if compressed_data_offset >= compressed_data_size {
60                return Err(keramics_core::error_trace_new!(
61                    "Invalid compressed data value too small"
62                ));
63            }
64            let oppcode: u8 = compressed_data[compressed_data_offset];
65            compressed_data_offset += 1;
66
67            if self.mediator.debug_output {
68                self.mediator
69                    .debug_print(format!("    oppcode: {}\n", oppcode));
70            }
71            if (oppcode & 0x80) != 0 {
72                let literal_size: u8 = (oppcode & 0x7f) + 1;
73
74                if literal_size as usize > compressed_data_size - compressed_data_offset {
75                    return Err(keramics_core::error_trace_new!(
76                        "Literal size value exceeds compressed data size"
77                    ));
78                }
79                if literal_size as usize > uncompressed_data_size - uncompressed_data_offset {
80                    return Err(keramics_core::error_trace_new!(
81                        "Literal size value exceeds uncompressed data size"
82                    ));
83                }
84                let compressed_data_end_offset: usize =
85                    compressed_data_offset + literal_size as usize;
86                let uncompressed_data_end_offset: usize =
87                    uncompressed_data_offset + literal_size as usize;
88
89                if self.mediator.debug_output {
90                    self.mediator.debug_print(format!("    literal data:\n"));
91                    self.mediator.debug_print_data(
92                        &compressed_data[compressed_data_offset..compressed_data_end_offset],
93                        true,
94                    );
95                }
96                uncompressed_data[uncompressed_data_offset..uncompressed_data_end_offset]
97                    .copy_from_slice(
98                        &compressed_data[compressed_data_offset..compressed_data_end_offset],
99                    );
100
101                compressed_data_offset = compressed_data_end_offset;
102                uncompressed_data_offset = uncompressed_data_end_offset;
103            } else {
104                let match_size: u8;
105                let distance: u16;
106
107                if (oppcode & 0x40) != 0 {
108                    if 2 > compressed_data_size - compressed_data_offset {
109                        return Err(keramics_core::error_trace_new!(
110                            "Invalid compressed data value too small"
111                        ));
112                    }
113                    match_size = (oppcode & 0x3f) + 4;
114                    distance = bytes_to_u16_be!(compressed_data, compressed_data_offset);
115
116                    compressed_data_offset += 2;
117                } else {
118                    if compressed_data_offset >= compressed_data_size {
119                        return Err(keramics_core::error_trace_new!(
120                            "Invalid compressed data value too small"
121                        ));
122                    }
123                    match_size = ((oppcode & 0x3f) >> 2) + 3;
124                    distance = ((oppcode as u16 & 0x03) << 8)
125                        | compressed_data[compressed_data_offset] as u16;
126
127                    compressed_data_offset += 1;
128                }
129                if uncompressed_data_offset < 1 {
130                    return Err(keramics_core::error_trace_new!(
131                        "Invalid uncompressed data offset value out of bounds"
132                    ));
133                }
134                if distance as usize >= uncompressed_data_offset {
135                    return Err(keramics_core::error_trace_new!(
136                        "Invalid distance value exceeds uncompressed data offset"
137                    ));
138                }
139                if match_size as usize > uncompressed_data_size - uncompressed_data_offset {
140                    return Err(keramics_core::error_trace_new!(
141                        "Invalid match size value exceeds uncompressed data size"
142                    ));
143                }
144                let match_offset: usize = uncompressed_data_offset - distance as usize - 1;
145                let mut match_end_offset: usize = match_offset;
146
147                for _ in 0..match_size {
148                    uncompressed_data[uncompressed_data_offset] =
149                        uncompressed_data[match_end_offset];
150
151                    match_end_offset += 1;
152                    uncompressed_data_offset += 1;
153                }
154                if self.mediator.debug_output {
155                    self.mediator
156                        .debug_print(format!("    match offset: {}\n", match_offset));
157                    self.mediator.debug_print(format!("    match data:\n"));
158                    self.mediator
159                        .debug_print_data(&uncompressed_data[match_offset..match_end_offset], true);
160                }
161            }
162        }
163        if self.mediator.debug_output {
164            self.mediator.debug_print(format!("}}\n\n",));
165        }
166        self.uncompressed_data_size = uncompressed_data_offset;
167
168        Ok(())
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    #[test]
177    fn test_decompress() -> Result<(), ErrorTrace> {
178        let test_data: [u8; 10] = [0x83, 0xfe, 0xed, 0xfa, 0xce, 0x00, 0x00, 0x40, 0x00, 0x06];
179        let mut test_context: AdcContext = AdcContext::new();
180
181        let mut uncompressed_data: Vec<u8> = vec![0; 11];
182        test_context.decompress(&test_data, &mut uncompressed_data)?;
183        assert_eq!(test_context.uncompressed_data_size, 11);
184
185        let expected_data: [u8; 11] = [
186            0xfe, 0xed, 0xfa, 0xce, 0xce, 0xce, 0xce, 0xfe, 0xed, 0xfa, 0xce,
187        ];
188        assert_eq!(uncompressed_data, expected_data);
189
190        Ok(())
191    }
192}