genicp/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2//! GenCP: generic control protocol encode/decode (transport-agnostic).
3
4use bitflags::bitflags;
5use bytes::{Buf, BufMut, Bytes, BytesMut};
6use thiserror::Error;
7
8/// Size of the GenCP header (in bytes).
9pub const HEADER_SIZE: usize = 8;
10
11bitflags! {
12    /// Flags that can be set on a GenCP command packet.
13    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
14    pub struct CommandFlags: u16 {
15        /// Request an acknowledgement for this command.
16        const ACK_REQUIRED = 0x0001;
17        /// Mark the command as a broadcast.
18        const BROADCAST = 0x8000;
19    }
20}
21
22/// GenCP operation codes supported by this crate.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum OpCode {
25    /// Read a block of memory from the device.
26    ReadMem,
27    /// Write a block of memory to the device.
28    WriteMem,
29}
30
31impl OpCode {
32    /// Raw command value as defined by the GenCP/GVCP specification.
33    pub const fn command_code(self) -> u16 {
34        match self {
35            OpCode::ReadMem => 0x0084,
36            OpCode::WriteMem => 0x0086,
37        }
38    }
39
40    /// Raw acknowledgement value as defined by the specification.
41    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/// Status codes returned by GenCP acknowledgements.
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum StatusCode {
66    /// Command completed successfully.
67    Success,
68    /// The requested command is not implemented by the device.
69    NotImplemented,
70    /// One of the command parameters was invalid.
71    InvalidParameter,
72    /// The requested address range cannot be accessed.
73    InvalidAddress,
74    /// The device was busy processing a previous command.
75    DeviceBusy,
76    /// The device reported a generic or transport specific error.
77    Error,
78    /// A status code not known to this implementation.
79    Unknown(u16),
80}
81
82impl StatusCode {
83    /// Convert from the raw status field in an acknowledgement header.
84    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    /// Convert to the raw value stored in the packet header.
97    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/// Errors that can occur when dealing with GenCP packets.
111#[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/// Command header for GenCP requests.
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub struct CommandHeader {
124    /// Request flags (ack required, broadcast, …).
125    pub flags: CommandFlags,
126    /// Operation code for the request.
127    pub opcode: OpCode,
128    /// Length of the payload in bytes.
129    pub length: u16,
130    /// Request identifier chosen by the client.
131    pub request_id: u16,
132}
133
134/// Header for GenCP acknowledgements.
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub struct AckHeader {
137    /// Status returned by the device.
138    pub status: StatusCode,
139    /// Operation code associated with the acknowledgement.
140    pub opcode: OpCode,
141    /// Length of the payload in bytes.
142    pub length: u16,
143    /// Request identifier that this acknowledgement answers.
144    pub request_id: u16,
145}
146
147/// GenCP command packet.
148#[derive(Debug, Clone)]
149pub struct GenCpCmd {
150    /// Packet header fields.
151    pub header: CommandHeader,
152    /// Command payload.
153    pub payload: Bytes,
154}
155
156/// GenCP acknowledgement packet.
157#[derive(Debug, Clone)]
158pub struct GenCpAck {
159    /// Header fields returned by the device.
160    pub header: AckHeader,
161    /// Payload data (command specific).
162    pub payload: Bytes,
163}
164
165/// Encode a GenCP command into the on-the-wire representation.
166///
167/// The returned buffer is ready to be transmitted by the transport layer.
168pub 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
179/// Decode a GenCP acknowledgement from raw bytes.
180pub 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}