use std::io;
#[derive(Debug, Default)]
pub(crate) struct Lrc {
sum: u32,
}
impl Lrc {
pub fn update(&mut self, byte: u8) {
self.sum = (self.sum + u32::from(byte)) & 0xFF;
}
pub fn reset(&mut self) {
self.sum = 0;
}
pub fn finish(&mut self) -> u32 {
let sum = ((self.sum ^ 0xFF) + 1) & 0xFF;
self.reset();
sum
}
pub fn verify(input: &[u8], hash: u32) -> bool {
let sum: u32 = input.iter().fold(0u32, |sum, b| u32::from(*b) + sum);
0 == ((sum + hash) & 0xFF)
}
}
#[derive(Debug)]
pub(crate) struct LrcWriter<W> {
hasher: Lrc,
writer: W,
}
impl<W> LrcWriter<W> {
pub fn new(writer: W) -> LrcWriter<W> {
LrcWriter {
hasher: Lrc::default(),
writer,
}
}
pub fn reset_hash(&mut self) {
self.hasher.reset();
}
pub fn finish_hash(&mut self) -> u32 {
self.hasher.finish()
}
#[cfg(test)]
pub fn into_inner(self) -> W {
self.writer
}
}
impl<W> io::Write for LrcWriter<W>
where
W: io::Write,
{
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
for byte in buf {
self.hasher.update(*byte);
}
self.writer.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.writer.flush()
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::Write as _;
#[test]
fn test_lrc() {
assert!(Lrc::verify(b"01 tools echo", 143));
assert!(!Lrc::verify(b"01 tools echo", 142));
}
#[test]
fn test_lrc_writer() {
let buf = Vec::with_capacity(80);
let writer = std::io::Cursor::new(buf);
let mut lrc_writer = LrcWriter::new(writer);
write!(&mut lrc_writer, "hello world").unwrap();
let hash = lrc_writer.finish_hash();
assert!(Lrc::verify(b"hello world", hash));
assert_eq!(
lrc_writer.into_inner().into_inner().as_slice(),
b"hello world"
);
}
#[test]
fn test_lrc_writer_reset_hash() {
let buf = Vec::with_capacity(80);
let writer = std::io::Cursor::new(buf);
let mut lrc_writer = LrcWriter::new(writer);
write!(&mut lrc_writer, "hello ").unwrap();
lrc_writer.reset_hash();
write!(&mut lrc_writer, "world").unwrap();
let hash = lrc_writer.finish_hash();
assert!(Lrc::verify(b"world", hash));
assert_eq!(
lrc_writer.into_inner().into_inner().as_slice(),
b"hello world"
);
}
}