1use serde::{Deserialize, Serialize};
10
11pub const BASE: u8 = 0x7F;
13
14const STX: u8 = 0x02;
15const ETX: u8 = 0x03;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
26#[serde(rename_all = "snake_case")]
27pub enum LrcMode {
28 Stx,
30 #[default]
32 Std,
33 Noext,
35 StxNoext,
37}
38
39impl LrcMode {
40 #[must_use]
48 pub fn compute(self, payload: &[u8]) -> u8 {
49 let mut lrc = BASE;
50 if matches!(self, LrcMode::Stx | LrcMode::StxNoext) {
51 lrc ^= STX;
52 }
53 for &b in payload {
54 lrc ^= b;
55 }
56 if matches!(self, LrcMode::Stx | LrcMode::Noext) {
57 lrc ^= ETX;
58 }
59 lrc
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 fn reference(payload: &[u8], mode: LrcMode) -> u8 {
70 let mut lrc = 0x7Fu8;
71 if mode == LrcMode::Stx || mode == LrcMode::StxNoext {
72 lrc ^= 0x02;
73 }
74 for &b in payload {
75 lrc ^= b;
76 }
77 if mode == LrcMode::Stx || mode == LrcMode::Noext {
78 lrc ^= 0x03;
79 }
80 lrc
81 }
82
83 #[test]
84 fn empty_payload_std_is_base() {
85 assert_eq!(LrcMode::Std.compute(&[]), BASE);
86 }
87
88 #[test]
89 fn empty_payload_stx_folds_stx_and_etx() {
90 assert_eq!(LrcMode::Stx.compute(&[]), 0x7E);
92 }
93
94 #[test]
95 fn known_vector_all_modes() {
96 let payload = *b"A"; assert_eq!(LrcMode::Std.compute(&payload), 0x3E);
98 assert_eq!(LrcMode::Stx.compute(&payload), 0x3F);
99 assert_eq!(LrcMode::Noext.compute(&payload), 0x3D);
100 assert_eq!(LrcMode::StxNoext.compute(&payload), 0x3C);
101 }
102
103 #[test]
104 fn matches_reference_for_every_mode() {
105 let payload = [0x00, 0x7F, 0x55, 0xAA, b'Z', 0x10];
106 for mode in [
107 LrcMode::Stx,
108 LrcMode::Std,
109 LrcMode::Noext,
110 LrcMode::StxNoext,
111 ] {
112 assert_eq!(mode.compute(&payload), reference(&payload, mode));
113 }
114 }
115
116 #[test]
117 fn str_and_bytes_agree() {
118 let payload = "12345678P0";
119 for mode in [
120 LrcMode::Stx,
121 LrcMode::Std,
122 LrcMode::Noext,
123 LrcMode::StxNoext,
124 ] {
125 assert_eq!(
126 mode.compute(payload.as_bytes()),
127 mode.compute(&payload.bytes().collect::<Vec<_>>())
128 );
129 }
130 }
131
132 #[test]
133 fn serde_snake_case_round_trip() {
134 assert_eq!(
135 serde_json::to_string(&LrcMode::StxNoext).unwrap(),
136 "\"stx_noext\""
137 );
138 assert_eq!(serde_json::to_string(&LrcMode::Std).unwrap(), "\"std\"");
139 let m: LrcMode = serde_json::from_str("\"noext\"").unwrap();
140 assert_eq!(m, LrcMode::Noext);
141 }
142}