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