vex_cdc/cdc2/
mod.rs

1//! Extended CDC packets.
2
3use core::fmt::Debug;
4use thiserror::Error;
5
6use crate::{
7    COMMAND_HEADER, REPLY_HEADER,
8    crc::{VEX_CRC16, crc16},
9    decode::{Decode, DecodeError, DecodeErrorKind},
10    encode::{Encode, MessageEncoder},
11    varint::VarU16,
12};
13
14pub mod controller;
15pub mod factory;
16pub mod file;
17pub mod system;
18
19/// CDC2 packet opcodes.
20pub mod ecmds {
21    // internal filesystem operations
22    pub const FILE_CTRL: u8 = 0x10;
23    pub const FILE_INIT: u8 = 0x11;
24    pub const FILE_EXIT: u8 = 0x12;
25    pub const FILE_WRITE: u8 = 0x13;
26    pub const FILE_READ: u8 = 0x14;
27    pub const FILE_LINK: u8 = 0x15;
28    pub const FILE_DIR: u8 = 0x16;
29    pub const FILE_DIR_ENTRY: u8 = 0x17;
30    pub const FILE_LOAD: u8 = 0x18;
31    pub const FILE_GET_INFO: u8 = 0x19;
32    pub const FILE_SET_INFO: u8 = 0x1A;
33    pub const FILE_ERASE: u8 = 0x1B;
34    pub const FILE_USER_STAT: u8 = 0x1C;
35    pub const FILE_VISUALIZE: u8 = 0x1D;
36    pub const FILE_CLEANUP: u8 = 0x1E;
37    pub const FILE_FORMAT: u8 = 0x1F;
38
39    // system
40    pub const SYS_FLAGS: u8 = 0x20;
41    pub const DEV_STATUS: u8 = 0x21;
42    pub const SYS_STATUS: u8 = 0x22;
43    pub const FDT_STATUS: u8 = 0x23;
44    pub const LOG_STATUS: u8 = 0x24;
45    pub const LOG_READ: u8 = 0x25;
46    pub const RADIO_STATUS: u8 = 0x26;
47    pub const USER_READ: u8 = 0x27;
48    pub const SYS_SCREEN_CAP: u8 = 0x28;
49    pub const SYS_USER_PROG: u8 = 0x29;
50    pub const SYS_DASH_TOUCH: u8 = 0x2A;
51    pub const SYS_DASH_SEL: u8 = 0x2B;
52    pub const SYS_DASH_EBL: u8 = 0x2C;
53    pub const SYS_DASH_DIS: u8 = 0x2D;
54    pub const SYS_KV_LOAD: u8 = 0x2E;
55    pub const SYS_KV_SAVE: u8 = 0x2F;
56
57    // catalog
58    pub const SYS_C_INFO_14: u8 = 0x31;
59    pub const SYS_C_INFO_58: u8 = 0x32;
60
61    // controller - only works over wired a controller connection
62    pub const CON_RADIO_INFO: u8 = 0x35;
63    pub const CON_VER_FLASH: u8 = 0x39;
64    pub const CON_RADIO_MODE: u8 = 0x41;
65    pub const CON_VER_EXPECT: u8 = 0x49;
66    pub const CON_FLASH_ERASE: u8 = 0x3B;
67    pub const CON_FLASH_WRITE: u8 = 0x3C;
68    pub const CON_FLASH_VALIDATE: u8 = 0x3E;
69    pub const CON_RADIO_FORCE: u8 = 0x3F;
70    pub const CON_COMP_CTRL: u8 = 0xC1;
71
72    // be careful!!
73    pub const FACTORY_STATUS: u8 = 0xF1;
74    pub const FACTORY_RESET: u8 = 0xF2;
75    pub const FACTORY_PING: u8 = 0xF4;
76    pub const FACTORY_PONG: u8 = 0xF5;
77    pub const FACTORY_HW_STATUS: u8 = 0xF9;
78    pub const FACTORY_CHAL: u8 = 0xFC;
79    pub const FACTORY_RESP: u8 = 0xFD;
80    pub const FACTORY_SPECIAL: u8 = 0xFE;
81    pub const FACTORY_EBL: u8 = 0xFF;
82}
83
84/// CDC2 Packet Acknowledgement Codes
85///
86/// Encoded as part of a [`Cdc2ReplyPacket`], this type sends either an acknowledgement ([`Cdc2Ack::Ack`])
87/// indicating that the command was successful or negative-acknowledgement ("NACK") if the command
88/// failed in some way.
89#[derive(Debug, Clone, Copy, Eq, PartialEq, Error)]
90#[repr(u8)]
91pub enum Cdc2Ack {
92    /// Acknowledges that a command has been processed successfully.
93    #[error("Packet was recieved successfully. (NACK 0x76)")]
94    Ack = 0x76,
95
96    /// A general negative-acknowledgement (NACK) that is sometimes received.
97    #[error("V5 device sent back a general negative-acknowledgement. (NACK 0xFF)")]
98    Nack = 0xFF,
99
100    /// Returned by the brain when a CDC2 packet's CRC Checksum does not validate.
101    #[error("Packet CRC checksum did not validate. (NACK 0xCE)")]
102    NackPacketCrc = 0xCE,
103
104    /// Returned by the brain when a packet's payload is of unexpected length (too short or too long).
105    #[error("Packet payload length was either too short or too long. (NACK 0xD0)")]
106    NackPacketLength = 0xD0,
107
108    /// Returned by the brain when we attempt to transfer too much data.
109    #[error("Attempted to transfer too much data. (NACK 0xD1)")]
110    NackTransferSize = 0xD1,
111
112    /// Returned by the brain when a program's CRC checksum fails.
113    #[error("Program CRC checksum did not validate. (NACK 0xD2)")]
114    NackProgramCrc = 0xD2,
115
116    /// Returned by the brain when there is an error with the program file.
117    #[error("Invalid program file. (NACK 0xD3)")]
118    NackProgramFile = 0xD3,
119
120    /// Returned by the brain when we fail to initialize a file transfer before beginning file operations.
121    #[error(
122        "Attempted to perform a file transfer operation before one was initialized. (NACK 0xD4)"
123    )]
124    NackUninitializedTransfer = 0xD4,
125
126    /// Returned by the brain when we initialize a file transfer incorrectly.
127    #[error("File transfer was initialized incorrectly. (NACK 0xD5)")]
128    NackInvalidInitialization = 0xD5,
129
130    /// Returned by the brain when we fail to pad a transfer to a four byte boundary.
131    #[error("File transfer was not padded to a four byte boundary. (NACK 0xD6)")]
132    NackAlignment = 0xD6,
133
134    /// Returned by the brain when the addr on a file transfer does not match
135    #[error("File transfer address did not match. (NACK 0xD7)")]
136    NackAddress = 0xD7,
137
138    /// Returned by the brain when the download length on a file transfer does not match
139    #[error("File transfer download length did not match. (NACK 0xD8)")]
140    NackIncomplete = 0xD8,
141
142    /// Returned by the brain when a file transfer attempts to access a directory that does not exist
143    #[error("Attempted to transfer file to a directory that does not exist. (NACK 0xD9)")]
144    NackNoDirectory = 0xD9,
145
146    /// Returned when the limit for user files has been reached
147    #[error("Limit for user files has been reached. (NACK 0xDA)")]
148    NackMaxUserFiles = 0xDA,
149
150    /// Returned when a file already exists and we did not specify overwrite when initializing the transfer
151    #[error("File already exists. (NACK 0xDB)")]
152    NackFileAlreadyExists = 0xDB,
153
154    /// Returned when the filesystem is full.
155    #[error("Filesystem storage is full. (NACK 0xDC)")]
156    NackFileStorageFull = 0xDC,
157
158    /// Packet timed out.
159    #[error("Packet timed out. (NACK 0x00)")]
160    Timeout = 0x00,
161
162    /// Internal Write Error.
163    #[error("Internal write error occurred. (NACK 0x01)")]
164    WriteError = 0x01,
165}
166
167impl Decode for Cdc2Ack {
168    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
169        match u8::decode(data)? {
170            0x76 => Ok(Self::Ack),
171            0xFF => Ok(Self::Nack),
172            0xCE => Ok(Self::NackPacketCrc),
173            0xD0 => Ok(Self::NackPacketLength),
174            0xD1 => Ok(Self::NackTransferSize),
175            0xD2 => Ok(Self::NackProgramCrc),
176            0xD3 => Ok(Self::NackProgramFile),
177            0xD4 => Ok(Self::NackUninitializedTransfer),
178            0xD5 => Ok(Self::NackInvalidInitialization),
179            0xD6 => Ok(Self::NackAlignment),
180            0xD7 => Ok(Self::NackAddress),
181            0xD8 => Ok(Self::NackIncomplete),
182            0xD9 => Ok(Self::NackNoDirectory),
183            0xDA => Ok(Self::NackMaxUserFiles),
184            0xDB => Ok(Self::NackFileAlreadyExists),
185            0xDC => Ok(Self::NackFileStorageFull),
186            0x00 => Ok(Self::Timeout),
187            0x01 => Ok(Self::WriteError),
188            v => Err(DecodeError::new::<Self>(DecodeErrorKind::UnexpectedByte {
189                name: "Cdc2Ack",
190                value: v,
191                expected: &[
192                    0x76, 0xFF, 0xCE, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9,
193                    0xDA, 0xDB, 0xDC, 0x00, 0x01,
194                ],
195            })),
196        }
197    }
198}
199
200/// CDC2 (Extended) Command Packet
201///
202/// This type encodes a host-to-device command packet used in the extended CDC2 protocol. The payload type `P`
203/// must implement [`Encode`].
204///
205/// All CDC2 commands have a corresponding [`Cdc2ReplyPacket`] from the device, even if the reply contains no
206/// payload. For example, a [`SystemFlagsPacket`] command corresponds to a [`SystemFlagsReplyPacket`] reply.
207/// See [`Cdc2ReplyPacket`] for more info on packet acknowledgement.
208///
209/// [`SystemFlagsPacket`]: system::SystemFlagsPacket
210/// [`SystemFlagsReplyPacket`]: system::SystemFlagsReplyPacket
211///
212/// # Encoding
213///
214/// This is an extension of the standard [`CdcCommandPacket`](crate::cdc::CdcCommandPacket) encoding that adds:
215///
216/// - An extended command opcode ([`ecmd`](ecmds)).
217/// - A CRC16 checksum covering the entire packet (including header).
218///
219/// | Field     | Size   | Description |
220/// |-----------|--------|-------------|
221/// | `header`  | 4      | Must be [`COMMAND_HEADER`]. |
222/// | `cmd`     | 1      | A [CDC command opcode](crate::cdc::cmds), typically [`USER_CDC`](crate::cdc::cmds::USER_CDC) (for a brain) or [`CON_CDC`](crate::cdc::cmds::CON_CDC) (for a controller). |
223/// | `ecmd`    | 1      | A [CDC2 extended command opcode](ecmds). |
224/// | `size`    | 1–2    | Number of remaining bytes in the packet (starting at `payload` through `crc16`), encoded as a [`VarU16`]. |
225/// | `payload` | n      | Encoded payload. |
226/// | `crc16`   | 2      | CRC16 checksum of the whole packet, computed with the [`VEX_CRC16`] algorithm. |
227
228#[derive(Debug, Clone, Copy, Eq, PartialEq)]
229pub struct Cdc2CommandPacket<const CMD: u8, const ECMD: u8, P: Encode> {
230    payload: P,
231}
232
233impl<P: Encode, const CMD: u8, const ECMD: u8> Cdc2CommandPacket<CMD, ECMD, P> {
234    pub const HEADER: [u8; 4] = COMMAND_HEADER;
235
236    /// Creates a new command packet with a payload.
237    ///
238    /// `payload` must implement the [`Encode`] trait.
239    pub fn new(payload: P) -> Self {
240        Self { payload }
241    }
242}
243
244impl<const CMD: u8, const ECMD: u8, P: Encode> Encode for Cdc2CommandPacket<CMD, ECMD, P> {
245    fn size(&self) -> usize {
246        let payload_size = self.payload.size();
247
248        8 + if payload_size > (u8::MAX >> 1) as _ {
249            2
250        } else {
251            1
252        } + payload_size
253    }
254
255    fn encode(&self, data: &mut [u8]) {
256        Self::HEADER.encode(data);
257        data[4] = CMD;
258        data[5] = ECMD;
259
260        let mut enc = MessageEncoder::new_with_position(data, 6);
261
262        // Push the payload size and encoded bytes
263        enc.write(&VarU16::new(self.payload.size() as u16));
264        enc.write(&self.payload);
265
266        // The CRC16 checksum is of the whole encoded packet, meaning we need
267        // to also include the header bytes.
268        let crc16 = VEX_CRC16.checksum(&enc.get_ref()[0..enc.position()]);
269        enc.write(&crc16.to_be_bytes());
270    }
271}
272
273/// CDC2 (Extended) Reply Packet
274///
275/// This type decodes a device-to-host reply packet used in the extended CDC2 protocol. The payload type `P` must
276/// implement [`Decode`].
277///
278/// All CDC2 replies are sent in response to a corresponding [`Cdc2CommandPacket`] from the host. For example,
279/// a [`SystemFlagsPacket`] command corresponds to a [`SystemFlagsReplyPacket`] reply.
280///
281/// [`SystemFlagsPacket`]: system::SystemFlagsPacket
282/// [`SystemFlagsReplyPacket`]: system::SystemFlagsReplyPacket
283///
284/// Reply packets encode an *acknowledgement code* in the form of a [`Cdc2Ack`], which will either acknowledge
285/// the command as valid and successful ([`Cdc2Ack::Ack`]) or return a negative acknowledgement if there was a
286/// problem processing or servicing the command.
287///
288/// # Encoding
289///
290/// This is an extension of the standard [`CdcReplyPacket`](crate::cdc::CdcReplyPacket) encoding that adds:
291///
292/// - An extended command opcode ([`ecmd`](ecmds)).
293/// - A packet acknowledgement code ([`Cdc2Ack`]).
294/// - A CRC16 checksum covering the entire packet (including header).
295///
296/// | Field     | Size   | Description |
297/// |-----------|--------|-------------|
298/// | `header`  | 2      | Must be [`REPLY_HEADER`]. |
299/// | `cmd`     | 1      | A [CDC command opcode](crate::cdc::cmds), typically [`USER_CDC`](crate::cdc::cmds::USER_CDC) (for a brain) or [`CON_CDC`](crate::cdc::cmds::CON_CDC) (for a controller). |
300/// | `size`    | 1–2    | Number of remaining bytes in the packet (starting at `ecmd` through `crc16`), encoded as a [`VarU16`]. |
301/// | `ecmd`    | 1      | A [CDC2 extended command opcode](ecmds). |
302/// | `ack`     | 1      | A [CDC2 packet acknowledgement code](Cdc2Ack). |
303/// | `payload` | n      | Encoded payload; potentially optional if a [NACK](Cdc2Ack) occurs |
304/// | `crc16`   | 2      | CRC16 checksum of the whole packet, computed with the [`VEX_CRC16`] algorithm. |
305#[derive(Clone, Copy, Eq, PartialEq)]
306pub struct Cdc2ReplyPacket<const CMD: u8, const ECMD: u8, P: Decode> {
307    /// Total payload size. This includes the size taken by the ecmd, ack, and crc fields.
308    pub size: u16,
309
310    /// Payload. Only decoded if the packet is acknowledged.
311    pub payload: Result<P, Cdc2Ack>,
312
313    /// CRC16 calculated from the entire packet contents.
314    pub crc16: u16,
315}
316
317impl<const CMD: u8, const ECMD: u8, P: Decode> Cdc2ReplyPacket<CMD, ECMD, P> {
318    pub const HEADER: [u8; 2] = REPLY_HEADER;
319
320    pub fn ack(&self) -> Cdc2Ack {
321        *self.payload.as_ref().err().unwrap_or(&Cdc2Ack::Ack)
322    }
323}
324
325impl<const CMD: u8, const ECMD: u8, P: Decode> Decode for Cdc2ReplyPacket<CMD, ECMD, P> {
326    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
327        let original_data = *data;
328
329        if <[u8; 2]>::decode(data)? != Self::HEADER {
330            return Err(DecodeError::new::<Self>(DecodeErrorKind::InvalidHeader));
331        }
332
333        let cmd = u8::decode(data)?;
334        if cmd != CMD {
335            return Err(DecodeError::new::<Self>(DecodeErrorKind::UnexpectedByte {
336                name: "cmd",
337                value: cmd,
338                expected: &[CMD],
339            }));
340        }
341
342        let payload_size = VarU16::decode(data)?;
343        let payload_size_size = payload_size.size() as usize;
344        let payload_size = payload_size.into_inner();
345
346        // Calculate the crc16 from the start of the packet up to the crc bytes.
347        //
348        // `payload_size` gives us the size of the payload including the CRC, and ecmd,
349        // so the checksum range is effectively:
350        // sizeof(header) + sizeof(cmd) + sizeof(payload_size) + payload_size - 2
351        // which is 2 + 1 + (either 2 or 1) + payload_size - 2
352        let expected_crc16 =
353            crc16::<Self>(original_data.get(0..((payload_size as usize) + payload_size_size + 1)))?;
354
355        let ecmd = u8::decode(data)?;
356        if ecmd != ECMD {
357            return Err(DecodeError::new::<Self>(DecodeErrorKind::UnexpectedByte {
358                name: "ecmd",
359                value: ecmd,
360                expected: &[ECMD],
361            }));
362        }
363
364        let ack = Cdc2Ack::decode(data)?;
365        let mut payload_data = data
366            .get(0..(payload_size as usize) - 4)
367            .ok_or_else(|| DecodeError::new::<Self>(DecodeErrorKind::UnexpectedEnd))?;
368
369        *data = &data[(payload_size as usize) - 4..];
370        let payload = if ack == Cdc2Ack::Ack {
371            Ok(P::decode(&mut payload_data)?)
372        } else {
373            Err(ack)
374        };
375
376        let crc16 = u16::decode(data)?;
377        if crc16 != expected_crc16 {
378            return Err(DecodeError::new::<Self>(DecodeErrorKind::Checksum {
379                value: crc16,
380                expected: expected_crc16,
381            }));
382        }
383
384        Ok(Self {
385            size: payload_size,
386            payload,
387            crc16,
388        })
389    }
390}