use serde::{Deserialize, Serialize};
pub const BASE: u8 = 0x7F;
const STX: u8 = 0x02;
const ETX: u8 = 0x03;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum LrcMode {
Stx,
#[default]
Std,
Noext,
StxNoext,
}
impl LrcMode {
#[must_use]
pub fn compute(self, payload: &[u8]) -> u8 {
let mut lrc = BASE;
if matches!(self, LrcMode::Stx | LrcMode::StxNoext) {
lrc ^= STX;
}
for &b in payload {
lrc ^= b;
}
if matches!(self, LrcMode::Stx | LrcMode::Noext) {
lrc ^= ETX;
}
lrc
}
}
#[cfg(test)]
mod tests {
use super::*;
fn reference(payload: &[u8], mode: LrcMode) -> u8 {
let mut lrc = 0x7Fu8;
if mode == LrcMode::Stx || mode == LrcMode::StxNoext {
lrc ^= 0x02;
}
for &b in payload {
lrc ^= b;
}
if mode == LrcMode::Stx || mode == LrcMode::Noext {
lrc ^= 0x03;
}
lrc
}
#[test]
fn empty_payload_std_is_base() {
assert_eq!(LrcMode::Std.compute(&[]), BASE);
}
#[test]
fn empty_payload_stx_folds_stx_and_etx() {
assert_eq!(LrcMode::Stx.compute(&[]), 0x7E);
}
#[test]
fn known_vector_all_modes() {
let payload = *b"A"; assert_eq!(LrcMode::Std.compute(&payload), 0x3E);
assert_eq!(LrcMode::Stx.compute(&payload), 0x3F);
assert_eq!(LrcMode::Noext.compute(&payload), 0x3D);
assert_eq!(LrcMode::StxNoext.compute(&payload), 0x3C);
}
#[test]
fn matches_reference_for_every_mode() {
let payload = [0x00, 0x7F, 0x55, 0xAA, b'Z', 0x10];
for mode in [
LrcMode::Stx,
LrcMode::Std,
LrcMode::Noext,
LrcMode::StxNoext,
] {
assert_eq!(mode.compute(&payload), reference(&payload, mode));
}
}
#[test]
fn str_and_bytes_agree() {
let payload = "12345678P0";
for mode in [
LrcMode::Stx,
LrcMode::Std,
LrcMode::Noext,
LrcMode::StxNoext,
] {
assert_eq!(
mode.compute(payload.as_bytes()),
mode.compute(&payload.bytes().collect::<Vec<_>>())
);
}
}
#[test]
fn serde_snake_case_round_trip() {
assert_eq!(
serde_json::to_string(&LrcMode::StxNoext).unwrap(),
"\"stx_noext\""
);
assert_eq!(serde_json::to_string(&LrcMode::Std).unwrap(), "\"std\"");
let m: LrcMode = serde_json::from_str("\"noext\"").unwrap();
assert_eq!(m, LrcMode::Noext);
}
}