Skip to main content

skydroid_protocol/
packet.rs

1//! The transmit-side packet builder — **allocation-free**.
2//!
3//! Builds the full wire packet: `command + checksum`, encoded as UTF-8 bytes,
4//! written into a caller-provided buffer. The checksum is a bytewise sum mod
5//! 256, formatted as 2 uppercase hex chars — exactly the app's
6//! `SkydroidControl.getCrc`.
7
8use crate::command::Command;
9use crate::hex::byte2hex;
10
11/// Compute the checksum byte for a command: `(sum of all bytes) mod 256`.
12#[inline]
13pub fn checksum_byte(cmd: &[u8]) -> u8 {
14    cmd.iter().fold(0u8, |acc, &b| acc.wrapping_add(b))
15}
16
17/// Compute the 2-character uppercase-hex checksum string as ASCII bytes.
18#[inline]
19pub fn checksum_hex(cmd: &[u8]) -> [u8; 2] {
20    byte2hex(checksum_byte(cmd))
21}
22
23/// Build the full wire packet from raw command bytes: `command + checksum`,
24/// written into `out`. Returns the total bytes written, or `None` if `out`
25/// is too small (`cmd.len() + 2` needed).
26///
27/// ```
28/// use skydroid_protocol::packet::build;
29/// let mut out = [0u8; 32];
30/// let n = build(b"#TPUD2wCAP01", &mut out).unwrap();
31/// assert_eq!(n, 14);
32/// assert_eq!(&out[..12], b"#TPUD2wCAP01");
33/// ```
34pub fn build(cmd: &[u8], out: &mut [u8]) -> Option<usize> {
35    let n = cmd.len() + 2;
36    if out.len() < n {
37        return None;
38    }
39    out[..cmd.len()].copy_from_slice(cmd);
40    let h = checksum_hex(cmd);
41    out[cmd.len()] = h[0];
42    out[cmd.len() + 1] = h[1];
43    Some(n)
44}
45
46/// Build a wire packet from any [`Command`] implementor, writing into `out`.
47///
48/// ```
49/// use skydroid_protocol::packet::build_command;
50/// use skydroid_protocol::command::Record;
51/// let mut out = [0u8; 32];
52/// let n = build_command(&Record::Start, &mut out).unwrap();
53/// assert_eq!(n, 14);
54/// assert_eq!(&out[..12], b"#TPUD2wREC01");
55/// ```
56pub fn build_command<C: Command>(cmd: &C, out: &mut [u8]) -> Option<usize> {
57    let cmd_len = cmd.len();
58    let n = cmd_len + 2;
59    if out.len() < n {
60        return None;
61    }
62    cmd.write(&mut out[..cmd_len])?;
63    let h = checksum_hex(&out[..cmd_len]);
64    out[cmd_len] = h[0];
65    out[cmd_len + 1] = h[1];
66    Some(n)
67}
68
69/// Given a full packet (`cmd + 2-char checksum`), verify the checksum matches.
70pub fn verify(packet: &[u8]) -> bool {
71    if packet.len() < 3 {
72        return false;
73    }
74    let (body, tail) = packet.split_at(packet.len() - 2);
75    let h = checksum_hex(body);
76    tail == &h
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use crate::command::Record;
83
84    #[test]
85    fn checksum_byte_is_sum_mod_256() {
86        assert_eq!(checksum_byte(b""), 0);
87        assert_eq!(checksum_byte(b"A"), 0x41);
88        assert_eq!(checksum_byte(b"AB"), 0x41 + 0x42);
89        // wrap around 256
90        assert_eq!(checksum_byte(&[0xFF, 0x01]), 0x00);
91    }
92
93    #[test]
94    fn checksum_hex_is_two_hex_chars() {
95        let h = checksum_hex(b"#TPUG2wPTZ02");
96        assert_eq!(h.len(), 2);
97        // Hex digits: 0-9 or A-F; uppercase letters if a letter.
98        assert!(h.iter().all(|b| b.is_ascii_hexdigit()));
99        assert!(h
100            .iter()
101            .all(|b| b.is_ascii_digit() || b.is_ascii_uppercase()));
102    }
103
104    #[test]
105    fn build_appends_two_chars() {
106        let mut out = [0u8; 32];
107        let n = build(b"#TPUD2wCAP01", &mut out).unwrap();
108        assert_eq!(n, 14);
109        assert_eq!(&out[..12], b"#TPUD2wCAP01");
110        let h = checksum_hex(b"#TPUD2wCAP01");
111        assert_eq!(&out[12..14], &h);
112    }
113
114    #[test]
115    fn build_returns_none_if_small() {
116        let mut out = [0u8; 4];
117        assert_eq!(build(b"#TPUD2wCAP01", &mut out), None);
118    }
119
120    #[test]
121    fn build_command_uses_command_trait() {
122        let mut out = [0u8; 32];
123        let n = build_command(&Record::Start, &mut out).unwrap();
124        assert_eq!(n, 14);
125        assert_eq!(&out[..12], b"#TPUD2wREC01");
126    }
127
128    #[test]
129    fn verify_roundtrip() {
130        let mut out = [0u8; 32];
131        let n = build(b"#TPUM2wZMC01", &mut out).unwrap();
132        assert!(verify(&out[..n]));
133        // corrupt a checksum char
134        let mut bad = out;
135        bad[n - 1] ^= 0xFF;
136        assert!(!verify(&bad[..n]));
137    }
138}