1use crate::bounds::{
23 checked_add, checked_capacity, checked_range, decoded_len, encode_input_len, encode_u32_len,
24 u32_to_usize,
25};
26use crate::error::CodecError;
27
28const NUM_STREAMS: usize = 4;
30
31const PROB_BITS: u32 = 14;
33const PROB_SCALE: u32 = 1 << PROB_BITS;
34
35const RANS_L: u32 = 1 << 23;
37const RANS_STATE_UPPER_BOUND: u32 = RANS_L << 8;
38
39const FREQ_TABLE_SIZE: usize = 256 * 4;
41
42const HEADER_SIZE: usize = 4 + FREQ_TABLE_SIZE + 4;
44
45pub fn encode(data: &[u8]) -> Result<Vec<u8>, CodecError> {
51 let encoded_len = encode_input_len(data.len(), "rANS")?;
52 if data.is_empty() {
53 let out = vec![0u8; HEADER_SIZE];
54 return Ok(out);
56 }
57
58 let mut freqs = [0u32; 256];
60 for &b in data {
61 freqs[b as usize] += 1;
62 }
63
64 let norm_freqs = normalize_frequencies(&freqs, data.len());
66
67 let (cum_freqs, sym_freqs) = build_cum_table(&norm_freqs);
69
70 let mut streams: [Vec<u8>; NUM_STREAMS] = std::array::from_fn(|_| Vec::new());
73 let mut states = [RANS_L; NUM_STREAMS];
74
75 for i in (0..data.len()).rev() {
77 let stream_idx = i % NUM_STREAMS;
78 let sym = data[i] as usize;
79 let freq = sym_freqs[sym];
80 let start = cum_freqs[sym];
81
82 if freq == 0 {
83 continue; }
85
86 rans_encode_symbol(
87 &mut states[stream_idx],
88 &mut streams[stream_idx],
89 start,
90 freq,
91 );
92 }
93
94 for i in 0..NUM_STREAMS {
96 let s = states[i];
97 streams[i].push((s & 0xFF) as u8);
98 streams[i].push(((s >> 8) & 0xFF) as u8);
99 streams[i].push(((s >> 16) & 0xFF) as u8);
100 streams[i].push(((s >> 24) & 0xFF) as u8);
101 }
102
103 let total_compressed: usize = streams.iter().map(|s| s.len()).sum();
105 let mut out = Vec::with_capacity(HEADER_SIZE + total_compressed + NUM_STREAMS * 4);
106
107 out.extend_from_slice(&encoded_len.to_le_bytes());
109
110 for &f in &norm_freqs {
112 out.extend_from_slice(&f.to_le_bytes());
113 }
114
115 let comp_payload_size = total_compressed + NUM_STREAMS * 4; out.extend_from_slice(
118 &encode_u32_len(comp_payload_size, "rANS compressed payload")?.to_le_bytes(),
119 );
120
121 for s in &streams {
123 out.extend_from_slice(&encode_u32_len(s.len(), "rANS stream")?.to_le_bytes());
124 }
125
126 for s in &streams {
128 out.extend_from_slice(s);
129 }
130
131 Ok(out)
132}
133
134pub fn decode(data: &[u8]) -> Result<Vec<u8>, CodecError> {
136 let header = checked_range(data, 0, HEADER_SIZE, "rANS header")?;
137 let uncompressed_size = decoded_len(
138 u32_to_usize(
139 u32::from_le_bytes(header[0..4].try_into().map_err(|_| CodecError::Corrupt {
140 detail: "rANS output size".into(),
141 })?),
142 "rANS output",
143 )?,
144 "rANS",
145 )?;
146 let mut norm_freqs = [0u32; 256];
147 for (index, frequency) in norm_freqs.iter_mut().enumerate() {
148 let offset = 4 + index * 4;
149 *frequency = u32::from_le_bytes(header[offset..offset + 4].try_into().map_err(|_| {
150 CodecError::Corrupt {
151 detail: "rANS frequency".into(),
152 }
153 })?);
154 }
155 let compressed_size = u32_to_usize(
156 u32::from_le_bytes(header[HEADER_SIZE - 4..].try_into().map_err(|_| {
157 CodecError::Corrupt {
158 detail: "rANS compressed size".into(),
159 }
160 })?),
161 "rANS compressed size",
162 )?;
163 if uncompressed_size == 0 {
164 if compressed_size != 0 || data.len() != HEADER_SIZE {
165 return Err(CodecError::Corrupt {
166 detail: "nonempty rANS payload for empty output".into(),
167 });
168 }
169 return Ok(Vec::new());
170 }
171 validate_frequencies(&norm_freqs)?;
172 let stream_sizes_len = NUM_STREAMS * 4;
173 let frame = checked_range(
174 data,
175 HEADER_SIZE,
176 compressed_size,
177 "rANS compressed payload",
178 )?;
179 if checked_add(HEADER_SIZE, compressed_size, "rANS frame end")? != data.len() {
180 return Err(CodecError::Corrupt {
181 detail: "trailing bytes after rANS frame".into(),
182 });
183 }
184 if compressed_size < stream_sizes_len {
185 return Err(CodecError::Corrupt {
186 detail: "rANS compressed size omits stream sizes".into(),
187 });
188 }
189 let mut stream_sizes = [0usize; NUM_STREAMS];
190 let mut payload_len = 0usize;
191 for (index, size) in stream_sizes.iter_mut().enumerate() {
192 let start = index * 4;
193 *size = u32_to_usize(
194 u32::from_le_bytes(frame[start..start + 4].try_into().map_err(|_| {
195 CodecError::Corrupt {
196 detail: "rANS stream size".into(),
197 }
198 })?),
199 "rANS stream size",
200 )?;
201 if *size < 4 {
202 return Err(CodecError::Corrupt {
203 detail: format!("rANS stream {index} too short for state"),
204 });
205 }
206 payload_len = checked_add(payload_len, *size, "rANS stream lengths")?;
207 }
208 if checked_add(stream_sizes_len, payload_len, "rANS stream framing")? != compressed_size {
209 return Err(CodecError::Corrupt {
210 detail: "rANS compressed-size metadata mismatch".into(),
211 });
212 }
213 let (cum_freqs, sym_freqs) = build_cum_table(&norm_freqs);
214 let lookup = build_decode_table(&cum_freqs, &sym_freqs);
215 let mut streams: [&[u8]; NUM_STREAMS] = [&[]; NUM_STREAMS];
216 let mut pos = stream_sizes_len;
217 for (index, stream) in streams.iter_mut().enumerate() {
218 *stream = checked_range(frame, pos, stream_sizes[index], "rANS stream range")?;
219 pos = checked_add(pos, stream_sizes[index], "rANS stream offset")?;
220 }
221 let mut states = [0u32; NUM_STREAMS];
222 let mut stream_pos = [0usize; NUM_STREAMS];
223 for index in 0..NUM_STREAMS {
224 let stream = streams[index];
225 let end = stream.len();
226 states[index] = u32::from_le_bytes(stream[end - 4..end].try_into().map_err(|_| {
227 CodecError::Corrupt {
228 detail: "rANS state".into(),
229 }
230 })?);
231 if !(RANS_L..RANS_STATE_UPPER_BOUND).contains(&states[index]) {
232 return Err(CodecError::Corrupt {
233 detail: format!("rANS stream {index} has invalid initial state"),
234 });
235 }
236 stream_pos[index] = end - 4;
237 }
238 let output_capacity = checked_capacity(uncompressed_size, 1, "rANS decoded bytes")?;
239 let mut output = vec![0u8; output_capacity];
240 for (index, out_byte) in output.iter_mut().enumerate() {
241 let stream_index = index % NUM_STREAMS;
242 let (symbol, state) =
243 rans_decode_symbol(states[stream_index], &lookup, &cum_freqs, &sym_freqs);
244 *out_byte = symbol;
245 states[stream_index] =
246 rans_decode_renorm(state, streams[stream_index], &mut stream_pos[stream_index]);
247 }
248 for index in 0..NUM_STREAMS {
249 if stream_pos[index] != 0 || states[index] != RANS_L {
250 return Err(CodecError::Corrupt {
251 detail: format!("rANS stream {index} has noncanonical terminal state"),
252 });
253 }
254 }
255 Ok(output)
256}
257
258fn rans_encode_symbol(state: &mut u32, bitstream: &mut Vec<u8>, start: u32, freq: u32) {
263 let max_state = ((RANS_L >> PROB_BITS) << 8) * freq;
265 while *state >= max_state {
266 bitstream.push((*state & 0xFF) as u8);
267 *state >>= 8;
268 }
269
270 *state = ((*state / freq) << PROB_BITS) + (*state % freq) + start;
272}
273
274fn rans_decode_symbol(
275 state: u32,
276 lookup: &[u8; PROB_SCALE as usize],
277 cum_freqs: &[u32; 257],
278 sym_freqs: &[u32; 256],
279) -> (u8, u32) {
280 let slot = state & (PROB_SCALE - 1);
281 let sym = lookup[slot as usize];
282 let start = cum_freqs[sym as usize];
283 let freq = sym_freqs[sym as usize];
284
285 let new_state = freq * (state >> PROB_BITS) + slot - start;
286 (sym, new_state)
287}
288
289fn rans_decode_renorm(mut state: u32, stream: &[u8], pos: &mut usize) -> u32 {
290 while state < RANS_L && *pos > 0 {
291 *pos -= 1;
292 state = (state << 8) | stream[*pos] as u32;
293 }
294 state
295}
296
297fn validate_frequencies(freqs: &[u32; 256]) -> Result<(), CodecError> {
302 let mut total = 0u32;
303 for &frequency in freqs {
304 total = total
305 .checked_add(frequency)
306 .ok_or_else(|| CodecError::Corrupt {
307 detail: "rANS frequency total overflows".into(),
308 })?;
309 }
310 if total != PROB_SCALE {
311 return Err(CodecError::Corrupt {
312 detail: format!("rANS frequencies sum to {total}, expected {PROB_SCALE}"),
313 });
314 }
315 Ok(())
316}
317
318fn normalize_frequencies(freqs: &[u32; 256], total: usize) -> [u32; 256] {
320 let mut norm = [0u32; 256];
321 let mut sum = 0u32;
322 let total_f64 = total as f64;
323
324 for i in 0..256 {
326 if freqs[i] > 0 {
327 norm[i] = ((freqs[i] as f64 / total_f64 * PROB_SCALE as f64).round() as u32).max(1);
329 sum += norm[i];
330 }
331 }
332
333 if sum > 0 {
335 while sum > PROB_SCALE {
336 let max_idx = norm
338 .iter()
339 .enumerate()
340 .filter(|(_, f)| **f > 1)
341 .max_by_key(|(_, f)| **f)
342 .map(|(i, _)| i)
343 .unwrap_or(0);
344 norm[max_idx] -= 1;
345 sum -= 1;
346 }
347 while sum < PROB_SCALE {
348 let max_idx = norm
349 .iter()
350 .enumerate()
351 .max_by_key(|(_, f)| **f)
352 .map(|(i, _)| i)
353 .unwrap_or(0);
354 norm[max_idx] += 1;
355 sum += 1;
356 }
357 }
358
359 norm
360}
361
362fn build_cum_table(freqs: &[u32; 256]) -> ([u32; 257], [u32; 256]) {
364 let mut cum = [0u32; 257];
365 let sym_freqs = *freqs;
366 for i in 0..256 {
367 cum[i + 1] = cum[i] + freqs[i];
368 }
369 (cum, sym_freqs)
370}
371
372fn build_decode_table(
374 cum_freqs: &[u32; 257],
375 _sym_freqs: &[u32; 256],
376) -> [u8; PROB_SCALE as usize] {
377 let mut table = [0u8; PROB_SCALE as usize];
378 for sym in 0..256u16 {
379 let start = cum_freqs[sym as usize] as usize;
380 let end = cum_freqs[sym as usize + 1] as usize;
381 for entry in table[start..end].iter_mut() {
382 *entry = sym as u8;
383 }
384 }
385 table
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391
392 #[test]
393 fn empty_roundtrip() {
394 let encoded = encode(&[]).expect("encode");
395 let decoded = decode(&encoded).unwrap();
396 assert!(decoded.is_empty());
397 }
398
399 #[test]
400 fn single_byte() {
401 let encoded = encode(&[42]).expect("encode");
402 let decoded = decode(&encoded).unwrap();
403 assert_eq!(decoded, vec![42]);
404 }
405
406 #[test]
407 fn repeated_bytes() {
408 let data = vec![0u8; 10_000];
409 let encoded = encode(&data).expect("encode");
410 let decoded = decode(&encoded).unwrap();
411 assert_eq!(decoded, data);
412
413 let ratio = data.len() as f64 / encoded.len() as f64;
415 assert!(
416 ratio > 2.0,
417 "repeated bytes should compress >2x, got {ratio:.1}x"
418 );
419 }
420
421 #[test]
422 fn text_data() {
423 let text = b"the quick brown fox jumps over the lazy dog. ";
424 let data: Vec<u8> = text.iter().copied().cycle().take(10_000).collect();
425 let encoded = encode(&data).expect("encode");
426 let decoded = decode(&encoded).unwrap();
427 assert_eq!(decoded, data);
428
429 let ratio = data.len() as f64 / encoded.len() as f64;
430 assert!(ratio > 1.5, "text should compress >1.5x, got {ratio:.1}x");
431 }
432
433 #[test]
434 fn uniform_random_data() {
435 let mut data = vec![0u8; 5000];
437 let mut rng: u64 = 12345;
438 for byte in &mut data {
439 rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
440 *byte = (rng >> 33) as u8;
441 }
442 let encoded = encode(&data).expect("encode");
443 let decoded = decode(&encoded).unwrap();
444 assert_eq!(decoded, data);
445 }
446
447 #[test]
448 fn all_byte_values() {
449 let data: Vec<u8> = (0..=255u8).cycle().take(4096).collect();
451 let encoded = encode(&data).expect("encode");
452 let decoded = decode(&encoded).unwrap();
453 assert_eq!(decoded, data);
454 }
455
456 #[test]
457 fn skewed_distribution() {
458 let mut data = vec![0u8; 10_000];
460 for i in 0..1000 {
461 data[i * 10] = 1;
462 }
463 let encoded = encode(&data).expect("encode");
464 let decoded = decode(&encoded).unwrap();
465 assert_eq!(decoded, data);
466
467 let ratio = data.len() as f64 / encoded.len() as f64;
468 assert!(
469 ratio > 1.5,
470 "skewed data should compress >1.5x, got {ratio:.1}x"
471 );
472 }
473
474 #[test]
475 fn better_than_raw_on_structured() {
476 let mut data = Vec::with_capacity(10_000);
478 for i in 0..10_000 {
479 data.push((i % 16) as u8); }
481 let encoded = encode(&data).expect("encode");
482 let decoded = decode(&encoded).unwrap();
483 assert_eq!(decoded, data);
484
485 let ratio = data.len() as f64 / encoded.len() as f64;
486 assert!(
487 ratio > 1.5,
488 "low-entropy data should compress >1.5x, got {ratio:.1}x"
489 );
490 }
491
492 #[test]
493 fn truncated_input_errors() {
494 assert!(decode(&[]).is_err());
495 assert!(decode(&[1, 0, 0, 0]).is_err()); }
497
498 #[test]
499 fn hostile_lengths_and_frequencies_fail_before_output_allocation() {
500 let mut frame = vec![0u8; HEADER_SIZE];
501 frame[..4].copy_from_slice(&u32::MAX.to_le_bytes());
502 assert!(matches!(
503 decode(&frame),
504 Err(CodecError::ResourceLimit { .. })
505 ));
506
507 let encoded = encode(b"bounded rans").expect("encode");
508 let mut malformed = encoded.clone();
509 malformed[4..8].copy_from_slice(&PROB_SCALE.wrapping_add(1).to_le_bytes());
510 assert!(matches!(
511 decode(&malformed),
512 Err(CodecError::Corrupt { .. })
513 ));
514 let mut bad_size = encoded;
515 bad_size[HEADER_SIZE - 4..HEADER_SIZE].copy_from_slice(&u32::MAX.to_le_bytes());
516 assert!(decode(&bad_size).is_err());
517 }
518
519 #[test]
520 fn noncanonical_stream_padding_and_high_state_are_rejected() {
521 let encoded = encode(&[42]).expect("encode");
522 let size_start = HEADER_SIZE;
523 let stream_zero_size = u32::from_le_bytes([
524 encoded[size_start],
525 encoded[size_start + 1],
526 encoded[size_start + 2],
527 encoded[size_start + 3],
528 ]);
529 let stream_zero_start = HEADER_SIZE + NUM_STREAMS * 4;
530 let state_start = stream_zero_start + stream_zero_size as usize - 4;
531
532 let mut padded = encoded.clone();
533 padded.insert(state_start, 0xaa);
534 padded[size_start..size_start + 4].copy_from_slice(&(stream_zero_size + 1).to_le_bytes());
535 let compressed_size = u32::from_le_bytes([
536 padded[HEADER_SIZE - 4],
537 padded[HEADER_SIZE - 3],
538 padded[HEADER_SIZE - 2],
539 padded[HEADER_SIZE - 1],
540 ]);
541 padded[HEADER_SIZE - 4..HEADER_SIZE].copy_from_slice(&(compressed_size + 1).to_le_bytes());
542 assert!(matches!(decode(&padded), Err(CodecError::Corrupt { .. })));
543
544 let mut high_state = encoded;
545 high_state[state_start..state_start + 4].copy_from_slice(&u32::MAX.to_le_bytes());
546 assert!(matches!(
547 decode(&high_state),
548 Err(CodecError::Corrupt { .. })
549 ));
550 }
551}