#![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
extern crate std;
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum EncodeError {
#[error("buffer too small: have {have} bytes, want {want} bytes")]
BufferTooSmall { have: usize, want: usize },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum DecodeError {
#[error("empty input")]
EmptyInput,
#[error("buffer too small: have {have} bytes, want {want} bytes")]
BufferTooSmall { have: usize, want: usize },
#[error("zero marker at position {at}")]
ZeroMarker { at: usize },
#[error("zero byte in data at position {at}")]
ZeroBinary { at: usize },
#[error("chunk overflow at position {at}: chunk {marker} exceeds data length {len}")]
ChunkOverflow { at: usize, marker: u8, len: usize },
}
#[inline]
pub const fn encode_buffer(size: usize) -> usize {
size + size.div_ceil(254) + 1
}
#[inline]
pub const fn decode_buffer(size: usize) -> usize {
if size == 0 {
return 0;
}
size - 1
}
#[inline]
pub fn encode(data: &[u8], encoded: &mut [u8]) -> Result<usize, EncodeError> {
let want = encode_buffer(data.len());
if encoded.len() < want {
return Err(EncodeError::BufferTooSmall {
have: encoded.len(),
want,
});
}
Ok(unsafe { encode_unsafe(data, encoded) })
}
#[inline]
pub unsafe fn encode_unsafe(data: &[u8], encoded: &mut [u8]) -> usize {
if data.is_empty() {
encoded[0] = 0x01;
return 1;
}
debug_assert!(encoded.len() >= encode_buffer(data.len()));
unsafe {
let mut input_pos = 0usize;
let mut output_pos = 0usize;
loop {
let run = if *data.get_unchecked(input_pos) == 0 {
Some(0)
} else {
memchr::memchr(0, data.get_unchecked(input_pos..))
};
let mut rem = run.unwrap_or(data.len() - input_pos);
while rem >= 254 {
*encoded.get_unchecked_mut(output_pos) = 0xff;
core::ptr::copy_nonoverlapping(
data.as_ptr().add(input_pos),
encoded.as_mut_ptr().add(output_pos + 1),
254,
);
input_pos += 254;
output_pos += 255;
rem -= 254;
}
if run.is_some() {
*encoded.get_unchecked_mut(output_pos) = rem as u8 + 1;
core::ptr::copy_nonoverlapping(
data.as_ptr().add(input_pos),
encoded.as_mut_ptr().add(output_pos + 1),
rem,
);
input_pos += rem + 1;
output_pos += rem + 1;
if input_pos == data.len() {
*encoded.get_unchecked_mut(output_pos) = 0x01;
return output_pos + 1;
}
} else {
if rem > 0 {
*encoded.get_unchecked_mut(output_pos) = rem as u8 + 1;
core::ptr::copy_nonoverlapping(
data.as_ptr().add(input_pos),
encoded.as_mut_ptr().add(output_pos + 1),
rem,
);
output_pos += rem + 1;
}
return output_pos;
}
}
}
}
#[inline]
pub fn decode(data: &[u8], decoded: &mut [u8]) -> Result<usize, DecodeError> {
if data.is_empty() {
return Err(DecodeError::EmptyInput);
}
if data.len() > 1 {
let want = decode_buffer(data.len());
if decoded.len() < want {
return Err(DecodeError::BufferTooSmall {
have: decoded.len(),
want,
});
}
}
unsafe { decode_unsafe(data, decoded) }
}
#[inline]
pub unsafe fn decode_unsafe(data: &[u8], decoded: &mut [u8]) -> Result<usize, DecodeError> {
if data.is_empty() {
return Err(DecodeError::EmptyInput);
}
if data.len() == 1 && data[0] == 0x01 {
return Ok(0);
}
debug_assert!(decoded.len() >= decode_buffer(data.len()));
if memchr::memchr(0, data).is_some() {
return decode_scalar(data, decoded);
}
decode_chunked::<false>(data, decoded)
}
#[inline]
pub fn decode_nonzero(data: &[u8], decoded: &mut [u8]) -> Result<usize, DecodeError> {
if data.is_empty() {
return Err(DecodeError::EmptyInput);
}
if data.len() > 1 {
let want = decode_buffer(data.len());
if decoded.len() < want {
return Err(DecodeError::BufferTooSmall {
have: decoded.len(),
want,
});
}
}
unsafe { decode_nonzero_unsafe(data, decoded) }
}
#[inline]
pub unsafe fn decode_nonzero_unsafe(data: &[u8], decoded: &mut [u8]) -> Result<usize, DecodeError> {
if data.is_empty() {
return Err(DecodeError::EmptyInput);
}
if data.len() == 1 && data[0] == 0x01 {
return Ok(0);
}
debug_assert!(decoded.len() >= decode_buffer(data.len()));
decode_chunked::<true>(data, decoded)
}
#[inline]
fn decode_chunked<const CHECKED: bool>(
data: &[u8],
decoded: &mut [u8],
) -> Result<usize, DecodeError> {
unsafe {
let mut input_pos = 0usize;
let mut output_pos = 0usize;
while input_pos + 255 < data.len() {
let marker = *data.get_unchecked(input_pos);
if CHECKED && marker == 0 {
return Err(DecodeError::ZeroMarker { at: input_pos });
}
let chunk = marker as usize - 1;
input_pos += 1;
core::ptr::copy_nonoverlapping(
data.as_ptr().add(input_pos),
decoded.as_mut_ptr().add(output_pos),
16,
);
if chunk > 16 {
core::ptr::copy_nonoverlapping(
data.as_ptr().add(input_pos),
decoded.as_mut_ptr().add(output_pos),
254,
);
}
input_pos += chunk;
output_pos += chunk;
*decoded.get_unchecked_mut(output_pos) = 0;
output_pos += (marker != 0xff) as usize;
}
loop {
let marker = *data.get_unchecked(input_pos);
if CHECKED && marker == 0 {
return Err(DecodeError::ZeroMarker { at: input_pos });
}
let chunk = marker as usize - 1;
input_pos += 1;
if input_pos + chunk > data.len() {
return Err(DecodeError::ChunkOverflow {
at: input_pos - 1,
marker,
len: data.len(),
});
}
core::ptr::copy_nonoverlapping(
data.as_ptr().add(input_pos),
decoded.as_mut_ptr().add(output_pos),
chunk,
);
input_pos += chunk;
output_pos += chunk;
if input_pos == data.len() {
return Ok(output_pos);
}
if marker != 0xff {
*decoded.get_unchecked_mut(output_pos) = 0;
output_pos += 1;
}
}
}
}
#[cold]
#[inline(never)]
fn decode_scalar(data: &[u8], decoded: &mut [u8]) -> Result<usize, DecodeError> {
unsafe {
let mut output_pos = 0usize;
let mut i = 0usize;
while i < data.len() {
let marker = *data.get_unchecked(i);
if marker == 0 {
return Err(DecodeError::ZeroMarker { at: i });
}
i += 1;
if i + (marker as usize) - 1 > data.len() {
return Err(DecodeError::ChunkOverflow {
at: i - 1,
marker,
len: data.len(),
});
}
for _ in 1..marker {
let b = *data.get_unchecked(i);
if b == 0 {
return Err(DecodeError::ZeroBinary { at: i });
}
*decoded.get_unchecked_mut(output_pos) = b;
output_pos += 1;
i += 1;
}
if i < data.len() && marker != 0xff {
*decoded.get_unchecked_mut(output_pos) = 0;
output_pos += 1;
}
}
Ok(output_pos)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::vec;
use std::vec::Vec;
#[test]
fn test_roundtrip_empty() {
let data = [];
let mut enc_buf = [0u8; 1];
let len = encode(&data, &mut enc_buf).unwrap();
assert_eq!(len, 1);
assert_eq!(enc_buf[0], 0x01);
let mut dec_buf = [0u8; 0];
let dec_len = decode(&enc_buf[..len], &mut dec_buf).unwrap();
assert_eq!(dec_len, 0);
}
#[test]
fn test_roundtrip_no_zeros() {
let data = [1, 2, 3, 4, 5];
let mut enc_buf = [0u8; encode_buffer(5)];
let len = encode(&data, &mut enc_buf).unwrap();
let mut dec_buf = [0u8; decode_buffer(encode_buffer(5))];
let dec_len = decode(&enc_buf[..len], &mut dec_buf).unwrap();
assert_eq!(&dec_buf[..dec_len], &data);
}
#[test]
fn test_roundtrip_with_zeros() {
let data = [0, 1, 0, 2, 0, 0, 3];
let mut enc_buf = [0u8; encode_buffer(7)];
let len = encode(&data, &mut enc_buf).unwrap();
let mut dec_buf = [0u8; decode_buffer(encode_buffer(7))];
let dec_len = decode(&enc_buf[..len], &mut dec_buf).unwrap();
assert_eq!(&dec_buf[..dec_len], &data);
}
#[test]
fn test_roundtrip_254_nonzero() {
let data: Vec<u8> = (1..=254).collect();
let mut enc_buf = vec![0u8; encode_buffer(254)];
let len = encode(&data, &mut enc_buf).unwrap();
let mut dec_buf = vec![0u8; decode_buffer(enc_buf.len())];
let dec_len = decode(&enc_buf[..len], &mut dec_buf).unwrap();
assert_eq!(&dec_buf[..dec_len], &data[..]);
}
#[test]
fn test_roundtrip_255_nonzero() {
let data: Vec<u8> = (1..=254).chain(std::iter::once(1)).collect();
let mut enc_buf = vec![0u8; encode_buffer(255)];
let len = encode(&data, &mut enc_buf).unwrap();
let mut dec_buf = vec![0u8; decode_buffer(enc_buf.len())];
let dec_len = decode(&enc_buf[..len], &mut dec_buf).unwrap();
assert_eq!(&dec_buf[..dec_len], &data[..]);
}
#[test]
fn test_roundtrip_chunk_boundaries() {
let sizes: Vec<usize> = if cfg!(miri) {
(0..=64)
.chain([253, 254, 255, 256, 507, 508, 509, 510, 1021, 1024])
.collect()
} else {
(0..=515)
.chain([1021, 1024, 4093, 4096, 8191, 65536])
.collect()
};
for size in sizes {
for period in [1usize, 2, 3, 253, 254, 255, 256] {
for phase in [0, period - 1] {
let data: Vec<u8> = (0..size)
.map(|i| {
if i % period == phase {
0
} else {
(i % 251 + 1) as u8
}
})
.collect();
roundtrip_reference(&data);
}
}
let data: Vec<u8> = (0..size).map(|i| (i % 251 + 1) as u8).collect();
roundtrip_reference(&data);
}
}
fn roundtrip_reference(data: &[u8]) {
let mut encoded = vec![0u8; encode_buffer(data.len())];
let encoded_len = encode(data, &mut encoded).unwrap();
let mut reference = vec![0u8; cobs::max_encoding_length(data.len())];
let reference_len = cobs::encode(data, &mut reference);
assert_eq!(&encoded[..encoded_len], &reference[..reference_len]);
let mut decoded = vec![0u8; decode_buffer(encoded_len)];
let decoded_len = decode(&encoded[..encoded_len], &mut decoded).unwrap();
assert_eq!(&decoded[..decoded_len], data);
let mut nonzero = vec![0u8; decode_buffer(encoded_len)];
let nonzero_len = decode_nonzero(&encoded[..encoded_len], &mut nonzero).unwrap();
assert_eq!(&nonzero[..nonzero_len], data);
}
#[test]
fn test_decode_malformed() {
let mut buffer = [0u8; 16];
assert_eq!(decode(&[], &mut buffer), Err(DecodeError::EmptyInput));
assert_eq!(
decode(&[0x00], &mut buffer),
Err(DecodeError::ZeroMarker { at: 0 })
);
assert_eq!(
decode(&[0x02, 0x41, 0x00], &mut buffer),
Err(DecodeError::ZeroMarker { at: 2 })
);
assert_eq!(
decode(&[0x02, 0x00], &mut buffer),
Err(DecodeError::ZeroBinary { at: 1 })
);
assert_eq!(
decode(&[0x03, 0x41, 0x00, 0x41], &mut buffer),
Err(DecodeError::ZeroBinary { at: 2 })
);
assert_eq!(
decode(&[0x03, 0x41], &mut buffer),
Err(DecodeError::ChunkOverflow {
at: 0,
marker: 3,
len: 2
})
);
assert_eq!(
decode(&[0x05, 0x41, 0x00, 0x41], &mut buffer),
Err(DecodeError::ChunkOverflow {
at: 0,
marker: 5,
len: 4
})
);
}
#[test]
fn test_decode_nonzero_malformed() {
let mut buffer = [0u8; 128];
assert_eq!(
decode_nonzero(&[], &mut buffer),
Err(DecodeError::EmptyInput)
);
assert_eq!(
decode_nonzero(&[0x03, 0x41], &mut buffer),
Err(DecodeError::ChunkOverflow {
at: 0,
marker: 3,
len: 2
})
);
let mut long = Vec::new();
for _ in 0..26 {
long.extend_from_slice(&[0x03, 0x41, 0x42]);
}
long.extend_from_slice(&[0x05, 0x41]);
assert_eq!(
decode_nonzero(&long, &mut buffer),
decode(&long, &mut [0u8; 128])
);
long[40] = 0;
let _ = decode_nonzero(&long, &mut buffer);
}
#[test]
fn test_buffer_too_small() {
let mut buffer = [0u8; 2];
assert_eq!(
encode(&[1, 2, 3], &mut buffer),
Err(EncodeError::BufferTooSmall {
have: 2,
want: encode_buffer(3)
})
);
assert_eq!(
decode(&[0x02, 0x41, 0x02, 0x42], &mut buffer),
Err(DecodeError::BufferTooSmall { have: 2, want: 3 })
);
}
}