packet.rs (7357B)
1 //! The scarlett2 wire packet: a 16-byte little-endian header followed by an 2 //! optional payload. Both requests and responses share this layout. 3 //! 4 //! ```text 5 //! offset size field 6 //! 0 4 cmd (u32) opcode 7 //! 4 2 size (u16) payload length in bytes 8 //! 6 2 seq (u16) sequence number, +1 per request 9 //! 8 4 error (u32) device error code (0 = ok) 10 //! 12 4 pad (u32) reserved, always 0 11 //! 16 .. data payload 12 //! ``` 13 //! 14 //! Validated against a real Scarlett 18i20 3rd Gen (see the Phase-0 spike): the 15 //! device echoes `cmd`, and `seq` except during INIT where it replies `seq-1` 16 //! (the kernel resets seq to 1 for INIT_1/INIT_2 and accepts a response seq of 0). 17 18 /// Size of the fixed packet header in bytes. 19 pub const HEADER_LEN: usize = 16; 20 21 /// Error decoding a response packet. 22 #[derive(Debug, Clone, PartialEq, Eq)] 23 pub enum PacketError { 24 /// Fewer than [`HEADER_LEN`] bytes were returned. 25 TooShort { got: usize }, 26 /// The device reported a non-zero error code. 27 Device { code: u32 }, 28 /// The response opcode did not match the request. 29 CmdMismatch { sent: u32, got: u32 }, 30 /// The declared payload size exceeds the bytes actually received. 31 SizeOverrun { declared: usize, available: usize }, 32 } 33 34 impl core::fmt::Display for PacketError { 35 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 36 match self { 37 PacketError::TooShort { got } => { 38 write!(f, "response too short: {got} bytes (need {HEADER_LEN})") 39 } 40 PacketError::Device { code } => write!(f, "device error code {code:#x}"), 41 PacketError::CmdMismatch { sent, got } => { 42 write!(f, "response cmd {got:#x} != request {sent:#x}") 43 } 44 PacketError::SizeOverrun { declared, available } => write!( 45 f, 46 "declared payload {declared} > available {available}" 47 ), 48 } 49 } 50 } 51 52 impl std::error::Error for PacketError {} 53 54 /// Build a request packet: 16-byte header + `payload`. 55 pub fn encode_request(cmd: u32, seq: u16, payload: &[u8]) -> Vec<u8> { 56 let mut out = Vec::with_capacity(HEADER_LEN + payload.len()); 57 out.extend_from_slice(&cmd.to_le_bytes()); 58 out.extend_from_slice(&(payload.len() as u16).to_le_bytes()); 59 out.extend_from_slice(&seq.to_le_bytes()); 60 out.extend_from_slice(&0u32.to_le_bytes()); // error 61 out.extend_from_slice(&0u32.to_le_bytes()); // pad 62 out.extend_from_slice(payload); 63 out 64 } 65 66 /// A decoded response header plus a view of its payload. 67 #[derive(Debug, Clone, PartialEq, Eq)] 68 pub struct Response { 69 pub cmd: u32, 70 pub size: u16, 71 pub seq: u16, 72 pub error: u32, 73 pub payload: Vec<u8>, 74 } 75 76 /// Parse a raw response buffer, validating it against the request `cmd`. 77 /// 78 /// `seq` is intentionally NOT validated here: the device's seq accounting has an 79 /// init-time off-by-one (confirmed on hardware), and callers that care can check 80 /// `Response::seq` themselves. We DO enforce a zero error code and a matching cmd. 81 pub fn decode_response(cmd: u32, buf: &[u8]) -> Result<Response, PacketError> { 82 if buf.len() < HEADER_LEN { 83 return Err(PacketError::TooShort { got: buf.len() }); 84 } 85 let r_cmd = u32::from_le_bytes(buf[0..4].try_into().unwrap()); 86 let size = u16::from_le_bytes(buf[4..6].try_into().unwrap()); 87 let seq = u16::from_le_bytes(buf[6..8].try_into().unwrap()); 88 let error = u32::from_le_bytes(buf[8..12].try_into().unwrap()); 89 90 if error != 0 { 91 return Err(PacketError::Device { code: error }); 92 } 93 if r_cmd != cmd { 94 return Err(PacketError::CmdMismatch { sent: cmd, got: r_cmd }); 95 } 96 97 let available = buf.len() - HEADER_LEN; 98 let declared = size as usize; 99 if declared > available { 100 return Err(PacketError::SizeOverrun { declared, available }); 101 } 102 let payload = buf[HEADER_LEN..HEADER_LEN + declared].to_vec(); 103 104 Ok(Response { cmd: r_cmd, size, seq, error, payload }) 105 } 106 107 #[cfg(test)] 108 mod tests { 109 use super::*; 110 111 #[test] 112 fn request_header_is_little_endian_and_16_bytes() { 113 // GET_DATA with an 8-byte {offset:u32, size:u32} payload, seq 3. 114 let mut payload = Vec::new(); 115 payload.extend_from_slice(&0u32.to_le_bytes()); // offset 0 116 payload.extend_from_slice(&8u32.to_le_bytes()); // size 8 117 let req = encode_request(0x0080_0000, 3, &payload); 118 119 assert_eq!(req.len(), HEADER_LEN + 8); 120 assert_eq!(&req[0..4], &[0x00, 0x00, 0x80, 0x00]); // cmd LE 121 assert_eq!(&req[4..6], &[0x08, 0x00]); // size = 8 122 assert_eq!(&req[6..8], &[0x03, 0x00]); // seq = 3 123 assert_eq!(&req[8..12], &[0; 4]); // error 124 assert_eq!(&req[12..16], &[0; 4]); // pad 125 } 126 127 #[test] 128 fn empty_payload_request_is_just_the_header() { 129 let req = encode_request(0x0000_0000, 1, &[]); // INIT_1 130 assert_eq!(req.len(), HEADER_LEN); 131 assert_eq!(&req[4..6], &[0, 0]); // size 0 132 } 133 134 fn raw_response(cmd: u32, seq: u16, error: u32, payload: &[u8]) -> Vec<u8> { 135 let mut b = Vec::new(); 136 b.extend_from_slice(&cmd.to_le_bytes()); 137 b.extend_from_slice(&(payload.len() as u16).to_le_bytes()); 138 b.extend_from_slice(&seq.to_le_bytes()); 139 b.extend_from_slice(&error.to_le_bytes()); 140 b.extend_from_slice(&0u32.to_le_bytes()); 141 b.extend_from_slice(payload); 142 b 143 } 144 145 #[test] 146 fn decodes_real_get_sync_response() { 147 // From the spike: GET_SYNC returned payload 01 00 00 00 (locked). 148 let raw = raw_response(0x0000_6004, 1, 0, &[0x01, 0x00, 0x00, 0x00]); 149 let resp = decode_response(0x0000_6004, &raw).unwrap(); 150 assert_eq!(resp.payload, vec![0x01, 0x00, 0x00, 0x00]); 151 assert_eq!(u32::from_le_bytes(resp.payload[..4].try_into().unwrap()), 1); 152 } 153 154 #[test] 155 fn decodes_real_get_data_response() { 156 // From the spike: GET_DATA@0 returned ec 20 00 00 00 00 01 00. 157 let p = [0xec, 0x20, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00]; 158 let raw = raw_response(0x0080_0000, 2, 0, &p); 159 let resp = decode_response(0x0080_0000, &raw).unwrap(); 160 assert_eq!(resp.payload, p); 161 } 162 163 #[test] 164 fn rejects_device_error_code() { 165 let raw = raw_response(0x0080_0000, 5, 0xdead, &[]); 166 assert_eq!( 167 decode_response(0x0080_0000, &raw), 168 Err(PacketError::Device { code: 0xdead }) 169 ); 170 } 171 172 #[test] 173 fn rejects_cmd_mismatch() { 174 let raw = raw_response(0x0000_2001, 5, 0, &[]); 175 assert_eq!( 176 decode_response(0x0080_0000, &raw), 177 Err(PacketError::CmdMismatch { sent: 0x0080_0000, got: 0x0000_2001 }) 178 ); 179 } 180 181 #[test] 182 fn rejects_short_buffer() { 183 assert_eq!( 184 decode_response(0, &[0u8; 8]), 185 Err(PacketError::TooShort { got: 8 }) 186 ); 187 } 188 189 #[test] 190 fn rejects_size_overrun() { 191 // header claims 16-byte payload but only 4 bytes follow 192 let mut b = encode_request(0x10, 0, &[]); 193 b[4] = 16; // size = 16 194 b.extend_from_slice(&[0u8; 4]); 195 assert_eq!( 196 decode_response(0x10, &b), 197 Err(PacketError::SizeOverrun { declared: 16, available: 4 }) 198 ); 199 } 200 }