#![no_std]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
#[inline(always)]
#[cfg(not(feature = "legacy-msrv"))]
const fn const_copy(dest: &mut [u8], di: usize, src: &[u8], n: usize) {
let (_, dt) = dest.split_at_mut(di);
let (dc, _) = dt.split_at_mut(n);
let (sc, _) = src.split_at(n);
dc.copy_from_slice(sc);
}
#[cfg_attr(test, mutants::skip)] #[inline(always)]
#[cfg(feature = "legacy-msrv")]
const fn const_copy(dest: &mut [u8], di: usize, src: &[u8], n: usize) {
let mut i = 0;
while i < n {
dest[di + i] = src[i];
i += 1;
}
}
#[must_use]
pub const fn encode(src: &[u8], dest: &mut [u8]) -> Option<usize> {
let mut si = 0;
let mut di = 0;
loop {
let remaining = src.len() - si;
let max_run = if remaining < 254 { remaining } else { 254 };
if max_run > 0 && src[si] == 0x00 {
if di >= dest.len() {
return None;
}
dest[di] = 0x01;
di += 1;
si += 1;
continue;
}
let (_, src_tail) = src.split_at(si);
let (src_window, _) = src_tail.split_at(max_run);
let mut run = 0;
while run < src_window.len() && src_window[run] != 0x00 {
run += 1;
}
let full_block = run == 254;
if di >= dest.len() {
return None;
}
dest[di] = if full_block { 0xFF } else { (run + 1) as u8 };
di += 1;
if run > 0 {
if di + run > dest.len() {
return None;
}
const_copy(dest, di, src_window, run);
di += run;
}
si += run;
if si >= src.len() {
break;
}
if !full_block {
si += 1; }
}
Some(di)
}
#[must_use]
pub fn decode(src: &[u8], dest: &mut [u8]) -> Option<usize> {
let mut src = src;
let mut di = 0;
while let Some((&code_byte, rest)) = src.split_first() {
src = rest;
let code = code_byte as usize;
if code == 0 {
return None;
}
if code == 1 {
let mut count = 1usize;
while let Some((&next, rest)) = src.split_first() {
if next != 0x01 {
break;
}
src = rest;
count += 1;
}
let zeros = if !src.is_empty() { count } else { count - 1 };
if di + zeros > dest.len() {
return None;
}
dest[di..di + zeros].fill(0);
di += zeros;
continue;
}
let n = code - 1;
if src.len() < n || di + n > dest.len() {
return None;
}
let (block, rest) = src.split_at(n);
dest[di..di + n].copy_from_slice(block);
di += n;
src = rest;
if code != 0xFF && !src.is_empty() {
if di >= dest.len() {
return None;
}
dest[di] = 0;
di += 1;
}
}
Some(di)
}
#[must_use]
pub const fn max_encoded_len(src_len: usize) -> usize {
src_len + (src_len / 254) + 1
}
#[cfg(test)]
mod tests {
use super::*;
extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;
fn dec(encoded: &[u8]) -> Option<Vec<u8>> {
let mut buf = vec![0u8; 512];
decode(encoded, &mut buf).map(|n| buf[..n].to_vec())
}
fn enc(input: &[u8]) -> Option<Vec<u8>> {
let mut buf = vec![0u8; max_encoded_len(input.len()) + 1];
encode(input, &mut buf).map(|n| buf[..n].to_vec())
}
const _: () = {
let input = [0x11, 0x00, 0x33];
let mut buf = [0u8; 8];
let Some(n) = encode(&input, &mut buf) else {
panic!("const encode failed");
};
assert!(n == 4);
assert!(buf[0] == 0x02);
assert!(buf[1] == 0x11);
assert!(buf[2] == 0x02);
assert!(buf[3] == 0x33);
};
#[test]
fn decode_empty() {
assert_eq!(dec(&[0x01]), Some(vec![]));
}
#[test]
fn decode_single_zero() {
assert_eq!(dec(&[0x01, 0x01]), Some(vec![0x00]));
}
#[test]
fn decode_two_zeros() {
assert_eq!(dec(&[0x01, 0x01, 0x01]), Some(vec![0x00, 0x00]));
}
#[test]
fn decode_single_nonzero() {
assert_eq!(dec(&[0x02, 0x11]), Some(vec![0x11]));
}
#[test]
fn decode_zero_delimited() {
assert_eq!(dec(&[0x01, 0x02, 0x11, 0x01]), Some(vec![0x00, 0x11, 0x00]));
}
#[test]
fn decode_mixed() {
assert_eq!(
dec(&[0x03, 0x11, 0x22, 0x02, 0x33]),
Some(vec![0x11, 0x22, 0x00, 0x33])
);
}
#[test]
fn decode_no_zeros() {
assert_eq!(
dec(&[0x05, 0x11, 0x22, 0x33, 0x44]),
Some(vec![0x11, 0x22, 0x33, 0x44])
);
}
#[test]
fn decode_trailing_zeros() {
assert_eq!(
dec(&[0x02, 0x11, 0x01, 0x01, 0x01]),
Some(vec![0x11, 0x00, 0x00, 0x00])
);
}
#[test]
fn decode_all_zeros_4() {
assert_eq!(
dec(&[0x01, 0x01, 0x01, 0x01, 0x01]),
Some(vec![0x00, 0x00, 0x00, 0x00])
);
}
#[test]
fn decode_all_ff_4() {
assert_eq!(
dec(&[0x05, 0xFF, 0xFF, 0xFF, 0xFF]),
Some(vec![0xFF, 0xFF, 0xFF, 0xFF])
);
}
#[test]
fn decode_alternating() {
assert_eq!(
dec(&[0x01, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03]),
Some(vec![0x00, 0x01, 0x00, 0x02, 0x00, 0x03])
);
}
#[test]
fn decode_254_nonzero() {
let input: Vec<u8> = (1..=254).map(|i| i as u8).collect();
let mut encoded = vec![0xFF];
encoded.extend(1u8..=254);
assert_eq!(dec(&encoded), Some(input));
}
#[test]
fn decode_255_nonzero_split() {
let input: Vec<u8> = (1..=255).map(|i| i as u8).collect();
let mut encoded = vec![0xFF];
encoded.extend(1u8..=254);
encoded.push(0x02);
encoded.push(0xFF);
assert_eq!(dec(&encoded), Some(input));
}
#[test]
fn decode_ping_tag_zero() {
assert_eq!(dec(&[0x01, 0x01]), Some(vec![0x00]));
}
#[test]
fn decode_unexpected_zero() {
assert_eq!(dec(&[0x00]), None);
}
#[test]
fn decode_truncated() {
assert_eq!(dec(&[0x04, 0x11]), None);
}
#[test]
fn decode_empty_input() {
assert_eq!(dec(&[]), Some(vec![]));
}
#[test]
fn decode_err_truncated_code_block() {
assert_eq!(dec(&[0x05, 0x31, 0x32, 0x33]), None);
}
#[test]
fn decode_err_zero_after_code_block() {
assert_eq!(dec(&[0x05, 0x31, 0x32, 0x33, 0x34, 0x00]), None);
}
#[test]
fn decode_zero_in_data_position_is_passthrough() {
assert_eq!(
dec(&[0x05, 0x31, 0x32, 0x00, 0x34]),
Some(vec![0x31, 0x32, 0x00, 0x34])
);
}
#[test]
fn corpus_wiki_vec9() {
let input: Vec<u8> = (1..=255).map(|i| i as u8).collect();
let mut expected = vec![0xFF];
expected.extend(1u8..=254);
expected.extend([0x02, 0xFF]);
assert_eq!(enc(&input), Some(expected.clone()));
assert_eq!(dec(&expected), Some(input));
}
#[test]
fn corpus_wiki_vec10() {
let mut input: Vec<u8> = (2..=255).map(|i| i as u8).collect();
input.push(0x00);
let mut expected = vec![0xFF];
expected.extend(2u8..=255);
expected.extend([0x01, 0x01]);
assert_eq!(enc(&input), Some(expected.clone()));
assert_eq!(dec(&expected), Some(input));
}
#[test]
fn corpus_wiki_vec11() {
let mut input: Vec<u8> = (3..=255).map(|i| i as u8).collect();
input.push(0x00);
input.push(0x01);
let mut expected = vec![0xFE];
expected.extend(3u8..=255);
expected.extend([0x02, 0x01]);
assert_eq!(enc(&input), Some(expected.clone()));
assert_eq!(dec(&expected), Some(input));
}
#[test]
fn corpus_paper_ipv4_header() {
let input = [
0x45, 0x00, 0x00, 0x2C, 0x4C, 0x79, 0x00, 0x00, 0x40, 0x06, 0x4F, 0x37,
];
let expected = [
0x02, 0x45, 0x01, 0x04, 0x2C, 0x4C, 0x79, 0x01, 0x05, 0x40, 0x06, 0x4F, 0x37,
];
assert_eq!(enc(&input), Some(expected.to_vec()));
assert_eq!(dec(&expected), Some(input.to_vec()));
}
#[test]
fn corpus_nanocobs_single_nonzero() {
assert_eq!(enc(&[0x34]), Some(vec![0x02, 0x34]));
}
#[test]
fn corpus_nanocobs_two_nonzero() {
assert_eq!(enc(&[0x34, 0x56]), Some(vec![0x03, 0x34, 0x56]));
}
#[test]
fn corpus_nanocobs_eight_nonzero() {
let input = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xFF];
let expected = [0x09, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xFF];
assert_eq!(enc(&input), Some(expected.to_vec()));
assert_eq!(dec(&expected), Some(input.to_vec()));
}
#[test]
fn corpus_nanocobs_eight_zeros() {
let input = [0x00; 8];
let expected = [0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01];
assert_eq!(enc(&input), Some(expected.to_vec()));
assert_eq!(dec(&expected), Some(input.to_vec()));
}
#[test]
fn corpus_nanocobs_interleaved_00_11() {
let input = [0x00, 0x11, 0x00, 0x22];
let expected = [0x01, 0x02, 0x11, 0x02, 0x22];
assert_eq!(enc(&input), Some(expected.to_vec()));
assert_eq!(dec(&expected), Some(input.to_vec()));
}
#[test]
fn corpus_nanocobs_trailing_zero() {
let input = [0x11, 0x00, 0x22, 0x00];
let expected = [0x02, 0x11, 0x02, 0x22, 0x01];
assert_eq!(enc(&input), Some(expected.to_vec()));
assert_eq!(dec(&expected), Some(input.to_vec()));
}
#[test]
fn corpus_nanocobs_253_fill() {
let input = vec![0x42u8; 253];
let mut expected = vec![0xFE];
expected.extend(vec![0x42u8; 253]);
assert_eq!(enc(&input), Some(expected.clone()));
assert_eq!(dec(&expected), Some(input));
}
#[test]
fn corpus_nanocobs_255_ones() {
let input = vec![0x01u8; 255];
let mut expected = vec![0xFF];
expected.extend(vec![0x01u8; 254]);
expected.extend([0x02, 0x01]);
assert_eq!(enc(&input), Some(expected.clone()));
assert_eq!(dec(&expected), Some(input));
}
#[test]
fn corpus_nanocobs_508_fill() {
let input = vec![0xAAu8; 508];
let mut expected = vec![0xFF];
expected.extend(vec![0xAAu8; 254]);
expected.push(0xFF);
expected.extend(vec![0xAAu8; 254]);
assert_eq!(enc(&input), Some(expected.clone()));
assert_eq!(dec(&expected), Some(input));
}
#[test]
fn corpus_cobsc_five_nonzero() {
let input = [0x31, 0x32, 0x33, 0x34, 0x35];
let expected = [0x06, 0x31, 0x32, 0x33, 0x34, 0x35];
assert_eq!(enc(&input), Some(expected.to_vec()));
assert_eq!(dec(&expected), Some(input.to_vec()));
}
#[test]
fn corpus_cobsc_two_segments() {
let input = [0x31, 0x32, 0x33, 0x34, 0x35, 0x00, 0x36, 0x37, 0x38, 0x39];
let expected = [
0x06, 0x31, 0x32, 0x33, 0x34, 0x35, 0x05, 0x36, 0x37, 0x38, 0x39,
];
assert_eq!(enc(&input), Some(expected.to_vec()));
assert_eq!(dec(&expected), Some(input.to_vec()));
}
#[test]
fn corpus_cobsc_leading_zero_two_segments() {
let input = [
0x00, 0x31, 0x32, 0x33, 0x34, 0x35, 0x00, 0x36, 0x37, 0x38, 0x39,
];
let expected = [
0x01, 0x06, 0x31, 0x32, 0x33, 0x34, 0x35, 0x05, 0x36, 0x37, 0x38, 0x39,
];
assert_eq!(enc(&input), Some(expected.to_vec()));
assert_eq!(dec(&expected), Some(input.to_vec()));
}
#[test]
fn corpus_cobsc_trailing_zero_two_segments() {
let input = [
0x31, 0x32, 0x33, 0x34, 0x35, 0x00, 0x36, 0x37, 0x38, 0x39, 0x00,
];
let expected = [
0x06, 0x31, 0x32, 0x33, 0x34, 0x35, 0x05, 0x36, 0x37, 0x38, 0x39, 0x01,
];
assert_eq!(enc(&input), Some(expected.to_vec()));
assert_eq!(dec(&expected), Some(input.to_vec()));
}
#[test]
fn corpus_cobsc_three_zeros() {
assert_eq!(enc(&[0x00, 0x00, 0x00]), Some(vec![0x01, 0x01, 0x01, 0x01]));
}
#[test]
fn corpus_cobsc_253_ascending() {
let input: Vec<u8> = (1..=253).map(|i| i as u8).collect();
let mut expected = vec![0xFE];
expected.extend(1u8..=253);
assert_eq!(enc(&input), Some(expected.clone()));
assert_eq!(dec(&expected), Some(input));
}
#[test]
fn corpus_cobsc_zero_then_256_bytes() {
let mut input = vec![0x00u8];
input.extend(1u8..=255);
let mut expected = vec![0x01, 0xFF];
expected.extend(1u8..=254);
expected.extend([0x02, 0xFF]);
assert_eq!(enc(&input), Some(expected.clone()));
assert_eq!(dec(&expected), Some(input));
}
#[test]
fn corpus_cobs2rs_abc_ghij_xyz() {
let input = [
0x41, 0x42, 0x43, 0x00, 0x67, 0x68, 0x69, 0x6A, 0x00, 0x78, 0x79, 0x7A,
];
let expected = [
0x04, 0x41, 0x42, 0x43, 0x05, 0x67, 0x68, 0x69, 0x6A, 0x04, 0x78, 0x79, 0x7A,
];
assert_eq!(enc(&input), Some(expected.to_vec()));
assert_eq!(dec(&expected), Some(input.to_vec()));
}
#[test]
fn corpus_fortier_254_nonzero_then_zero() {
let mut input: Vec<u8> = (1..=254).collect();
input.push(0x00);
let mut expected = vec![0xFF];
expected.extend(1u8..=254);
expected.extend([0x01, 0x01]);
assert_eq!(enc(&input), Some(expected.clone()));
assert_eq!(dec(&expected), Some(input));
}
#[test]
fn corpus_python_hello_world() {
let input = b"Hello world\x00This is a test";
let expected = [
0x0C, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x0F, 0x54,
0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74,
];
assert_eq!(enc(input), Some(expected.to_vec()));
assert_eq!(dec(&expected), Some(input.to_vec()));
}
#[test]
fn encode_empty() {
assert_eq!(enc(&[]), Some(vec![0x01]));
}
#[test]
fn encode_single_zero() {
assert_eq!(enc(&[0x00]), Some(vec![0x01, 0x01]));
}
#[test]
fn encode_single_nonzero() {
assert_eq!(enc(&[0x11]), Some(vec![0x02, 0x11]));
}
#[test]
fn encode_mixed() {
assert_eq!(
enc(&[0x11, 0x22, 0x00, 0x33]),
Some(vec![0x03, 0x11, 0x22, 0x02, 0x33])
);
}
#[test]
fn encode_no_zeros() {
assert_eq!(
enc(&[0x11, 0x22, 0x33, 0x44]),
Some(vec![0x05, 0x11, 0x22, 0x33, 0x44])
);
}
#[test]
fn encode_trailing_zeros() {
assert_eq!(
enc(&[0x11, 0x00, 0x00, 0x00]),
Some(vec![0x02, 0x11, 0x01, 0x01, 0x01])
);
}
#[test]
fn encode_254_nonzero() {
let input: Vec<u8> = (1..=254).map(|i| i as u8).collect();
let mut expected = vec![0xFF];
expected.extend(1u8..=254);
assert_eq!(enc(&input), Some(expected));
}
#[test]
fn encode_254_block_boundary_exact() {
let input: Vec<u8> = (1..=254).map(|i| i as u8).collect();
let encoded = enc(&input).unwrap();
assert_eq!(encoded[0], 0xFF); assert_eq!(encoded.len(), 255); let mut dec = vec![0u8; 256];
let n = decode(&encoded, &mut dec).unwrap();
assert_eq!(&dec[..n], &input[..]);
}
#[test]
fn encode_255_nonzero_needs_two_blocks() {
let input: Vec<u8> = (1..=255).map(|i| (i % 255 + 1) as u8).collect();
let encoded = enc(&input).unwrap();
assert_eq!(encoded[0], 0xFF); assert_eq!(encoded[255], 0x02); assert_eq!(encoded.len(), 257); }
#[test]
fn encode_block_boundary_matches_corncobs() {
for len in [254, 255, 256, 508, 512] {
let input: Vec<u8> = (0..len).map(|i| (i % 254 + 1) as u8).collect();
let ours = enc(&input).unwrap();
let mut their_buf = vec![0u8; corncobs::max_encoded_len(input.len())];
let their_len = corncobs::encode_buf(&input, &mut their_buf);
assert_eq!(
&ours[..],
&their_buf[..their_len - 1],
"mismatch at len={len}"
);
}
}
#[test]
fn roundtrip_empty() {
roundtrip(&[]);
}
#[test]
fn roundtrip_single_zero() {
roundtrip(&[0x00]);
}
#[test]
fn roundtrip_ping() {
roundtrip(&[0x00]); }
#[test]
fn roundtrip_mixed() {
roundtrip(&[0x11, 0x22, 0x00, 0x33, 0x00, 0x00, 0x44]);
}
#[test]
fn roundtrip_254_nonzero() {
let data: Vec<u8> = (1..=254).map(|i| i as u8).collect();
roundtrip(&data);
}
#[test]
fn roundtrip_255_nonzero() {
let data: Vec<u8> = (0..255).map(|i| (i + 1) as u8).collect();
roundtrip(&data);
}
#[test]
fn roundtrip_256_with_zeros() {
let mut data = vec![0u8; 256];
for (i, b) in data.iter_mut().enumerate() {
*b = i as u8; }
roundtrip(&data);
}
fn roundtrip(input: &[u8]) {
let mut enc_buf = vec![0u8; max_encoded_len(input.len()) + 1];
let enc_len = encode(input, &mut enc_buf).expect("encode failed");
let encoded = &enc_buf[..enc_len];
assert!(
!encoded.contains(&0x00),
"encoded output contains 0x00: {:?}",
encoded
);
let mut dec_buf = vec![0u8; input.len() + 1];
let dec_len = decode(encoded, &mut dec_buf).expect("decode failed");
assert_eq!(&dec_buf[..dec_len], input, "round-trip mismatch");
}
fn cross_encode(input: &[u8]) {
let mut our_buf = vec![0u8; max_encoded_len(input.len()) + 2];
let our_len = encode(input, &mut our_buf).expect("our encode failed");
our_buf[our_len] = 0x00;
let our_with_sentinel = &our_buf[..our_len + 1];
let mut their_buf = vec![0u8; input.len() + 1];
let their_len = corncobs::decode_buf(our_with_sentinel, &mut their_buf)
.expect("corncobs failed to decode our output");
assert_eq!(&their_buf[..their_len], input, "cross-encode mismatch");
}
fn cross_decode(input: &[u8]) {
let mut their_buf = vec![0u8; corncobs::max_encoded_len(input.len())];
let their_len = corncobs::encode_buf(input, &mut their_buf);
let their_encoded = &their_buf[..their_len];
let data_end = their_encoded
.iter()
.rposition(|&b| b != 0x00)
.map_or(0, |i| i + 1);
let their_no_sentinel = &their_encoded[..data_end];
let mut our_buf = vec![0u8; input.len() + 1];
let our_len =
decode(their_no_sentinel, &mut our_buf).expect("our decode failed on corncobs output");
assert_eq!(&our_buf[..our_len], input, "cross-decode mismatch");
}
#[test]
fn interop_empty() {
cross_encode(&[]);
cross_decode(&[]);
}
#[test]
fn interop_single_zero() {
cross_encode(&[0x00]);
cross_decode(&[0x00]);
}
#[test]
fn interop_mixed() {
let data = [0x11, 0x22, 0x00, 0x33, 0x00, 0x00, 0x44];
cross_encode(&data);
cross_decode(&data);
}
#[test]
fn interop_254_block() {
let data: Vec<u8> = (1..=254).map(|i| i as u8).collect();
cross_encode(&data);
cross_decode(&data);
}
#[test]
fn interop_255_block() {
let data: Vec<u8> = (0..255).map(|i| (i + 1) as u8).collect();
cross_encode(&data);
cross_decode(&data);
}
#[test]
fn interop_random_payloads() {
use rand::RngExt;
let mut rng = rand::rng();
for _ in 0..500 {
let len = rng.random_range(0..=512);
let data: Vec<u8> = (0..len).map(|_| rng.random()).collect();
cross_encode(&data);
cross_decode(&data);
}
}
#[test]
fn interop_all_zeros() {
for len in [0, 1, 2, 10, 100, 254, 255, 256, 512] {
let data = vec![0u8; len];
cross_encode(&data);
cross_decode(&data);
}
}
#[test]
fn interop_all_ff() {
for len in [0, 1, 2, 10, 100, 254, 255, 256, 512] {
let data = vec![0xFFu8; len];
cross_encode(&data);
cross_decode(&data);
}
}
#[test]
fn encode_dest_exact_size() {
let input = [0x11, 0x00, 0x33];
let needed = max_encoded_len(input.len());
let mut buf = vec![0u8; needed];
assert_eq!(encode(&input, &mut buf), Some(4));
}
#[test]
fn encode_dest_one_too_small() {
let input = [0x11, 0x00, 0x33];
let needed = max_encoded_len(input.len());
let mut buf = vec![0u8; needed - 1];
assert_eq!(encode(&input, &mut buf), None);
}
#[test]
fn encode_dest_empty() {
assert_eq!(encode(&[0x11], &mut []), None);
}
#[test]
fn encode_empty_into_single_byte() {
let mut buf = [0u8; 1];
assert_eq!(encode(&[], &mut buf), Some(1));
assert_eq!(buf[0], 0x01);
}
#[test]
fn decode_dest_too_small() {
assert_eq!(decode(&[0x03, 0x11, 0x22], &mut [0u8; 1]), None);
}
#[test]
fn decode_dest_exact_size() {
let mut out = [0u8; 2];
assert_eq!(decode(&[0x03, 0x11, 0x22], &mut out), Some(2));
assert_eq!(out, [0x11, 0x22]);
}
#[test]
fn decode_zeros_dest_exact_size() {
for n in [1, 2, 10, 64, 254, 255, 256] {
let encoded: Vec<u8> = vec![0x01; n + 1];
let mut out = vec![0u8; n]; assert_eq!(decode(&encoded, &mut out), Some(n), "failed at n={n}");
assert!(out.iter().all(|&b| b == 0x00), "wrong data at n={n}");
}
}
#[test]
fn decode_zeros_dest_one_too_small() {
for n in [2, 10, 64, 256] {
let encoded: Vec<u8> = vec![0x01; n + 1];
let mut out = vec![0u8; n - 1]; assert_eq!(decode(&encoded, &mut out), None, "should fail at n={n}");
}
}
#[test]
fn roundtrip_508_nonzero() {
let data: Vec<u8> = (0..508).map(|i| (i % 254 + 1) as u8).collect();
roundtrip(&data);
}
#[test]
fn roundtrip_762_nonzero() {
let data: Vec<u8> = (0..762).map(|i| (i % 254 + 1) as u8).collect();
roundtrip(&data);
}
#[test]
fn roundtrip_254_nonzero_then_zero() {
let mut data: Vec<u8> = (1..=254).collect();
data.push(0x00);
roundtrip(&data);
}
#[test]
fn roundtrip_254_nonzero_then_zeros() {
let mut data: Vec<u8> = (1..=254).collect();
data.extend([0x00, 0x00, 0x00]);
roundtrip(&data);
}
#[test]
fn max_encoded_len_zero() {
assert_eq!(max_encoded_len(0), 1);
}
#[test]
fn max_encoded_len_one() {
assert_eq!(max_encoded_len(1), 2);
}
#[test]
fn max_encoded_len_254() {
assert_eq!(max_encoded_len(254), 256);
}
#[test]
fn max_encoded_len_255() {
assert_eq!(max_encoded_len(255), 257);
}
#[test]
fn encode_fits_max_encoded_len_all_patterns() {
for len in [0, 1, 2, 10, 100, 253, 254, 255, 256, 508, 512, 1024] {
for fill in [0x00u8, 0x01, 0x7F, 0xFF] {
let data = vec![fill; len];
let max = max_encoded_len(len);
let mut buf = vec![0u8; max];
let n = encode(&data, &mut buf)
.unwrap_or_else(|| panic!("encode failed for len={len}, fill=0x{fill:02X}"));
assert!(
n <= max,
"encoded len {n} exceeds max {max} for len={len}, fill=0x{fill:02X}"
);
}
}
}
}
#[cfg(test)]
mod proptests {
extern crate alloc;
use super::*;
use alloc::vec;
use proptest::prelude::*;
const CASES: u32 = 25_000;
fn config() -> ProptestConfig {
ProptestConfig {
cases: CASES,
..ProptestConfig::default()
}
}
proptest! {
#![proptest_config(config())]
#[test]
fn roundtrip_any(data in proptest::collection::vec(any::<u8>(), 0..4096)) {
let mut enc_buf = vec![0u8; max_encoded_len(data.len()) + 1];
let enc_len = encode(&data, &mut enc_buf).unwrap();
let encoded = &enc_buf[..enc_len];
prop_assert!(!encoded.contains(&0x00));
let mut dec_buf = vec![0u8; data.len() + 1];
let dec_len = decode(encoded, &mut dec_buf).unwrap();
prop_assert_eq!(&dec_buf[..dec_len], &data[..]);
}
#[test]
fn roundtrip_exact_dest(data in proptest::collection::vec(any::<u8>(), 0..4096)) {
let max = max_encoded_len(data.len());
let mut enc_buf = vec![0u8; max];
let enc_len = encode(&data, &mut enc_buf).unwrap();
prop_assert!(enc_len <= max);
let mut dec_buf = vec![0u8; data.len()];
let dec_len = decode(&enc_buf[..enc_len], &mut dec_buf).unwrap();
prop_assert_eq!(&dec_buf[..dec_len], &data[..]);
}
}
proptest! {
#![proptest_config(config())]
#[test]
fn encoded_length_bounded(data in proptest::collection::vec(any::<u8>(), 0..4096)) {
let mut enc_buf = vec![0u8; max_encoded_len(data.len()) + 1];
let enc_len = encode(&data, &mut enc_buf).unwrap();
prop_assert!(enc_len <= max_encoded_len(data.len()));
}
#[test]
fn encoded_no_zeros(data in proptest::collection::vec(any::<u8>(), 0..4096)) {
let mut enc_buf = vec![0u8; max_encoded_len(data.len()) + 1];
let enc_len = encode(&data, &mut enc_buf).unwrap();
prop_assert!(!enc_buf[..enc_len].contains(&0x00));
}
#[test]
fn encode_small_dest_never_panics(
data in proptest::collection::vec(any::<u8>(), 1..512),
shrink in 1usize..256,
) {
let max = max_encoded_len(data.len());
let dest_size = max.saturating_sub(shrink);
let mut buf = vec![0u8; dest_size];
let _ = encode(&data, &mut buf); }
#[test]
fn encode_empty_is_0x01(_dummy in 0u8..1) {
let mut buf = [0u8; 1];
let n = encode(&[], &mut buf).unwrap();
prop_assert_eq!(n, 1);
prop_assert_eq!(buf[0], 0x01);
}
}
proptest! {
#![proptest_config(config())]
#[test]
fn decode_garbage_never_panics(data in proptest::collection::vec(any::<u8>(), 0..4096)) {
let mut dec_buf = vec![0u8; data.len() + 256];
let _ = decode(&data, &mut dec_buf);
}
#[test]
fn decode_small_dest_never_panics(
data in proptest::collection::vec(any::<u8>(), 0..512),
dest_size in 0usize..64,
) {
let mut buf = vec![0u8; dest_size];
let _ = decode(&data, &mut buf);
}
#[test]
fn decode_rejects_zero_code_byte(
suffix in proptest::collection::vec(any::<u8>(), 0..64),
) {
let mut data = vec![0x00];
data.extend(&suffix);
let mut buf = vec![0u8; data.len() + 256];
prop_assert_eq!(decode(&data, &mut buf), None);
}
#[test]
fn decode_of_encode_always_succeeds(data in proptest::collection::vec(any::<u8>(), 0..4096)) {
let mut enc_buf = vec![0u8; max_encoded_len(data.len()) + 1];
let enc_len = encode(&data, &mut enc_buf).unwrap();
let mut dec_buf = vec![0u8; data.len() + 1];
prop_assert!(decode(&enc_buf[..enc_len], &mut dec_buf).is_some());
}
}
proptest! {
#![proptest_config(config())]
#[test]
fn interop_our_encode_their_decode(data in proptest::collection::vec(any::<u8>(), 0..4096)) {
let mut enc_buf = vec![0u8; max_encoded_len(data.len()) + 2];
let enc_len = encode(&data, &mut enc_buf).unwrap();
enc_buf[enc_len] = 0x00;
let mut dec_buf = vec![0u8; data.len() + 1];
let dec_len = corncobs::decode_buf(&enc_buf[..enc_len + 1], &mut dec_buf).unwrap();
prop_assert_eq!(&dec_buf[..dec_len], &data[..]);
}
#[test]
fn interop_their_encode_our_decode(data in proptest::collection::vec(any::<u8>(), 0..4096)) {
let mut enc_buf = vec![0u8; corncobs::max_encoded_len(data.len())];
let enc_len = corncobs::encode_buf(&data, &mut enc_buf);
let data_end = enc_buf[..enc_len].iter().rposition(|&b| b != 0x00).map_or(0, |i| i + 1);
let mut dec_buf = vec![0u8; data.len() + 1];
let dec_len = decode(&enc_buf[..data_end], &mut dec_buf).unwrap();
prop_assert_eq!(&dec_buf[..dec_len], &data[..]);
}
#[test]
fn interop_encode_byte_identical(data in proptest::collection::vec(any::<u8>(), 0..4096)) {
let mut our_buf = vec![0u8; max_encoded_len(data.len()) + 2];
let our_len = encode(&data, &mut our_buf).unwrap();
let mut their_buf = vec![0u8; corncobs::max_encoded_len(data.len())];
let their_len = corncobs::encode_buf(&data, &mut their_buf);
let their_end = their_buf[..their_len]
.iter()
.rposition(|&b| b != 0x00)
.map_or(0, |i| i + 1);
prop_assert_eq!(
&our_buf[..our_len],
&their_buf[..their_end],
"encoded output differs for input len={}",
data.len()
);
}
}
proptest! {
#![proptest_config(config())]
#[test]
fn encode_deterministic(data in proptest::collection::vec(any::<u8>(), 0..2048)) {
let mut buf1 = vec![0u8; max_encoded_len(data.len()) + 1];
let mut buf2 = vec![0u8; max_encoded_len(data.len()) + 1];
let n1 = encode(&data, &mut buf1).unwrap();
let n2 = encode(&data, &mut buf2).unwrap();
prop_assert_eq!(n1, n2);
prop_assert_eq!(&buf1[..n1], &buf2[..n2]);
}
#[test]
fn decode_deterministic(data in proptest::collection::vec(any::<u8>(), 0..2048)) {
let mut enc_buf = vec![0u8; max_encoded_len(data.len()) + 1];
let enc_len = encode(&data, &mut enc_buf).unwrap();
let encoded = &enc_buf[..enc_len];
let mut buf1 = vec![0u8; data.len() + 1];
let mut buf2 = vec![0u8; data.len() + 1];
let n1 = decode(encoded, &mut buf1).unwrap();
let n2 = decode(encoded, &mut buf2).unwrap();
prop_assert_eq!(n1, n2);
prop_assert_eq!(&buf1[..n1], &buf2[..n2]);
}
#[test]
fn encoded_length_at_least_one(data in proptest::collection::vec(any::<u8>(), 0..4096)) {
let mut enc_buf = vec![0u8; max_encoded_len(data.len()) + 1];
let enc_len = encode(&data, &mut enc_buf).unwrap();
prop_assert!(enc_len >= 1);
}
#[test]
fn encoded_length_strictly_greater(data in proptest::collection::vec(any::<u8>(), 0..4096)) {
let mut enc_buf = vec![0u8; max_encoded_len(data.len()) + 1];
let enc_len = encode(&data, &mut enc_buf).unwrap();
prop_assert!(enc_len > data.len());
}
#[test]
fn decoded_length_matches_original(data in proptest::collection::vec(any::<u8>(), 0..4096)) {
let mut enc_buf = vec![0u8; max_encoded_len(data.len()) + 1];
let enc_len = encode(&data, &mut enc_buf).unwrap();
let mut dec_buf = vec![0u8; data.len() + 1];
let dec_len = decode(&enc_buf[..enc_len], &mut dec_buf).unwrap();
prop_assert_eq!(dec_len, data.len());
}
#[test]
fn first_encoded_byte_is_valid_code(data in proptest::collection::vec(any::<u8>(), 0..4096)) {
let mut enc_buf = vec![0u8; max_encoded_len(data.len()) + 1];
let enc_len = encode(&data, &mut enc_buf).unwrap();
prop_assert!(enc_len >= 1);
prop_assert!(enc_buf[0] >= 1); }
}
proptest! {
#![proptest_config(config())]
#[test]
fn roundtrip_near_block_boundary(
data in proptest::collection::vec(any::<u8>(), 250..260)
) {
let mut enc_buf = vec![0u8; max_encoded_len(data.len()) + 1];
let enc_len = encode(&data, &mut enc_buf).unwrap();
let encoded = &enc_buf[..enc_len];
prop_assert!(!encoded.contains(&0x00));
let mut dec_buf = vec![0u8; data.len() + 1];
let dec_len = decode(encoded, &mut dec_buf).unwrap();
prop_assert_eq!(&dec_buf[..dec_len], &data[..]);
}
#[test]
fn roundtrip_multi_block(
data in proptest::collection::vec(any::<u8>(), 500..520)
) {
let mut enc_buf = vec![0u8; max_encoded_len(data.len()) + 1];
let enc_len = encode(&data, &mut enc_buf).unwrap();
let encoded = &enc_buf[..enc_len];
prop_assert!(!encoded.contains(&0x00));
let mut dec_buf = vec![0u8; data.len() + 1];
let dec_len = decode(encoded, &mut dec_buf).unwrap();
prop_assert_eq!(&dec_buf[..dec_len], &data[..]);
}
#[test]
fn roundtrip_tiny(data in proptest::collection::vec(any::<u8>(), 0..8)) {
let mut enc_buf = vec![0u8; max_encoded_len(data.len()) + 1];
let enc_len = encode(&data, &mut enc_buf).unwrap();
let encoded = &enc_buf[..enc_len];
prop_assert!(!encoded.contains(&0x00));
let mut dec_buf = vec![0u8; data.len() + 1];
let dec_len = decode(encoded, &mut dec_buf).unwrap();
prop_assert_eq!(&dec_buf[..dec_len], &data[..]);
}
#[test]
fn roundtrip_all_zeros(len in 0usize..2048) {
let data = vec![0u8; len];
let mut enc_buf = vec![0u8; max_encoded_len(len) + 1];
let enc_len = encode(&data, &mut enc_buf).unwrap();
let encoded = &enc_buf[..enc_len];
prop_assert!(!encoded.contains(&0x00));
let mut dec_buf = vec![0u8; len + 1];
let dec_len = decode(encoded, &mut dec_buf).unwrap();
prop_assert_eq!(&dec_buf[..dec_len], &data[..]);
}
#[test]
fn roundtrip_all_ff(len in 0usize..2048) {
let data = vec![0xFFu8; len];
let mut enc_buf = vec![0u8; max_encoded_len(len) + 1];
let enc_len = encode(&data, &mut enc_buf).unwrap();
let encoded = &enc_buf[..enc_len];
prop_assert!(!encoded.contains(&0x00));
let mut dec_buf = vec![0u8; len + 1];
let dec_len = decode(encoded, &mut dec_buf).unwrap();
prop_assert_eq!(&dec_buf[..dec_len], &data[..]);
}
#[test]
fn roundtrip_single_byte_repeated(byte in any::<u8>(), len in 0usize..1024) {
let data = vec![byte; len];
let mut enc_buf = vec![0u8; max_encoded_len(len) + 1];
let enc_len = encode(&data, &mut enc_buf).unwrap();
let encoded = &enc_buf[..enc_len];
prop_assert!(!encoded.contains(&0x00));
let mut dec_buf = vec![0u8; len + 1];
let dec_len = decode(encoded, &mut dec_buf).unwrap();
prop_assert_eq!(&dec_buf[..dec_len], &data[..]);
}
#[test]
fn roundtrip_binary_alphabet(
data in proptest::collection::vec(prop_oneof![Just(0x00u8), Just(0xFFu8)], 0..2048)
) {
let mut enc_buf = vec![0u8; max_encoded_len(data.len()) + 1];
let enc_len = encode(&data, &mut enc_buf).unwrap();
let encoded = &enc_buf[..enc_len];
prop_assert!(!encoded.contains(&0x00));
let mut dec_buf = vec![0u8; data.len() + 1];
let dec_len = decode(encoded, &mut dec_buf).unwrap();
prop_assert_eq!(&dec_buf[..dec_len], &data[..]);
}
}
}