1#![cfg_attr(docsrs, feature(doc_cfg))]
2use bitflags::bitflags;
5use bytes::{Buf, BufMut, Bytes, BytesMut};
6use thiserror::Error;
7
8pub const HEADER_SIZE: usize = 8;
10
11bitflags! {
12 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
14 pub struct CommandFlags: u16 {
15 const ACK_REQUIRED = 0x0001;
17 const BROADCAST = 0x8000;
19 }
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum OpCode {
25 ReadMem,
27 WriteMem,
29}
30
31impl OpCode {
32 pub const fn command_code(self) -> u16 {
34 match self {
35 OpCode::ReadMem => 0x0084,
36 OpCode::WriteMem => 0x0086,
37 }
38 }
39
40 pub const fn ack_code(self) -> u16 {
42 self.command_code() + 1
43 }
44
45 #[allow(dead_code)]
46 fn from_command(code: u16) -> Result<Self, GenCpError> {
47 match code {
48 0x0084 => Ok(OpCode::ReadMem),
49 0x0086 => Ok(OpCode::WriteMem),
50 _ => Err(GenCpError::UnknownOpcode(code)),
51 }
52 }
53
54 fn from_ack(code: u16) -> Result<Self, GenCpError> {
55 match code {
56 0x0085 => Ok(OpCode::ReadMem),
57 0x0087 => Ok(OpCode::WriteMem),
58 _ => Err(GenCpError::UnknownOpcode(code)),
59 }
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum StatusCode {
66 Success,
68 NotImplemented,
70 InvalidParameter,
72 InvalidAddress,
74 DeviceBusy,
76 Error,
78 Unknown(u16),
80}
81
82impl StatusCode {
83 pub fn from_raw(raw: u16) -> Self {
85 match raw {
86 0x0000 => StatusCode::Success,
87 0x8001 => StatusCode::NotImplemented,
88 0x8002 => StatusCode::InvalidParameter,
89 0x8003 => StatusCode::InvalidAddress,
90 0x8004 => StatusCode::DeviceBusy,
91 0x8005 => StatusCode::Error,
92 other => StatusCode::Unknown(other),
93 }
94 }
95
96 pub const fn to_raw(self) -> u16 {
98 match self {
99 StatusCode::Success => 0x0000,
100 StatusCode::NotImplemented => 0x8001,
101 StatusCode::InvalidParameter => 0x8002,
102 StatusCode::InvalidAddress => 0x8003,
103 StatusCode::DeviceBusy => 0x8004,
104 StatusCode::Error => 0x8005,
105 StatusCode::Unknown(code) => code,
106 }
107 }
108}
109
110#[derive(Debug, Error)]
112pub enum GenCpError {
113 #[error("invalid packet: {0}")]
114 InvalidPacket(&'static str),
115 #[error("unknown opcode: {0:#06x}")]
116 UnknownOpcode(u16),
117 #[error("io: {0}")]
118 Io(#[from] std::io::Error),
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub struct CommandHeader {
124 pub flags: CommandFlags,
126 pub opcode: OpCode,
128 pub length: u16,
130 pub request_id: u16,
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub struct AckHeader {
137 pub status: StatusCode,
139 pub opcode: OpCode,
141 pub length: u16,
143 pub request_id: u16,
145}
146
147#[derive(Debug, Clone)]
149pub struct GenCpCmd {
150 pub header: CommandHeader,
152 pub payload: Bytes,
154}
155
156#[derive(Debug, Clone)]
158pub struct GenCpAck {
159 pub header: AckHeader,
161 pub payload: Bytes,
163}
164
165pub fn encode_cmd(cmd: &GenCpCmd) -> Bytes {
169 debug_assert_eq!(cmd.header.length as usize, cmd.payload.len());
170 let mut buffer = BytesMut::with_capacity(HEADER_SIZE + cmd.payload.len());
171 buffer.put_u16(cmd.header.flags.bits());
172 buffer.put_u16(cmd.header.opcode.command_code());
173 buffer.put_u16(cmd.header.length);
174 buffer.put_u16(cmd.header.request_id);
175 buffer.extend_from_slice(&cmd.payload);
176 buffer.freeze()
177}
178
179pub fn decode_ack(buf: &[u8]) -> Result<GenCpAck, GenCpError> {
181 if buf.len() < HEADER_SIZE {
182 return Err(GenCpError::InvalidPacket("too short"));
183 }
184 let mut cursor = buf;
185 let status_raw = cursor.get_u16();
186 let opcode_raw = cursor.get_u16();
187 let length = cursor.get_u16();
188 let request_id = cursor.get_u16();
189
190 let expected = HEADER_SIZE + length as usize;
191 if buf.len() != expected {
192 return Err(GenCpError::InvalidPacket("length mismatch"));
193 }
194
195 let opcode = OpCode::from_ack(opcode_raw)?;
196 let status = StatusCode::from_raw(status_raw);
197
198 let payload = Bytes::copy_from_slice(&buf[HEADER_SIZE..]);
199 Ok(GenCpAck {
200 header: AckHeader {
201 status,
202 opcode,
203 length,
204 request_id,
205 },
206 payload,
207 })
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn encode_read_mem_roundtrip() {
216 let payload = {
217 let mut p = BytesMut::with_capacity(12);
218 p.put_u64(0x0010_0200);
219 p.put_u32(64);
220 p.freeze()
221 };
222 let cmd = GenCpCmd {
223 header: CommandHeader {
224 flags: CommandFlags::ACK_REQUIRED,
225 opcode: OpCode::ReadMem,
226 length: payload.len() as u16,
227 request_id: 0x42,
228 },
229 payload,
230 };
231
232 let encoded = encode_cmd(&cmd);
233 assert_eq!(
234 &encoded[..2],
235 &CommandFlags::ACK_REQUIRED.bits().to_be_bytes()
236 );
237 assert_eq!(&encoded[2..4], &0x0084u16.to_be_bytes());
238 assert_eq!(&encoded[4..6], &(cmd.payload.len() as u16).to_be_bytes());
239 assert_eq!(&encoded[6..8], &0x0042u16.to_be_bytes());
240 assert_eq!(&encoded[8..], &cmd.payload[..]);
241 }
242
243 #[test]
244 fn decode_read_mem_ack() {
245 let payload = vec![0xAA; 4];
246 let mut buf = BytesMut::with_capacity(HEADER_SIZE + payload.len());
247 buf.put_u16(0x0000);
248 buf.put_u16(0x0085);
249 buf.put_u16(payload.len() as u16);
250 buf.put_u16(0x4242);
251 buf.extend_from_slice(&payload);
252
253 let ack = decode_ack(&buf).expect("decode");
254 assert_eq!(ack.header.status, StatusCode::Success);
255 assert_eq!(ack.header.opcode, OpCode::ReadMem);
256 assert_eq!(ack.header.length as usize, payload.len());
257 assert_eq!(ack.header.request_id, 0x4242);
258 assert_eq!(&ack.payload[..], &payload[..]);
259 }
260
261 #[test]
262 fn decode_write_mem_ack() {
263 let payload: Vec<u8> = Vec::new();
264 let mut buf = BytesMut::with_capacity(HEADER_SIZE + payload.len());
265 buf.put_u16(0x0000);
266 buf.put_u16(0x0087);
267 buf.put_u16(0);
268 buf.put_u16(0x1001);
269 let ack = decode_ack(&buf).expect("decode");
270 assert_eq!(ack.header.opcode, OpCode::WriteMem);
271 assert_eq!(ack.header.status, StatusCode::Success);
272 assert_eq!(ack.payload.len(), 0);
273 }
274}