vex_cdc/cdc2/
file.rs

1//! Internal filesystem access packets.
2
3use core::str;
4
5use alloc::vec::Vec;
6
7use crate::{
8    Decode, DecodeError, DecodeWithLength, Encode, FixedString, Version,
9    cdc::{CdcReplyPacket, cmds::USER_CDC},
10    cdc2::{
11        Cdc2Ack, Cdc2CommandPacket, Cdc2ReplyPacket,
12        ecmds::{
13            FILE_CLEANUP, FILE_CTRL, FILE_DIR, FILE_DIR_ENTRY, FILE_ERASE, FILE_EXIT, FILE_FORMAT,
14            FILE_GET_INFO, FILE_INIT, FILE_LINK, FILE_LOAD, FILE_READ, FILE_SET_INFO,
15            FILE_USER_STAT, FILE_WRITE,
16        },
17    },
18    decode::DecodeErrorKind,
19};
20
21#[derive(Debug, Clone, Copy, Eq, PartialEq)]
22#[repr(u8)]
23pub enum FileTransferOperation {
24    /// Write (upload) a file.
25    Write = 1,
26
27    /// Read (download) a file.
28    Read = 2,
29}
30
31#[repr(u8)]
32#[derive(Debug, Clone, Copy, Eq, PartialEq)]
33pub enum FileInitOption {
34    None = 0,
35    Overwrite = 1,
36}
37
38#[repr(u8)]
39#[derive(Debug, Clone, Copy, Eq, PartialEq)]
40pub enum FileTransferTarget {
41    Ddr = 0,
42    Qspi = 1,
43    Cbuf = 2,
44    Vbuf = 3,
45    Ddrc = 4,
46    Ddre = 5,
47    Flash = 6,
48    Radio = 7,
49    A1 = 13,
50    B1 = 14,
51    B2 = 15,
52}
53
54#[repr(u8)]
55#[derive(Debug, Clone, Copy, Eq, PartialEq)]
56pub enum FileVendor {
57    User = 1,
58    Sys = 15,
59    Dev1 = 16,
60    Dev2 = 24,
61    Dev3 = 32,
62    Dev4 = 40,
63    Dev5 = 48,
64    Dev6 = 56,
65    VexVm = 64,
66    Vex = 240,
67    Undefined = 241,
68}
69impl Decode for FileVendor {
70    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
71        match u8::decode(data)? {
72            1 => Ok(Self::User),
73            15 => Ok(Self::Sys),
74            16 => Ok(Self::Dev1),
75            24 => Ok(Self::Dev2),
76            32 => Ok(Self::Dev3),
77            40 => Ok(Self::Dev4),
78            48 => Ok(Self::Dev5),
79            56 => Ok(Self::Dev6),
80            64 => Ok(Self::VexVm),
81            240 => Ok(Self::Vex),
82            241 => Ok(Self::Undefined),
83            v => Err(DecodeError::new::<Self>(DecodeErrorKind::UnexpectedByte {
84                name: "FileVendor",
85                value: v,
86                expected: &[
87                    0x01, 0x0F, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x40, 0xF0, 0xF1,
88                ],
89            })),
90        }
91    }
92}
93
94#[repr(u8)]
95#[derive(Debug, Clone, Copy, Eq, PartialEq)]
96pub enum FileLoadAction {
97    Run = 0,
98    Stop = 128,
99}
100
101#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
102#[repr(u8)]
103pub enum ExtensionType {
104    /// Regular unencrypted file.
105    #[default]
106    Binary = 0x0,
107
108    /// A file which depends on a VM.
109    ///
110    /// This is the file type used for VEXCode Python bin uploads, since they need the
111    /// Python VM to function.
112    Vm = 0x61,
113
114    /// File's contents is encrypted.
115    EncryptedBinary = 0x73,
116}
117
118impl Decode for ExtensionType {
119    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
120        Ok(match u8::decode(data)? {
121            0x0 => Self::Binary,
122            0x61 => Self::Vm,
123            0x73 => Self::EncryptedBinary,
124            unknown => {
125                return Err(DecodeError::new::<Self>(DecodeErrorKind::UnexpectedByte {
126                    name: "ExtensionType",
127                    value: unknown,
128                    expected: &[0x0],
129                }));
130            }
131        })
132    }
133}
134
135#[derive(Debug, Clone, Eq, PartialEq)]
136pub struct FileMetadata {
137    pub extension: FixedString<3>,
138    pub extension_type: ExtensionType,
139    pub timestamp: i32,
140    pub version: Version,
141}
142
143impl Encode for FileMetadata {
144    fn size(&self) -> usize {
145        12
146    }
147
148    fn encode(&self, data: &mut [u8]) {
149        data[..self.extension.len()].copy_from_slice(self.extension.as_bytes());
150        data[3] = self.extension_type as _;
151        self.timestamp.encode(&mut data[4..]);
152        self.version.encode(&mut data[8..]);
153    }
154}
155
156impl Decode for FileMetadata {
157    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
158        Ok(Self {
159            // SAFETY: length is guaranteed to be less than 4.
160            extension: unsafe {
161                FixedString::new_unchecked(
162                    str::from_utf8(&<[u8; 3]>::decode(data)?)
163                        .map_err(|e| DecodeError::new::<Self>(e.into()))?,
164                )
165            },
166            extension_type: Decode::decode(data).unwrap(),
167            timestamp: i32::decode(data)?,
168            version: Version::decode(data)?,
169        })
170    }
171}
172
173/// Start uploading or downloading file from the device
174pub type FileTransferInitializePacket =
175    Cdc2CommandPacket<USER_CDC, FILE_INIT, FileTransferInitializePayload>;
176pub type FileTransferInitializeReplyPacket =
177    Cdc2ReplyPacket<USER_CDC, FILE_INIT, FileTransferInitializeReplyPayload>;
178
179#[derive(Debug, Clone, Eq, PartialEq)]
180pub struct FileTransferInitializePayload {
181    pub operation: FileTransferOperation,
182    pub target: FileTransferTarget,
183    pub vendor: FileVendor,
184    pub options: FileInitOption,
185    pub file_size: u32,
186    pub load_address: u32,
187    pub write_file_crc: u32,
188    pub metadata: FileMetadata,
189    pub file_name: FixedString<23>,
190}
191
192impl Encode for FileTransferInitializePayload {
193    fn size(&self) -> usize {
194        28 + self.file_name.size()
195    }
196
197    fn encode(&self, data: &mut [u8]) {
198        [
199            self.operation as u8,
200            self.target as u8,
201            self.vendor as u8,
202            self.options as u8,
203        ]
204        .encode(data);
205        self.file_size.encode(&mut data[4..]);
206        self.load_address.encode(&mut data[8..]);
207        self.write_file_crc.encode(&mut data[12..]);
208        self.metadata.encode(&mut data[16..]);
209        self.file_name.encode(&mut data[28..]);
210    }
211}
212
213#[derive(Debug, Clone, Copy, Eq, PartialEq)]
214pub struct FileTransferInitializeReplyPayload {
215    /// The amount of receive data (in bytes) that can be sent in every packet.
216    pub window_size: u16,
217
218    /// In read operation, the device returns the target file size (in bytes).
219    ///
220    /// In write operation, the device returns the value 3145728.
221    pub file_size: u32,
222
223    /// In read operation, the device returns the CRC value of the target file.
224    ///
225    /// In write operation, the device returns the same CRC value as the previous packets.
226    pub file_crc: u32,
227}
228
229impl Decode for FileTransferInitializeReplyPayload {
230    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
231        let window_size = u16::decode(data)?;
232        let file_size = u32::decode(data)?;
233        // Convert from big endian
234        let file_crc = u32::decode(data)?.swap_bytes();
235        Ok(Self {
236            window_size,
237            file_size,
238            file_crc,
239        })
240    }
241}
242
243/// Finish uploading or downloading file from the device
244pub type FileTransferExitPacket = Cdc2CommandPacket<USER_CDC, FILE_EXIT, FileExitAction>;
245pub type FileTransferExitReplyPacket = Cdc2ReplyPacket<USER_CDC, FILE_EXIT, ()>;
246
247/// The action to run when a file transfer is completed.
248#[repr(u8)]
249#[derive(Debug, Clone, Copy, Eq, PartialEq)]
250pub enum FileExitAction {
251    DoNothing = 0,
252    RunProgram = 1,
253    Halt = 2,
254    ShowRunScreen = 3,
255}
256impl Encode for FileExitAction {
257    fn size(&self) -> usize {
258        1
259    }
260    fn encode(&self, data: &mut [u8]) {
261        data[0] = *self as _;
262    }
263}
264/// Write to the brain
265pub type FileDataWritePacket = Cdc2CommandPacket<USER_CDC, FILE_WRITE, FileDataWritePayload>;
266pub type FileDataWriteReplyPacket = Cdc2ReplyPacket<USER_CDC, FILE_WRITE, ()>;
267
268#[derive(Debug, Clone, Eq, PartialEq)]
269pub struct FileDataWritePayload {
270    /// Memory address to write to.
271    pub address: i32,
272
273    /// A sequence of bytes to write. Must be 4-byte aligned.
274    pub chunk_data: Vec<u8>,
275}
276impl Encode for FileDataWritePayload {
277    fn size(&self) -> usize {
278        4 + self.chunk_data.len()
279    }
280
281    fn encode(&self, data: &mut [u8]) {
282        self.address.encode(data);
283        self.chunk_data.encode(&mut data[4..]);
284    }
285}
286
287/// Read from the brain
288pub type FileDataReadPacket = Cdc2CommandPacket<USER_CDC, FILE_READ, FileDataReadPayload>;
289/// Returns the file content. This packet doesn't have an ack if the data is available.
290pub type FileDataReadReplyPacket = CdcReplyPacket<USER_CDC, FileDataReadReplyPayload>;
291
292#[derive(Debug, Clone, Copy, Eq, PartialEq)]
293pub struct FileDataReadPayload {
294    /// Memory address to read from.
295    pub address: u32,
296
297    /// Number of bytes to read (4-byte aligned).
298    pub size: u16,
299}
300impl Encode for FileDataReadPayload {
301    fn size(&self) -> usize {
302        6
303    }
304
305    fn encode(&self, data: &mut [u8]) {
306        self.address.encode(data);
307        self.size.encode(&mut data[4..]);
308    }
309}
310
311#[derive(Debug, Clone, Eq, PartialEq)]
312pub enum FileDataReadReplyContents {
313    Ack { address: u32, data: Vec<u8> },
314    Nack(Cdc2Ack),
315}
316
317impl Decode for FileDataReadReplyContents {
318    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
319        if data.len() == 1 {
320            Ok(Self::Nack(Cdc2Ack::decode(data)?))
321        } else {
322            let address = u32::decode(data)?;
323            let chunk_data = Vec::decode_with_len(data, data.len())?;
324
325            Ok(Self::Ack {
326                address,
327                data: chunk_data,
328            })
329        }
330    }
331}
332
333#[derive(Debug, Clone, Eq, PartialEq)]
334pub struct FileDataReadReplyPayload {
335    pub contents: FileDataReadReplyContents,
336    pub crc: u16,
337}
338impl Decode for FileDataReadReplyPayload {
339    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
340        let ecmd = u8::decode(data)?;
341        if ecmd != FILE_READ {
342            return Err(DecodeError::new::<Self>(DecodeErrorKind::UnexpectedByte {
343                name: "ecmd",
344                value: ecmd,
345                expected: &[FILE_READ],
346            }));
347        }
348
349        let contents = FileDataReadReplyContents::decode(
350            &mut data
351                .get(..data.len() - 2)
352                .ok_or_else(|| DecodeError::new::<Self>(DecodeErrorKind::UnexpectedEnd))?,
353        )?;
354        *data = &data[data.len() - 2..];
355
356        let crc = u16::decode(data)?.swap_bytes();
357
358        Ok(Self { contents, crc })
359    }
360}
361impl FileDataReadReplyPayload {
362    pub fn unwrap(self) -> Result<(u32, Vec<u8>), Cdc2Ack> {
363        match self.contents {
364            FileDataReadReplyContents::Ack { address, data } => Ok((address, data)),
365            FileDataReadReplyContents::Nack(nack) => Err(nack),
366        }
367    }
368}
369
370/// File linking means allowing one file to be loaded after another file first (its parent).
371///
372/// This is used in PROS for the hot/cold linking.
373pub type FileLinkPacket = Cdc2CommandPacket<USER_CDC, FILE_LINK, FileLinkPayload>;
374pub type FileLinkReplyPacket = Cdc2ReplyPacket<USER_CDC, FILE_LINK, ()>;
375
376#[derive(Debug, Clone, Eq, PartialEq)]
377pub struct FileLinkPayload {
378    pub vendor: FileVendor,
379    /// 0 = default. (RESEARCH NEEDED)
380    pub reserved: u8,
381    pub required_file: FixedString<23>,
382}
383impl Encode for FileLinkPayload {
384    fn size(&self) -> usize {
385        2 + self.required_file.size()
386    }
387
388    fn encode(&self, data: &mut [u8]) {
389        data[0] = self.vendor as _;
390        data[1] = self.reserved;
391        self.required_file.encode(&mut data[2..]);
392    }
393}
394
395pub type DirectoryFileCountPacket =
396    Cdc2CommandPacket<USER_CDC, FILE_DIR, DirectoryFileCountPayload>;
397pub type DirectoryFileCountReplyPacket = Cdc2ReplyPacket<USER_CDC, FILE_DIR, u16>;
398
399#[derive(Debug, Clone, Copy, Eq, PartialEq)]
400pub struct DirectoryFileCountPayload {
401    pub vendor: FileVendor,
402    /// Unused as of VEXos 1.1.5
403    pub reserved: u8,
404}
405impl Encode for DirectoryFileCountPayload {
406    fn size(&self) -> usize {
407        2
408    }
409    fn encode(&self, data: &mut [u8]) {
410        data[0] = self.vendor as _;
411        data[1] = self.reserved;
412    }
413}
414
415pub type DirectoryEntryPacket = Cdc2CommandPacket<USER_CDC, FILE_DIR_ENTRY, DirectoryEntryPayload>;
416pub type DirectoryEntryReplyPacket =
417    Cdc2ReplyPacket<USER_CDC, FILE_DIR_ENTRY, DirectoryEntryReplyPayload>;
418
419#[derive(Debug, Clone, Copy, Eq, PartialEq)]
420pub struct DirectoryEntryPayload {
421    pub file_index: u8,
422    pub reserved: u8,
423}
424impl Encode for DirectoryEntryPayload {
425    fn size(&self) -> usize {
426        2
427    }
428
429    fn encode(&self, data: &mut [u8]) {
430        data[0] = self.file_index;
431        data[1] = self.reserved;
432    }
433}
434
435#[derive(Debug, Clone, PartialEq, Eq)]
436pub struct DirectoryEntryReplyPayload {
437    pub file_index: u8,
438    pub size: u32,
439
440    /// The storage entry address of the file.
441    pub load_address: u32,
442    pub crc: u32,
443
444    pub metadata: Option<FileMetadata>,
445    pub file_name: FixedString<23>,
446}
447
448impl Decode for DirectoryEntryReplyPayload {
449    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
450        let file_index = u8::decode(data)?;
451        let size = u32::decode(data)?;
452        let load_address = u32::decode(data)?;
453        let crc = u32::decode(data)?;
454
455        let metadata = if data.get(0) == Some(&255) {
456            let _ = <[u8; 12]>::decode(data);
457            None
458        } else {
459            Some(FileMetadata::decode(data)?)
460        };
461
462        let file_name = FixedString::<23>::decode(data)?;
463
464        Ok(Self {
465            file_index,
466            size,
467            load_address,
468            crc,
469            metadata,
470            file_name,
471        })
472    }
473}
474
475/// Run a binrary file on the brain or stop the program running on the brain.
476pub type FileLoadActionPacket = Cdc2CommandPacket<USER_CDC, FILE_LOAD, FileLoadActionPayload>;
477pub type FileLoadActionReplyPacket = Cdc2ReplyPacket<USER_CDC, FILE_LOAD, ()>;
478
479#[derive(Debug, Clone, Eq, PartialEq)]
480pub struct FileLoadActionPayload {
481    pub vendor: FileVendor,
482    pub action: FileLoadAction,
483    pub file_name: FixedString<23>,
484}
485impl Encode for FileLoadActionPayload {
486    fn size(&self) -> usize {
487        2 + self.file_name.size()
488    }
489
490    fn encode(&self, data: &mut [u8]) {
491        data[0] = self.vendor as _;
492        data[1] = self.action as _;
493        self.file_name.encode(&mut data[2..]);
494    }
495}
496pub type FileMetadataPacket = Cdc2CommandPacket<USER_CDC, FILE_GET_INFO, FileMetadataPayload>;
497pub type FileMetadataReplyPacket =
498    Cdc2ReplyPacket<USER_CDC, FILE_GET_INFO, Option<FileMetadataReplyPayload>>;
499
500#[derive(Debug, Clone, Eq, PartialEq)]
501pub struct FileMetadataPayload {
502    pub vendor: FileVendor,
503    /// Unused as of VEXos 1.1.5
504    pub reserved: u8,
505    pub file_name: FixedString<23>,
506}
507impl Encode for FileMetadataPayload {
508    fn size(&self) -> usize {
509        2 + self.file_name.size()
510    }
511
512    fn encode(&self, data: &mut [u8]) {
513        data[0] = self.vendor as _;
514        data[1] = self.reserved as _;
515        self.file_name.encode(&mut data[2..]);
516    }
517}
518
519#[derive(Debug, Clone, Eq, PartialEq)]
520pub struct FileMetadataReplyPayload {
521    /// RESEARCH NEEDED: Unknown what this is if there is no link to the file.
522    pub linked_vendor: Option<FileVendor>,
523    pub size: u32,
524    /// The storage entry address of the file.
525    pub load_address: u32,
526    pub crc32: u32,
527    pub metadata: FileMetadata,
528}
529impl Decode for Option<FileMetadataReplyPayload> {
530    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
531        let maybe_vid = u8::decode(data).unwrap();
532
533        let linked_vendor = match maybe_vid {
534            // 0 is returned if there is no linked file.
535            0 => None,
536            // 255 is returned if no file was found.
537            // In this case, the rest of the packet will be empty, so
538            // we return None for the whole packet.
539            255 => return Ok(None),
540            vid => Some(FileVendor::decode(&mut [vid].as_slice())?),
541        };
542
543        let size = u32::decode(data)?;
544
545        // This happens when we try to read a system file from the
546        // `/vex_/*` VID. In this case, all of bytes after the vendor
547        // will be returned as 0xff or 0x0, making this packet useless,
548        // so we'll return `None` here.
549        if size == 0xFFFFFFFF {
550            return Ok(None);
551        }
552
553        let load_address = u32::decode(data)?;
554        let crc32 = u32::decode(data)?;
555        let metadata = FileMetadata::decode(data)?;
556
557        Ok(Some(FileMetadataReplyPayload {
558            linked_vendor,
559            size,
560            load_address,
561            crc32,
562            metadata,
563        }))
564    }
565}
566
567pub type FileMetadataSetPacket = Cdc2CommandPacket<USER_CDC, FILE_SET_INFO, FileMetadataSetPayload>;
568pub type FileMetadataSetReplyPacket = Cdc2ReplyPacket<USER_CDC, FILE_SET_INFO, ()>;
569
570#[derive(Debug, Clone, Eq, PartialEq)]
571pub struct FileMetadataSetPayload {
572    pub vendor: FileVendor,
573    /// 0 = default. (RESEARCH NEEDED)
574    pub options: u8,
575    /// The storage entry address of the file.
576    pub load_address: u32,
577    pub metadata: FileMetadata,
578    pub file_name: FixedString<23>,
579}
580impl Encode for FileMetadataSetPayload {
581    fn size(&self) -> usize {
582        18 + self.file_name.size()
583    }
584
585    fn encode(&self, data: &mut [u8]) {
586        data[0] = self.vendor as _;
587        data[1] = self.options as _;
588        self.load_address.encode(&mut data[2..]);
589        self.metadata.encode(&mut data[6..]);
590        self.file_name.encode(&mut data[18..]);
591    }
592}
593
594pub type FileErasePacket = Cdc2CommandPacket<USER_CDC, FILE_ERASE, FileErasePayload>;
595pub type FileEraseReplyPacket = Cdc2ReplyPacket<USER_CDC, FILE_ERASE, ()>;
596
597#[derive(Debug, Clone, Eq, PartialEq)]
598pub struct FileErasePayload {
599    pub vendor: FileVendor,
600    /// Unused as of VEXos 1.1.5
601    pub reserved: u8,
602    pub file_name: FixedString<23>,
603}
604impl Encode for FileErasePayload {
605    fn size(&self) -> usize {
606        2 + self.file_name.size()
607    }
608
609    fn encode(&self, data: &mut [u8]) {
610        data[0] = self.vendor as _;
611        data[1] = self.reserved as _;
612        self.file_name.encode(&mut data[2..]);
613    }
614}
615
616pub type FileCleanUpPacket = Cdc2CommandPacket<USER_CDC, FILE_CLEANUP, ()>;
617pub type FileCleanUpReplyPacket = Cdc2ReplyPacket<USER_CDC, FILE_CLEANUP, FileCleanUpReplyPayload>;
618
619#[derive(Debug, Clone, Copy, Eq, PartialEq)]
620pub struct FileCleanUpReplyPayload {
621    count: u16,
622}
623
624impl Decode for FileCleanUpReplyPayload {
625    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
626        Ok(Self {
627            count: Decode::decode(data)?,
628        })
629    }
630}
631
632/// Same as "File Clear Up", but takes longer
633pub type FileFormatPacket = Cdc2CommandPacket<USER_CDC, FILE_FORMAT, FileFormatConfirmation>;
634pub type FileFormatReplyPacket = Cdc2CommandPacket<USER_CDC, FILE_FORMAT, ()>;
635
636#[derive(Debug, Clone, Copy, Eq, PartialEq)]
637pub struct FileFormatConfirmation {
638    /// Must be [0x44, 0x43, 0x42, 0x41].
639    pub confirmation_code: [u8; 4],
640}
641
642impl FileFormatConfirmation {
643    pub const FORMAT_CODE: [u8; 4] = [0x44, 0x43, 0x42, 0x41];
644
645    pub const fn new() -> Self {
646        Self {
647            confirmation_code: Self::FORMAT_CODE,
648        }
649    }
650}
651
652impl Default for FileFormatConfirmation {
653    fn default() -> Self {
654        Self::new()
655    }
656}
657
658impl Encode for FileFormatConfirmation {
659    fn size(&self) -> usize {
660        4
661    }
662
663    fn encode(&self, data: &mut [u8]) {
664        self.confirmation_code.encode(data)
665    }
666}
667
668#[derive(Debug, Clone, Copy, Eq, PartialEq)]
669pub enum FileControlGroup {
670    Radio(RadioChannel),
671}
672
673impl Encode for FileControlGroup {
674    fn size(&self) -> usize {
675        if matches!(self, Self::Radio(_)) { 2 } else { 0 }
676    }
677
678    fn encode(&self, data: &mut [u8]) {
679        #[allow(irrefutable_let_patterns)] // may change in the future
680        if let Self::Radio(channel) = self {
681            data[0] = 0x01;
682            data[1] = *channel as _;
683        }
684    }
685}
686
687#[derive(Debug, Clone, Copy, Eq, PartialEq)]
688#[repr(u8)]
689pub enum RadioChannel {
690    // NOTE: There's probably a secret third channel for matches, but that's not known.
691    /// Used when controlling the robot outside of a competition match.
692    Pit = 0x00,
693
694    /// Used when wirelessly uploading or downloading data to/from the V5 Brain.
695    ///
696    /// Higher radio bandwidth for file transfer purposes.
697    Download = 0x01,
698}
699
700pub type FileControlPacket = Cdc2CommandPacket<USER_CDC, FILE_CTRL, FileControlGroup>;
701pub type FileControlReplyPacket = Cdc2ReplyPacket<USER_CDC, FILE_CTRL, ()>;
702
703pub type ProgramStatusPacket = Cdc2CommandPacket<USER_CDC, FILE_USER_STAT, ProgramStatusPayload>;
704pub type ProgramStatusReplyPacket =
705    Cdc2ReplyPacket<USER_CDC, FILE_USER_STAT, ProgramStatusReplyPayload>;
706
707#[derive(Debug, Clone, Eq, PartialEq)]
708pub struct ProgramStatusPayload {
709    pub vendor: FileVendor,
710    /// Unused as of VEXos 1.1.5
711    pub reserved: u8,
712    /// The bin file name.
713    pub file_name: FixedString<23>,
714}
715impl Encode for ProgramStatusPayload {
716    fn size(&self) -> usize {
717        2 + self.file_name.size()
718    }
719
720    fn encode(&self, data: &mut [u8]) {
721        data[0] = self.vendor as _;
722        data[1] = self.reserved;
723
724        self.file_name.encode(&mut data[2..]);
725    }
726}
727
728#[derive(Debug, Clone, Copy, Eq, PartialEq)]
729pub struct ProgramStatusReplyPayload {
730    /// A zero-based slot number.
731    pub slot: u8,
732
733    /// A zero-based slot number, always same as Slot.
734    pub requested_slot: u8,
735}