casc_lib/blte/
compression.rs1use super::encryption::{TactKeyStore, decrypt_block as decrypt_encrypted_block};
13use crate::error::{CascError, Result};
14
15pub fn decode_block_with_keys(block: &[u8], keystore: Option<&TactKeyStore>) -> Result<Vec<u8>> {
22 if block.is_empty() {
23 return Ok(Vec::new());
24 }
25 match block[0] {
26 b'N' => decode_raw(&block[1..]),
27 b'Z' => decode_zlib(&block[1..]),
28 b'4' => decode_lz4(&block[1..]),
29 b'E' => decode_encrypted(&block[1..], keystore),
30 b'F' => Err(CascError::InvalidFormat(
31 "recursive BLTE (mode F) not supported".into(),
32 )),
33 mode => Err(CascError::InvalidFormat(format!(
34 "unknown BLTE mode: 0x{:02X}",
35 mode
36 ))),
37 }
38}
39
40pub fn decode_block(block: &[u8]) -> Result<Vec<u8>> {
45 decode_block_with_keys(block, None)
46}
47
48fn decode_encrypted(data: &[u8], keystore: Option<&TactKeyStore>) -> Result<Vec<u8>> {
49 let keystore = keystore.ok_or_else(|| {
50 CascError::EncryptionKeyMissing("no keystore provided for encrypted block".into())
51 })?;
52 let decrypted = decrypt_encrypted_block(data, keystore)?;
54 decode_block_with_keys(&decrypted, Some(keystore))
56}
57
58fn decode_raw(data: &[u8]) -> Result<Vec<u8>> {
59 Ok(data.to_vec())
60}
61
62fn decode_zlib(data: &[u8]) -> Result<Vec<u8>> {
63 use flate2::read::ZlibDecoder;
64 use std::io::Read;
65
66 let mut decoder = ZlibDecoder::new(data);
67 let mut output = Vec::new();
68 decoder
69 .read_to_end(&mut output)
70 .map_err(|e| CascError::DecompressionFailed(format!("zlib: {}", e)))?;
71 Ok(output)
72}
73
74fn decode_lz4(data: &[u8]) -> Result<Vec<u8>> {
75 let mut output = Vec::new();
76 let mut offset = 0;
77
78 while offset < data.len() {
79 if offset + 8 > data.len() {
81 return Err(CascError::InvalidFormat(
82 "LZ4: truncated sub-block header".into(),
83 ));
84 }
85
86 let decompressed_size =
87 u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize;
88 offset += 4;
89
90 let compressed_size =
91 u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize;
92 offset += 4;
93
94 if compressed_size >= decompressed_size {
95 if offset + decompressed_size > data.len() {
97 return Err(CascError::InvalidFormat(
98 "LZ4: truncated uncompressed sub-block payload".into(),
99 ));
100 }
101 output.extend_from_slice(&data[offset..offset + decompressed_size]);
102 offset += decompressed_size;
103 } else {
104 if offset + compressed_size > data.len() {
106 return Err(CascError::InvalidFormat(
107 "LZ4: truncated compressed sub-block payload".into(),
108 ));
109 }
110 let decompressed =
111 lz4_flex::decompress(&data[offset..offset + compressed_size], decompressed_size)
112 .map_err(|e| CascError::DecompressionFailed(format!("LZ4: {}", e)))?;
113 output.extend_from_slice(&decompressed);
114 offset += compressed_size;
115 }
116 }
117
118 Ok(output)
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use flate2::Compression;
125 use flate2::write::ZlibEncoder;
126 use std::io::Write;
127
128 fn zlib_compress(data: &[u8]) -> Vec<u8> {
129 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
130 encoder.write_all(data).unwrap();
131 encoder.finish().unwrap()
132 }
133
134 #[test]
135 fn mode_n_passthrough() {
136 let mut block = vec![b'N'];
137 block.extend_from_slice(b"hello world");
138 let result = decode_block(&block).unwrap();
139 assert_eq!(result, b"hello world");
140 }
141
142 #[test]
143 fn mode_n_empty_payload() {
144 let block = vec![b'N'];
145 let result = decode_block(&block).unwrap();
146 assert!(result.is_empty());
147 }
148
149 #[test]
150 fn mode_z_decompresses() {
151 let original = b"hello world compressed with zlib!";
152 let compressed = zlib_compress(original);
153 let mut block = vec![b'Z'];
154 block.extend_from_slice(&compressed);
155 let result = decode_block(&block).unwrap();
156 assert_eq!(result, original);
157 }
158
159 #[test]
160 fn mode_z_large_data() {
161 let original: Vec<u8> = (0..10000).map(|i| (i % 256) as u8).collect();
162 let compressed = zlib_compress(&original);
163 let mut block = vec![b'Z'];
164 block.extend_from_slice(&compressed);
165 let result = decode_block(&block).unwrap();
166 assert_eq!(result, original);
167 }
168
169 #[test]
170 fn mode_z_invalid_data() {
171 let block = vec![b'Z', 0xFF, 0xFE, 0xFD];
172 assert!(decode_block(&block).is_err());
173 }
174
175 #[test]
176 fn mode_e_without_keystore_errors() {
177 let block = vec![b'E', 0x00];
178 assert!(decode_block(&block).is_err());
179 assert!(decode_block_with_keys(&block, None).is_err());
180 }
181
182 #[test]
183 fn mode_e_with_empty_keystore_errors() {
184 use crate::blte::encryption::TactKeyStore;
185 let mut block = vec![b'E'];
187 block.push(1u8); block.push(8u8); block.extend_from_slice(&0xDEADu64.to_le_bytes());
190 block.extend_from_slice(&4u32.to_le_bytes()); block.extend_from_slice(&[0; 4]); block.push(b'S'); block.extend_from_slice(b"fake_encrypted_data");
194
195 let ks = TactKeyStore::new();
196 let result = decode_block_with_keys(&block, Some(&ks));
197 assert!(result.is_err());
198 }
199
200 #[test]
201 fn mode_e_decrypt_and_decompress_raw() {
202 use crate::blte::encryption::TactKeyStore;
203
204 let key_name: u64 = 0xFA505078126ACB3E;
205 let ks = TactKeyStore::with_known_keys();
206 let key = ks.get(key_name).unwrap();
207
208 let plaintext = b"Nhello";
210 let iv_bytes = [0x10, 0x20, 0x30, 0x40];
211
212 let mut encrypted_payload = plaintext.to_vec();
214 {
215 use salsa20::Salsa20;
216 use salsa20::cipher::{KeyIvInit, StreamCipher};
217 let mut full_key = [0u8; 32];
218 full_key[..16].copy_from_slice(key);
219 full_key[16..].copy_from_slice(key);
220 let mut nonce = [0u8; 8];
221 nonce[..4].copy_from_slice(&iv_bytes);
222 let mut cipher = Salsa20::new(&full_key.into(), &nonce.into());
223 cipher.apply_keystream(&mut encrypted_payload);
224 }
225
226 let mut block = vec![b'E'];
228 block.push(1u8);
229 block.push(8u8);
230 block.extend_from_slice(&key_name.to_le_bytes());
231 block.extend_from_slice(&4u32.to_le_bytes());
232 block.extend_from_slice(&iv_bytes);
233 block.push(b'S');
234 block.extend_from_slice(&encrypted_payload);
235
236 let result = decode_block_with_keys(&block, Some(&ks)).unwrap();
237 assert_eq!(result, b"hello");
238 }
239
240 #[test]
241 fn mode_unknown_returns_error() {
242 let block = vec![b'X', 0x00];
243 let err = decode_block(&block).unwrap_err();
244 assert!(err.to_string().contains("unknown BLTE mode"));
245 }
246
247 #[test]
248 fn empty_block() {
249 let result = decode_block(&[]).unwrap();
250 assert!(result.is_empty());
251 }
252
253 #[test]
254 fn mode_4_single_subblock_compressed() {
255 let original: Vec<u8> = b"AAAA".repeat(256);
257 let compressed = lz4_flex::compress(&original);
258 assert!(
259 compressed.len() < original.len(),
260 "test data must actually compress smaller"
261 );
262
263 let decompressed_size = original.len() as u32;
264 let compressed_size = compressed.len() as u32;
265
266 let mut block = vec![b'4'];
267 block.extend_from_slice(&decompressed_size.to_le_bytes());
268 block.extend_from_slice(&compressed_size.to_le_bytes());
269 block.extend_from_slice(&compressed);
270
271 let result = decode_block(&block).unwrap();
272 assert_eq!(result, original);
273 }
274
275 #[test]
276 fn mode_4_single_subblock_uncompressed() {
277 let original = b"raw data";
279 let decompressed_size = original.len() as u32;
280 let compressed_size = decompressed_size; let mut block = vec![b'4'];
283 block.extend_from_slice(&decompressed_size.to_le_bytes());
284 block.extend_from_slice(&compressed_size.to_le_bytes());
285 block.extend_from_slice(original);
286
287 let result = decode_block(&block).unwrap();
288 assert_eq!(result, original);
289 }
290
291 #[test]
292 fn mode_4_multiple_subblocks() {
293 let part1: Vec<u8> = b"BBBB".repeat(200);
295 let part2: Vec<u8> = b"CCCC".repeat(300);
296 let compressed1 = lz4_flex::compress(&part1);
297 let compressed2 = lz4_flex::compress(&part2);
298 assert!(compressed1.len() < part1.len());
299 assert!(compressed2.len() < part2.len());
300
301 let mut block = vec![b'4'];
302 block.extend_from_slice(&(part1.len() as u32).to_le_bytes());
304 block.extend_from_slice(&(compressed1.len() as u32).to_le_bytes());
305 block.extend_from_slice(&compressed1);
306 block.extend_from_slice(&(part2.len() as u32).to_le_bytes());
308 block.extend_from_slice(&(compressed2.len() as u32).to_le_bytes());
309 block.extend_from_slice(&compressed2);
310
311 let result = decode_block(&block).unwrap();
312 let expected: Vec<u8> = [part1.as_slice(), part2.as_slice()].concat();
313 assert_eq!(result, expected);
314 }
315
316 #[test]
317 fn mode_4_empty_returns_empty() {
318 let block = vec![b'4'];
319 let result = decode_block(&block).unwrap();
320 assert!(result.is_empty());
321 }
322
323 #[test]
324 fn mode_4_truncated_header_errors() {
325 let block = vec![b'4', 0x10, 0x00, 0x00, 0x00];
327 assert!(decode_block(&block).is_err());
328 }
329}