#[cfg(test)]
use std::io::{self, Write};
#[inline]
pub(crate) fn crc8(bytes: &[u8]) -> u8 {
crc8_seeded(0, bytes)
}
#[inline]
pub(crate) fn crc16(bytes: &[u8]) -> u16 {
crc16_seeded(0, bytes)
}
#[cfg(test)]
pub(crate) struct Crc8Writer<W: Write> {
writer: W,
crc: u8,
}
#[cfg(test)]
impl<W: Write> Crc8Writer<W> {
#[inline]
pub(crate) fn new(writer: W) -> Self {
Self { writer, crc: 0 }
}
#[inline]
pub(crate) fn with_crc(crc: u8, writer: W) -> Self {
Self { writer, crc }
}
#[inline]
fn update_crc(&mut self, bytes: &[u8]) {
self.crc = crc8_seeded(self.crc, bytes);
}
}
#[cfg(test)]
impl<W: Write> Write for Crc8Writer<W> {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let written = self.writer.write(buf)?;
self.update_crc(&buf[..written]);
Ok(written)
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
self.writer.write_all(&[self.crc])?;
self.writer.flush()
}
}
#[cfg(test)]
pub(crate) struct Crc16Writer<W: Write> {
writer: W,
crc: u16,
}
#[cfg(test)]
impl<W: Write> Crc16Writer<W> {
#[inline]
pub(crate) fn new(writer: W) -> Self {
Self { writer, crc: 0 }
}
#[inline]
pub(crate) fn with_crc(crc: u16, writer: W) -> Self {
Self { writer, crc }
}
#[inline]
fn update_crc(&mut self, bytes: &[u8]) {
self.crc = crc16_seeded(self.crc, bytes);
}
}
#[cfg(test)]
impl<W: Write> Write for Crc16Writer<W> {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let written = self.writer.write(buf)?;
self.update_crc(&buf[..written]);
Ok(written)
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
let crc = self.crc.to_be_bytes();
self.writer.write_all(&crc)?;
self.writer.flush()
}
}
#[inline]
fn crc8_seeded(mut crc: u8, bytes: &[u8]) -> u8 {
for &byte in bytes {
crc ^= byte;
for _ in 0..8 {
crc = if crc & 0x80 != 0 {
(crc << 1) ^ 0x07
} else {
crc << 1
};
}
}
crc
}
#[inline]
fn crc16_seeded(mut crc: u16, bytes: &[u8]) -> u16 {
for &byte in bytes {
crc ^= (byte as u16) << 8;
for _ in 0..8 {
crc = if crc & 0x8000 != 0 {
(crc << 1) ^ 0x8005
} else {
crc << 1
};
}
}
crc
}
#[cfg(test)]
mod tests {
use super::{Crc8Writer, Crc16Writer};
use std::io::{self, Write};
struct ShortWriter {
max_write: usize,
bytes: Vec<u8>,
}
impl ShortWriter {
fn new(max_write: usize) -> Self {
Self {
max_write,
bytes: Vec::new(),
}
}
}
impl Write for ShortWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let written = buf.len().min(self.max_write);
self.bytes.extend_from_slice(&buf[..written]);
Ok(written)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
fn assert_crc_output(input: &[u8], expected_crc: u8, chunk_sizes: &[usize]) {
let mut sink = Vec::new();
{
let mut writer = Crc8Writer::new(&mut sink);
let mut offset = 0;
for &chunk_size in chunk_sizes {
let end = offset + chunk_size;
writer.write_all(&input[offset..end]).unwrap();
offset = end;
}
assert_eq!(offset, input.len());
writer.flush().unwrap();
}
let mut expected = input.to_vec();
expected.push(expected_crc);
assert_eq!(sink, expected);
}
fn crc8_reference(mut crc: u8, bytes: &[u8]) -> u8 {
for &byte in bytes {
crc ^= byte;
for _ in 0..8 {
crc = if crc & 0x80 != 0 {
(crc << 1) ^ 0x07
} else {
crc << 1
};
}
}
crc
}
fn assert_crc16_output(input: &[u8], expected_crc: u16, chunk_sizes: &[usize]) {
let mut sink = Vec::new();
{
let mut writer = Crc16Writer::new(&mut sink);
let mut offset = 0;
for &chunk_size in chunk_sizes {
let end = offset + chunk_size;
writer.write_all(&input[offset..end]).unwrap();
offset = end;
}
assert_eq!(offset, input.len());
writer.flush().unwrap();
}
let mut expected = input.to_vec();
expected.extend_from_slice(&expected_crc.to_be_bytes());
assert_eq!(sink, expected);
}
fn crc16_reference(mut crc: u16, bytes: &[u8]) -> u16 {
for &byte in bytes {
crc ^= (byte as u16) << 8;
for _ in 0..8 {
crc = if crc & 0x8000 != 0 {
(crc << 1) ^ 0x8005
} else {
crc << 1
};
}
}
crc
}
#[test]
fn crc_matches_rfc9639_frame_header_examples() {
assert_crc_output(b"", 0x00, &[]);
let example_2_first_frame = b"\xff\xf8\x69\x98\x00\x0f";
assert_crc_output(example_2_first_frame, 0x99, &[6]);
assert_crc_output(example_2_first_frame, 0x99, &[2, 4]);
assert_crc_output(example_2_first_frame, 0x99, &[1, 1, 1, 1, 1, 1]);
let example_2_second_frame = b"\xff\xf8\x69\x18\x01\x02";
assert_crc_output(example_2_second_frame, 0xa4, &[6]);
assert_crc_output(example_2_second_frame, 0xa4, &[3, 3]);
assert_crc_output(example_2_second_frame, 0xa4, &[1, 1, 1, 1, 1, 1]);
let example_3_frame = b"\xff\xf8\x68\x02\x00\x17";
assert_crc_output(example_3_frame, 0xe9, &[6]);
assert_crc_output(example_3_frame, 0xe9, &[2, 2, 2]);
assert_crc_output(example_3_frame, 0xe9, &[1, 1, 1, 1, 1, 1]);
}
#[test]
fn write_handles_short_writes_and_accumulates_only_written_bytes() {
let input = b"\xff\xf8\x68\x02\x00\x17";
let mut sink = ShortWriter::new(2);
{
let mut writer = Crc8Writer::new(&mut sink);
let written = writer.write(input).unwrap();
assert_eq!(written, 2);
writer.write_all(&input[written..]).unwrap();
writer.flush().unwrap();
}
let mut expected = input.to_vec();
expected.push(0xe9);
assert_eq!(sink.bytes, expected);
}
#[test]
fn with_crc_resumes_from_previous_state() {
let prefix = b"\xff\xf8\x68";
let suffix = b"\x02\x00\x17";
let mut prefix_sink = Vec::new();
let seed = {
let mut writer = Crc8Writer::new(&mut prefix_sink);
writer.write_all(prefix).unwrap();
writer.crc
};
let mut sink = Vec::new();
{
let mut writer = Crc8Writer::with_crc(seed, &mut sink);
writer.write_all(suffix).unwrap();
writer.flush().unwrap();
}
assert_eq!(prefix_sink, prefix.to_vec());
let mut expected = suffix.to_vec();
expected.push(crc8_reference(seed, suffix));
assert_eq!(sink, expected);
}
#[test]
fn crc16_matches_rfc9639_frame_footer_examples() {
assert_crc16_output(b"", 0x0000, &[]);
let example_1_frame = b"\xff\xf8\x69\x18\x00\x00\xbf\x03\x58\xfd\x03\x12\x8b";
assert_crc16_output(example_1_frame, 0xaa9a, &[13]);
assert_crc16_output(example_1_frame, 0xaa9a, &[2, 11]);
assert_crc16_output(
example_1_frame,
0xaa9a,
&[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
);
let example_3_frame =
b"\xff\xf8\x68\x02\x00\x17\xe9\x44\x00\x4f\x6f\x31\x3d\x10\x47\xd2\x27\xcb\x6d\x09\x08\x31\x45\x2b\xdc\x28\x22\x22\x80";
assert_crc16_output(example_3_frame, 0x57a3, &[29]);
assert_crc16_output(example_3_frame, 0x57a3, &[4, 8, 17]);
assert_crc16_output(
example_3_frame,
0x57a3,
&[
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1,
],
);
}
#[test]
fn crc16_write_handles_short_writes_and_accumulates_only_written_bytes() {
let input = b"\xff\xf8\x68\x02\x00\x17";
let mut sink = ShortWriter::new(2);
{
let mut writer = Crc16Writer::new(&mut sink);
let written = writer.write(input).unwrap();
assert_eq!(written, 2);
writer.write_all(&input[written..]).unwrap();
writer.flush().unwrap();
}
let mut expected = input.to_vec();
expected.extend_from_slice(&crc16_reference(0, input).to_be_bytes());
assert_eq!(sink.bytes, expected);
}
#[test]
fn crc16_with_crc_resumes_from_previous_state() {
let prefix = b"\xff\xf8\x69\x18\x00\x00";
let suffix = b"\xbf\x03\x58\xfd\x03\x12\x8b";
let mut prefix_sink = Vec::new();
let seed = {
let mut writer = Crc16Writer::new(&mut prefix_sink);
writer.write_all(prefix).unwrap();
writer.crc
};
let mut sink = Vec::new();
{
let mut writer = Crc16Writer::with_crc(seed, &mut sink);
writer.write_all(suffix).unwrap();
writer.flush().unwrap();
}
assert_eq!(prefix_sink, prefix.to_vec());
let mut expected = suffix.to_vec();
expected.extend_from_slice(&crc16_reference(seed, suffix).to_be_bytes());
assert_eq!(sink, expected);
}
}