zencan_common/
sdo.rs

1//! Common SDO implementation
2//!
3//! Defines messages, constants, etc for SDO protocol
4use int_enum::IntEnum;
5
6use crate::messages::{CanId, CanMessage};
7
8/// Specifies the possible server command specifier (SCS) values in SDO response packets
9enum ServerCommand {
10    SegmentUpload = 0,
11    SegmentDownload = 1,
12    Upload = 2,
13    Download = 3,
14    Abort = 4,
15    BlockDownload = 5,
16    BlockUpload = 6,
17}
18
19impl TryFrom<u8> for ServerCommand {
20    type Error = ();
21
22    fn try_from(value: u8) -> Result<Self, Self::Error> {
23        use ServerCommand::*;
24        match value {
25            0 => Ok(SegmentUpload),
26            1 => Ok(SegmentDownload),
27            2 => Ok(Upload),
28            3 => Ok(Download),
29            4 => Ok(Abort),
30            5 => Ok(BlockDownload),
31            6 => Ok(BlockUpload),
32            _ => Err(()),
33        }
34    }
35}
36
37/// SDO Abort Code
38///
39/// Defines the various reasons an SDO transfer can be aborted
40#[derive(Clone, Copy, Debug, PartialEq, IntEnum)]
41#[repr(u32)]
42pub enum AbortCode {
43    /// Toggle bit not alternated
44    ToggleNotAlternated = 0x0503_0000,
45    /// SDO protocol timed out
46    SdoTimeout = 0x0504_0000,
47    /// Client/server command specifier not valid or unknown
48    InvalidCommandSpecifier = 0x0504_0001,
49    /// Invalid block size (block mode only)
50    InvalidBlockSize = 0x0504_0002,
51    /// Invalid sequence number (block mode only)
52    InvalidSequenceNumber = 0x0504_0003,
53    /// CRC Error (block mode only )
54    CrcError = 0x0504_0004,
55    /// Out of memory
56    OutOfMemory = 0x0504_0005,
57    /// Unsupported access to an object
58    UnsupportedAccess = 0x0601_0000,
59    /// Attempt to read a write only object
60    WriteOnly = 0x0601_0001,
61    /// Attempt to write a read only object
62    ReadOnly = 0x0601_0002,
63    /// Object does not exist in the dictionary
64    NoSuchObject = 0x0602_0000,
65    /// Object cannot be mapped to the PDO
66    UnnallowedPdo = 0x0604_0041,
67    /// The number and length of objects would exceed PDO length
68    PdoTooLong = 0x0604_0042,
69    /// General parameter incompatibility
70    IncompatibleParameter = 0x0604_0043,
71    /// Access failed due to hardware error
72    HardwareError = 0x0606_0000,
73    /// Data type does not match, length of service parameter does not match
74    DataTypeMismatch = 0x0607_0010,
75    /// Data type does not match, length of service parameter too high
76    DataTypeMismatchLengthHigh = 0x0607_0012,
77    /// Data type does not match, length of service parameter too low
78    DataTypeMismatchLengthLow = 0x0607_0013,
79    /// Sub-index does not exist
80    NoSuchSubIndex = 0x0609_0011,
81    /// Invalid value for parameter (download only)
82    InvalidValue = 0x0609_0030,
83    /// Value of parameter too high (download only)
84    ValueTooHigh = 0x0609_0031,
85    /// Value of parameter too low (download only)
86    ValueTooLow = 0x0609_0032,
87    /// Resource isn't available
88    ResourceNotAvailable = 0x060A_0023,
89    /// General error
90    GeneralError = 0x0800_0000,
91    /// Data cannot be transferred or stored to the application
92    CantStore = 0x0800_0020,
93    /// Data cannot be transferred or stored to the application because of local control
94    CantStoreLocalControl = 0x0800_0021,
95    /// Data cannot be transferred or stored to the application because of the device state
96    CantStoreDeviceState = 0x0800_0022,
97    /// No object dictionary is present
98    NoObjectDict = 0x0800_0023,
99    /// No data available
100    NoData = 0x0800_0024,
101}
102
103#[derive(Clone, Copy, Debug, PartialEq)]
104#[repr(u8)]
105enum ClientCommand {
106    DownloadSegment = 0,
107    InitiateDownload = 1,
108    InitiateUpload = 2,
109    ReqUploadSegment = 3,
110    Abort = 4,
111    BlockUpload = 5,
112    BlockDownload = 6,
113}
114
115impl TryFrom<u8> for ClientCommand {
116    type Error = ();
117
118    fn try_from(value: u8) -> Result<Self, Self::Error> {
119        use ClientCommand::*;
120        match value {
121            0 => Ok(DownloadSegment),
122            1 => Ok(InitiateDownload),
123            2 => Ok(InitiateUpload),
124            3 => Ok(ReqUploadSegment),
125            4 => Ok(Abort),
126            5 => Ok(BlockUpload),
127            6 => Ok(BlockDownload),
128            _ => Err(()),
129        }
130    }
131}
132
133/// Represents the CAN message used to send a segment during a block upload or download
134#[derive(Clone, Copy, Debug, PartialEq)]
135pub struct BlockSegment {
136    /// Complete flag
137    ///
138    /// Indicates this is the last segment in the block transfer
139    pub c: bool,
140    /// The sequence number for the segment
141    ///
142    /// The sequence number starts at 1 on the first segment of a block, and increments on
143    /// subsequent segment, up to a maximum of 127.
144    pub seqnum: u8,
145    /// The data bytes of this segment
146    pub data: [u8; 7],
147}
148
149impl TryFrom<&[u8]> for BlockSegment {
150    type Error = ();
151
152    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
153        if value.len() != 8 {
154            return Err(());
155        }
156        let c = (value[0] & (1 << 7)) != 0;
157        let seqnum = value[0] & 0x7f;
158        let data: [u8; 7] = value[1..8].try_into().unwrap();
159        Ok(Self { c, seqnum, data })
160    }
161}
162
163impl BlockSegment {
164    /// Convert to the CAN message payload bytes
165    pub fn to_bytes(&self) -> [u8; 8] {
166        let mut bytes = [0; 8];
167        bytes[0] = (self.c as u8) << 7 | self.seqnum & 0x7f;
168        bytes[1..8].copy_from_slice(&self.data);
169        bytes
170    }
171
172    /// Create a CanMessage from the BlockSegment for transmission
173    pub fn to_can_message(&self, id: CanId) -> CanMessage {
174        CanMessage {
175            data: self.to_bytes(),
176            dlc: 8,
177            rtr: false,
178            id,
179        }
180    }
181}
182
183#[derive(Clone, Copy, Debug, PartialEq)]
184#[repr(u8)]
185enum BlockDownloadClientSubcommand {
186    InitiateDownload = 0,
187    EndDownload = 1,
188}
189
190impl TryFrom<u8> for BlockDownloadClientSubcommand {
191    type Error = ();
192
193    fn try_from(value: u8) -> Result<Self, Self::Error> {
194        match value {
195            0 => Ok(Self::InitiateDownload),
196            1 => Ok(Self::EndDownload),
197            _ => Err(()),
198        }
199    }
200}
201
202#[derive(Clone, Copy, Debug, PartialEq)]
203#[repr(u8)]
204enum BlockDownloadServerSubcommand {
205    InitiateDownloadAck = 0,
206    EndDownloadAck = 1,
207    ConfirmBlock = 2,
208}
209
210impl TryFrom<u8> for BlockDownloadServerSubcommand {
211    type Error = ();
212
213    fn try_from(value: u8) -> Result<Self, Self::Error> {
214        match value {
215            0 => Ok(Self::InitiateDownloadAck),
216            1 => Ok(Self::EndDownloadAck),
217            2 => Ok(Self::ConfirmBlock),
218            _ => Err(()),
219        }
220    }
221}
222
223#[derive(Clone, Copy, Debug, PartialEq)]
224#[repr(u8)]
225enum BlockUploadClientSubcommand {
226    InitiateUpload = 0,
227    EndUpload = 1,
228    ConfirmBlock = 2,
229    StartUpload = 3,
230}
231
232impl TryFrom<u8> for BlockUploadClientSubcommand {
233    type Error = ();
234
235    fn try_from(value: u8) -> Result<Self, Self::Error> {
236        match value {
237            0 => Ok(Self::InitiateUpload),
238            1 => Ok(Self::EndUpload),
239            2 => Ok(Self::ConfirmBlock),
240            3 => Ok(Self::StartUpload),
241            _ => Err(()),
242        }
243    }
244}
245
246#[derive(Clone, Copy, Debug, PartialEq)]
247#[repr(u8)]
248enum BlockUploadServerSubcommand {
249    InitiateUpload = 0,
250    EndUpload = 1,
251}
252
253impl TryFrom<u8> for BlockUploadServerSubcommand {
254    type Error = ();
255
256    fn try_from(value: u8) -> Result<Self, Self::Error> {
257        match value {
258            0 => Ok(Self::InitiateUpload),
259            1 => Ok(Self::EndUpload),
260            _ => Err(()),
261        }
262    }
263}
264
265/// An SDO Request
266///
267/// This represents the possible request messages which can be send from client to server
268#[derive(Clone, Copy, Debug)]
269#[cfg_attr(feature = "defmt", derive(defmt::Format))]
270pub enum SdoRequest {
271    /// Begin a download, writing data to an object on the server
272    InitiateDownload {
273        /// Number of unused bytes in data
274        n: u8,
275        /// Expedited
276        e: bool,
277        /// size valid
278        s: bool,
279        /// Object index
280        index: u16,
281        /// Object sub-index
282        sub: u8,
283        /// data (value on expedited, size when e=1 and s=1)
284        data: [u8; 4],
285    },
286    /// Send a segment of data to the server
287    DownloadSegment {
288        /// Toggle flag
289        t: bool,
290        /// Number of unused bytes in data
291        n: u8,
292        /// When set, indicates there are no more segments to be sent
293        c: bool,
294        /// Segment data
295        data: [u8; 7],
296    },
297    /// Begin an upload of data from an object on the server
298    InitiateUpload {
299        /// The requested object index
300        index: u16,
301        /// The requested sub object
302        sub: u8,
303    },
304    /// Request the next segment in an upload
305    ReqUploadSegment {
306        /// Toggle flag
307        t: bool,
308    },
309    /// Initiate a block download
310    InitiateBlockDownload {
311        /// Client CRC supported flag
312        cc: bool,
313        /// size flag
314        s: bool,
315        /// Index of object to download to
316        index: u16,
317        /// Sub object to download to
318        sub: u8,
319        /// If s=1, contains the number of bytes to be downloaded
320        size: u32,
321    },
322    /// End a block download
323    EndBlockDownload {
324        /// Indicates the number of buytes in the last segment of the last block that do not contain
325        /// data
326        n: u8,
327        /// CRC of the block (if supported by both client and server)
328        crc: u16,
329    },
330    /// Initiate a block upload
331    InitiateBlockUpload {
332        /// Client supports CRC
333        cc: bool,
334        /// Index of the object to upload
335        index: u16,
336        /// Sub index of the object to upload
337        sub: u8,
338        /// Number of segments per block
339        blksize: u8,
340        /// Protocol switch threshold
341        ///
342        /// Specifies when the server may switch the upload protocol to segmented or expedited
343        /// pst = 0: Change of protocol not allowed
344        /// pst > 0: If the size of the data is <= pst the server may switch the protocol
345        pst: u8,
346    },
347    /// End a block upload
348    EndBlockUpload,
349    /// Request server to start sending upload block
350    StartBlockUpload,
351    /// Confirm reciept of a block from a block upload
352    ConfirmBlock {
353        /// The sequence number of the last successfully received block
354        ackseq: u8,
355        /// The number of segments to use for the next upload block
356        blksize: u8,
357    },
358
359    /// Sent by client to abort an ongoing transaction
360    Abort {
361        /// The object index of the active transaction
362        index: u16,
363        /// The sub object of the active transaction
364        sub: u8,
365        /// The abort reason
366        abort_code: u32,
367    },
368}
369
370impl SdoRequest {
371    /// Create an abort message
372    pub fn abort(index: u16, sub: u8, abort_code: AbortCode) -> Self {
373        SdoRequest::Abort {
374            index,
375            sub,
376            abort_code: abort_code as u32,
377        }
378    }
379
380    /// Create an initiate download request
381    pub fn initiate_download(index: u16, sub: u8, size: Option<u32>) -> Self {
382        let data = size.unwrap_or(0).to_le_bytes();
383
384        SdoRequest::InitiateDownload {
385            n: 0,
386            e: false,
387            s: size.is_some(),
388            index,
389            sub,
390            data,
391        }
392    }
393
394    /// Create an initiate block download request
395    pub fn initiate_block_download(index: u16, sub: u8, crc_supported: bool, size: u32) -> Self {
396        SdoRequest::InitiateBlockDownload {
397            cc: crc_supported,
398            s: true,
399            index,
400            sub,
401            size,
402        }
403    }
404
405    /// Create an end block download request
406    ///
407    /// # Arguments
408    ///
409    /// * `n` - Number of bytes in the last segment which do not contain valid data
410    /// * `crc` - The CRC computed by client for the downloaded data
411    pub fn end_block_download(n: u8, crc: u16) -> Self {
412        SdoRequest::EndBlockDownload { n, crc }
413    }
414
415    /// Creat a `DownloadSegment` requests
416    pub fn download_segment(toggle: bool, last_segment: bool, segment_data: &[u8]) -> Self {
417        let mut data = [0; 7];
418        data[0..segment_data.len()].copy_from_slice(segment_data);
419        SdoRequest::DownloadSegment {
420            t: toggle,
421            n: 7 - segment_data.len() as u8,
422            c: last_segment,
423            data,
424        }
425    }
426
427    /// Create an expedited download message
428    pub fn expedited_download(index: u16, sub: u8, data: &[u8]) -> Self {
429        let mut msg_data = [0; 4];
430        msg_data[0..data.len()].copy_from_slice(data);
431
432        SdoRequest::InitiateDownload {
433            n: (4 - data.len()) as u8,
434            e: true,
435            s: true,
436            index,
437            sub,
438            data: msg_data,
439        }
440    }
441
442    /// Creata an `InitiateUpload` request
443    pub fn initiate_upload(index: u16, sub: u8) -> Self {
444        SdoRequest::InitiateUpload { index, sub }
445    }
446
447    /// Create an InitiateBlockUpload request
448    pub fn initiate_block_upload(index: u16, sub: u8, cc: bool, blksize: u8, pst: u8) -> Self {
449        SdoRequest::InitiateBlockUpload {
450            index,
451            sub,
452            cc,
453            blksize,
454            pst,
455        }
456    }
457
458    /// Create a `ReqUploadSegment` request
459    pub fn upload_segment_request(toggle: bool) -> Self {
460        SdoRequest::ReqUploadSegment { t: toggle }
461    }
462
463    /// Convert the request to message payload bytes
464    pub fn to_bytes(self) -> [u8; 8] {
465        let mut payload = [0; 8];
466
467        match self {
468            SdoRequest::InitiateDownload {
469                n,
470                e,
471                s,
472                index,
473                sub,
474                data,
475            } => {
476                payload[0] = ((ClientCommand::InitiateDownload as u8) << 5)
477                    | (n << 2)
478                    | ((e as u8) << 1)
479                    | s as u8;
480                payload[1] = (index & 0xff) as u8;
481                payload[2] = (index >> 8) as u8;
482                payload[3] = sub;
483                payload[4..8].copy_from_slice(&data);
484            }
485            SdoRequest::DownloadSegment { t, n, c, data } => {
486                payload[0] = ((ClientCommand::DownloadSegment as u8) << 5)
487                    | ((t as u8) << 4)
488                    | ((n & 7) << 1)
489                    | (c as u8);
490
491                payload[1..8].copy_from_slice(&data);
492            }
493            SdoRequest::InitiateUpload { index, sub } => {
494                payload[0] = (ClientCommand::InitiateUpload as u8) << 5;
495                payload[1] = (index & 0xff) as u8;
496                payload[2] = (index >> 8) as u8;
497                payload[3] = sub;
498            }
499            SdoRequest::ReqUploadSegment { t } => {
500                payload[0] = ((ClientCommand::ReqUploadSegment as u8) << 5) | ((t as u8) << 4);
501            }
502            SdoRequest::Abort {
503                index,
504                sub,
505                abort_code,
506            } => {
507                payload[0] = (ClientCommand::Abort as u8) << 5;
508                payload[1] = (index & 0xff) as u8;
509                payload[2] = (index >> 8) as u8;
510                payload[3] = sub;
511                payload[4..8].copy_from_slice(&abort_code.to_le_bytes());
512            }
513            SdoRequest::InitiateBlockDownload {
514                cc,
515                s,
516                index,
517                sub,
518                size,
519            } => {
520                payload[0] = ((ClientCommand::BlockDownload as u8) << 5)
521                    | ((cc as u8) << 2)
522                    | ((s as u8) << 1);
523                payload[1] = (index & 0xff) as u8;
524                payload[2] = (index >> 8) as u8;
525                payload[3] = sub;
526                payload[4..8].copy_from_slice(&size.to_le_bytes());
527            }
528            SdoRequest::EndBlockDownload { n, crc } => {
529                payload[0] = ((ClientCommand::BlockDownload as u8) << 5)
530                    | (n << 2)
531                    | BlockDownloadClientSubcommand::EndDownload as u8;
532                payload[1..3].copy_from_slice(&crc.to_le_bytes());
533            }
534            SdoRequest::InitiateBlockUpload {
535                index,
536                sub,
537                cc,
538                blksize,
539                pst,
540            } => {
541                payload[0] = ((ClientCommand::BlockUpload as u8) << 5)
542                    | ((cc as u8) << 2)
543                    | (BlockUploadClientSubcommand::InitiateUpload as u8);
544                payload[1] = (index & 0xff) as u8;
545                payload[2] = (index >> 8) as u8;
546                payload[3] = sub;
547                payload[4] = blksize;
548                payload[5] = pst;
549            }
550            SdoRequest::EndBlockUpload => {
551                payload[0] = ((ClientCommand::BlockUpload as u8) << 5)
552                    | (BlockUploadClientSubcommand::EndUpload as u8);
553            }
554            SdoRequest::StartBlockUpload => {
555                payload[0] = ((ClientCommand::BlockUpload as u8) << 5)
556                    | (BlockUploadClientSubcommand::StartUpload as u8);
557            }
558            SdoRequest::ConfirmBlock { ackseq, blksize } => {
559                payload[0] = ((ClientCommand::BlockUpload as u8) << 5)
560                    | (BlockUploadClientSubcommand::ConfirmBlock as u8);
561                payload[1] = ackseq;
562                payload[2] = blksize;
563            }
564        }
565        payload
566    }
567
568    /// Convert the request to a CanMessage using the provided COB ID
569    pub fn to_can_message(self, id: CanId) -> CanMessage {
570        let payload = self.to_bytes();
571        CanMessage::new(id, &payload)
572    }
573}
574
575impl TryFrom<&[u8]> for SdoRequest {
576    type Error = AbortCode;
577
578    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
579        if value.len() < 8 {
580            return Err(AbortCode::DataTypeMismatchLengthLow);
581        }
582        let ccs = value[0] >> 5;
583        let ccs: ClientCommand = match ccs.try_into() {
584            Ok(ccs) => ccs,
585            Err(_) => return Err(AbortCode::InvalidCommandSpecifier),
586        };
587
588        match ccs {
589            ClientCommand::DownloadSegment => {
590                let t = (value[0] & (1 << 4)) != 0;
591                let n = (value[0] >> 1) & 0x7;
592                let c = (value[0] & (1 << 0)) != 0;
593                let data = value[1..8].try_into().unwrap();
594                Ok(SdoRequest::DownloadSegment { t, n, c, data })
595            }
596            ClientCommand::InitiateDownload => {
597                let n = (value[0] >> 2) & 0x3;
598                let e = (value[0] & (1 << 1)) != 0;
599                let s = (value[0] & (1 << 0)) != 0;
600                let index = value[1] as u16 | ((value[2] as u16) << 8);
601                let sub = value[3];
602                let data = value[4..8].try_into().unwrap();
603                Ok(SdoRequest::InitiateDownload {
604                    n,
605                    e,
606                    s,
607                    index,
608                    sub,
609                    data,
610                })
611            }
612            ClientCommand::InitiateUpload => {
613                let index = value[1] as u16 | ((value[2] as u16) << 8);
614                let sub = value[3];
615                Ok(SdoRequest::InitiateUpload { index, sub })
616            }
617            ClientCommand::ReqUploadSegment => {
618                let t = (((value[0]) >> 4) & 1) != 0;
619                Ok(SdoRequest::ReqUploadSegment { t })
620            }
621            ClientCommand::Abort => {
622                let index = value[1] as u16 | ((value[2] as u16) << 8);
623                let sub = value[3];
624                let abort_code = u32::from_le_bytes(value[4..8].try_into().unwrap());
625                Ok(SdoRequest::Abort {
626                    index,
627                    sub,
628                    abort_code,
629                })
630            }
631            ClientCommand::BlockUpload => {
632                let csc = value[0] & 3;
633                let subcommand = match BlockUploadClientSubcommand::try_from(csc) {
634                    Ok(subcommand) => subcommand,
635                    Err(_) => return Err(AbortCode::InvalidCommandSpecifier),
636                };
637                match subcommand {
638                    BlockUploadClientSubcommand::InitiateUpload => {
639                        let cc = value[0] & (1 << 2) != 0;
640                        let index = value[1] as u16 | ((value[2] as u16) << 8);
641                        let sub = value[3];
642                        let blksize = value[4];
643                        let pst = value[5];
644                        Ok(SdoRequest::InitiateBlockUpload {
645                            index,
646                            sub,
647                            cc,
648                            blksize,
649                            pst,
650                        })
651                    }
652                    BlockUploadClientSubcommand::EndUpload => Ok(SdoRequest::EndBlockUpload),
653                    BlockUploadClientSubcommand::ConfirmBlock => {
654                        let ackseq = value[1];
655                        let blksize = value[2];
656                        Ok(SdoRequest::ConfirmBlock { ackseq, blksize })
657                    }
658                    BlockUploadClientSubcommand::StartUpload => Ok(SdoRequest::StartBlockUpload),
659                }
660            }
661            ClientCommand::BlockDownload => {
662                let csc = value[0] & 0x1;
663                let subcommand = match BlockDownloadClientSubcommand::try_from(csc) {
664                    Ok(subcommand) => subcommand,
665                    Err(_) => return Err(AbortCode::InvalidCommandSpecifier),
666                };
667                match subcommand {
668                    BlockDownloadClientSubcommand::InitiateDownload => {
669                        let cc = (value[0] & (1 << 2)) != 0;
670                        let s = (value[0] & (1 << 1)) != 0;
671                        let index = value[1] as u16 | ((value[2] as u16) << 8);
672                        let sub = value[3];
673                        let size = u32::from_le_bytes(value[4..8].try_into().unwrap());
674                        Ok(SdoRequest::InitiateBlockDownload {
675                            cc,
676                            s,
677                            index,
678                            sub,
679
680                            size,
681                        })
682                    }
683                    BlockDownloadClientSubcommand::EndDownload => {
684                        let n = (value[0] >> 2) & 7;
685                        let crc = u16::from_le_bytes(value[1..3].try_into().unwrap());
686                        Ok(SdoRequest::EndBlockDownload { n, crc })
687                    }
688                }
689            }
690        }
691    }
692}
693
694/// Represents a response from SDO server to client
695#[derive(Copy, Clone, Debug, PartialEq)]
696#[cfg_attr(feature = "defmt", derive(defmt::Format))]
697pub enum SdoResponse {
698    /// Response to an [`SdoRequest::InitiateUpload`]
699    ConfirmUpload {
700        /// Number of unused bytes in data
701        n: u8,
702        /// Expedited flag
703        e: bool,
704        /// size flag
705        s: bool,
706        /// The index of the object being uploaded
707        index: u16,
708        /// The sub object being uploaded
709        sub: u8,
710        /// Value if e=1, or size if s=1
711        data: [u8; 4],
712    },
713    /// Send an upload segment
714    UploadSegment {
715        /// The toggle bit
716        t: bool,
717        /// The number of unused bytes in data
718        n: u8,
719        /// Flag indicating this is the final segment
720        c: bool,
721        /// object data
722        data: [u8; 7],
723    },
724    /// Response to a [`SdoRequest::InitiateDownload`]
725    ConfirmDownload {
726        /// The index of the object to be written to
727        index: u16,
728        /// The sub object to be written to
729        sub: u8,
730    },
731    /// Response to a [`SdoRequest::DownloadSegment`]
732    ConfirmDownloadSegment {
733        /// Toggle flag
734        t: bool,
735    },
736    /// Confirm a block download initiation
737    ConfirmBlockDownload {
738        /// Flag indicating server supports CRC generation
739        sc: bool,
740        /// Index of the object being downloaded
741        index: u16,
742        /// Sub index of the object being downloaded
743        sub: u8,
744        /// Number of segments for client to send in the next block
745        blksize: u8,
746    },
747    /// Confirm completion of a block
748    ConfirmBlock {
749        /// Sequence number of the last segment successfully received by the server
750        ackseq: u8,
751        /// Number of segments for the client to send in the next block
752        blksize: u8,
753    },
754    /// Confirm completion of a block download
755    ConfirmBlockDownloadEnd,
756    /// Confirm a block upload initiation
757    ConfirmBlockUpload {
758        /// Flag indicating server supports CRC on block transfer
759        sc: bool,
760        /// Size flag - indicates a valid size is stored in size field
761        s: bool,
762        /// Index of the object being uploaded
763        index: u16,
764        /// Sub index of the object being uploaded
765        sub: u8,
766        /// Size of the object to be uploaded
767        size: u32,
768    },
769    /// Send by server to end block upload
770    BlockUploadEnd {
771        /// Indicates the number of bytes in the last segment which are not valid
772        n: u8,
773        /// The CRC of the block upload. Valid only if both server and client indicated support for
774        /// CRC
775        crc: u16,
776    },
777    /// Sent by server to abort an ongoing transaction
778    Abort {
779        /// Object index of the active transfer
780        index: u16,
781        /// Sub object of the active transfer
782        sub: u8,
783        /// Abort reason
784        abort_code: u32,
785    },
786}
787
788impl TryFrom<[u8; 8]> for SdoResponse {
789    type Error = ();
790
791    fn try_from(value: [u8; 8]) -> Result<Self, Self::Error> {
792        let scs = value[0] >> 5;
793        let command: ServerCommand = scs.try_into()?;
794        match command {
795            ServerCommand::SegmentUpload => {
796                let t = (value[0] & (1 << 4)) != 0;
797                let n = (value[0] >> 1) & 7;
798                let c = (value[0] & (1 << 0)) != 0;
799                let data: [u8; 7] = value[1..8].try_into().unwrap();
800
801                Ok(SdoResponse::UploadSegment { t, n, c, data })
802            }
803            ServerCommand::SegmentDownload => {
804                let t = (value[0] & (1 << 4)) != 0;
805                Ok(SdoResponse::ConfirmDownloadSegment { t })
806            }
807            ServerCommand::Upload => {
808                let n = (value[0] >> 2) & 0x3;
809                let e = (value[0] & (1 << 1)) != 0;
810                let s = (value[0] & (1 << 0)) != 0;
811                let index = u16::from_le_bytes(value[1..3].try_into().unwrap());
812                let sub = value[3];
813                let data: [u8; 4] = value[4..8].try_into().unwrap();
814                Ok(SdoResponse::ConfirmUpload {
815                    n,
816                    e,
817                    s,
818                    index,
819                    sub,
820                    data,
821                })
822            }
823            ServerCommand::Download => {
824                let index = u16::from_le_bytes(value[1..3].try_into().unwrap());
825                let sub = value[3];
826                Ok(SdoResponse::ConfirmDownload { index, sub })
827            }
828            ServerCommand::BlockDownload => {
829                match BlockDownloadServerSubcommand::try_from(value[0] & 0x3)? {
830                    BlockDownloadServerSubcommand::ConfirmBlock => {
831                        let ackseq = value[1];
832                        let blksize = value[2];
833                        Ok(SdoResponse::ConfirmBlock { ackseq, blksize })
834                    }
835                    BlockDownloadServerSubcommand::InitiateDownloadAck => {
836                        let sc = (value[0] & (1 << 2)) != 0;
837                        let index = u16::from_le_bytes(value[1..3].try_into().unwrap());
838                        let sub = value[3];
839                        let blksize = value[4];
840                        Ok(SdoResponse::ConfirmBlockDownload {
841                            sc,
842                            index,
843                            sub,
844                            blksize,
845                        })
846                    }
847                    BlockDownloadServerSubcommand::EndDownloadAck => {
848                        Ok(SdoResponse::ConfirmBlockDownloadEnd)
849                    }
850                }
851            }
852            ServerCommand::BlockUpload => {
853                match BlockUploadServerSubcommand::try_from(value[0] & 0x3)? {
854                    BlockUploadServerSubcommand::InitiateUpload => {
855                        let s = (value[0] & (1 << 1)) != 0;
856                        let sc = (value[0] & (1 << 2)) != 0;
857                        let index = u16::from_le_bytes(value[1..3].try_into().unwrap());
858                        let sub = value[3];
859                        let size = u32::from_le_bytes(value[4..8].try_into().unwrap());
860                        Ok(SdoResponse::ConfirmBlockUpload {
861                            sc,
862                            s,
863                            index,
864                            sub,
865                            size,
866                        })
867                    }
868                    BlockUploadServerSubcommand::EndUpload => {
869                        let n = (value[0] >> 2) & 7;
870                        let crc = u16::from_le_bytes(value[1..3].try_into().unwrap());
871                        Ok(SdoResponse::BlockUploadEnd { n, crc })
872                    }
873                }
874            }
875            ServerCommand::Abort => {
876                let index = u16::from_le_bytes(value[1..3].try_into().unwrap());
877                let sub = value[3];
878                let abort_code = u32::from_le_bytes(value[4..8].try_into().unwrap());
879                Ok(SdoResponse::Abort {
880                    index,
881                    sub,
882                    abort_code,
883                })
884            }
885        }
886    }
887}
888
889impl TryFrom<CanMessage> for SdoResponse {
890    type Error = ();
891    fn try_from(msg: CanMessage) -> Result<Self, Self::Error> {
892        msg.data.try_into()
893    }
894}
895impl SdoResponse {
896    /// Create a `ConfirmUpload` response for an expedited upload
897    pub fn expedited_upload(index: u16, sub: u8, data: &[u8]) -> SdoResponse {
898        if data.len() > 4 {
899            panic!("Cannot create expedited upload with more than 4 bytes");
900        }
901
902        let mut msg_data = [0; 4];
903        msg_data[0..data.len()].copy_from_slice(data);
904
905        let s;
906        let n;
907        // For 0 length uploads, set the size bit to 0, to indicate that this is an empty response.
908        // It's not clear if this is CiA301 compatible, but it is how zencan does it!
909        if data.is_empty() {
910            s = false;
911            n = 0;
912        } else {
913            s = true;
914            n = 4 - data.len() as u8;
915        }
916        SdoResponse::ConfirmUpload {
917            index,
918            sub,
919            e: true,
920            s,
921            n,
922            data: msg_data,
923        }
924    }
925
926    /// Create a `ConfirmUpload` response for a segmented upload
927    pub fn upload_acknowledge(index: u16, sub: u8, size: Option<u32>) -> SdoResponse {
928        SdoResponse::ConfirmUpload {
929            n: 0,
930            e: false,
931            s: size.is_some(),
932            index,
933            sub,
934            data: size.unwrap_or(0).to_le_bytes(),
935        }
936    }
937
938    /// Create a ConfirmBlockUpload reponse
939    pub fn block_upload_acknowledge(
940        index: u16,
941        sc: bool,
942        sub: u8,
943        size: Option<u32>,
944    ) -> SdoResponse {
945        SdoResponse::ConfirmBlockUpload {
946            sc,
947            s: size.is_some(),
948            index,
949            sub,
950            size: size.unwrap_or(0),
951        }
952    }
953
954    /// Create an `UploadSegment` response
955    pub fn upload_segment(t: bool, c: bool, data: &[u8]) -> SdoResponse {
956        let n = (7 - data.len()) as u8;
957        let mut buf = [0; 7];
958        buf[0..data.len()].copy_from_slice(data);
959        SdoResponse::UploadSegment { t, n, c, data: buf }
960    }
961
962    /// Create a `ConfirmDownload` response
963    pub fn download_acknowledge(index: u16, sub: u8) -> SdoResponse {
964        SdoResponse::ConfirmDownload { index, sub }
965    }
966
967    /// Create a `ConfirmDownloadSegment` response
968    pub fn download_segment_acknowledge(t: bool) -> SdoResponse {
969        SdoResponse::ConfirmDownloadSegment { t }
970    }
971
972    /// Create a ConfirmBlockDownload response
973    pub fn block_download_acknowledge(sc: bool, index: u16, sub: u8, blksize: u8) -> SdoResponse {
974        SdoResponse::ConfirmBlockDownload {
975            sc,
976            index,
977            sub,
978            blksize,
979        }
980    }
981
982    /// Create a ConfirmBlock response
983    pub fn confirm_block(ackseq: u8, blksize: u8) -> SdoResponse {
984        SdoResponse::ConfirmBlock { ackseq, blksize }
985    }
986
987    /// Create an abort response
988    pub fn abort(index: u16, sub: u8, abort_code: AbortCode) -> SdoResponse {
989        let abort_code = abort_code as u32;
990        SdoResponse::Abort {
991            index,
992            sub,
993            abort_code,
994        }
995    }
996
997    /// Convert to message payload bytes
998    pub fn to_bytes(&self) -> [u8; 8] {
999        let mut payload = [0; 8];
1000
1001        match self {
1002            SdoResponse::ConfirmUpload {
1003                n,
1004                e,
1005                s,
1006                index,
1007                sub,
1008                data,
1009            } => {
1010                payload[0] = ((ServerCommand::Upload as u8) << 5)
1011                    | ((n & 0x3) << 2)
1012                    | ((*e as u8) << 1)
1013                    | (*s as u8);
1014                payload[1] = (index & 0xff) as u8;
1015                payload[2] = (index >> 8) as u8;
1016                payload[3] = *sub;
1017                payload[4..8].copy_from_slice(data);
1018            }
1019            SdoResponse::ConfirmDownload { index, sub } => {
1020                payload[0] = (ServerCommand::Download as u8) << 5;
1021                payload[1] = (index & 0xff) as u8;
1022                payload[2] = (index >> 8) as u8;
1023                payload[3] = *sub;
1024            }
1025            SdoResponse::UploadSegment { t, n, c, data } => {
1026                payload[0] = ((ServerCommand::SegmentUpload as u8) << 5)
1027                    | ((*t as u8) << 4)
1028                    | (n << 1)
1029                    | *c as u8;
1030                payload[1..8].copy_from_slice(data);
1031            }
1032            SdoResponse::ConfirmBlockDownload {
1033                sc,
1034                index,
1035                sub,
1036                blksize,
1037            } => {
1038                payload[0] = ((ServerCommand::BlockDownload as u8) << 5)
1039                    | ((*sc as u8) << 2)
1040                    | (BlockDownloadServerSubcommand::InitiateDownloadAck as u8);
1041                payload[1] = (index & 0xff) as u8;
1042                payload[2] = (index >> 8) as u8;
1043                payload[3] = *sub;
1044                payload[4] = *blksize;
1045            }
1046            SdoResponse::ConfirmBlock { ackseq, blksize } => {
1047                payload[0] = ((ServerCommand::BlockDownload as u8) << 5)
1048                    | (BlockDownloadServerSubcommand::ConfirmBlock as u8);
1049                payload[1] = *ackseq;
1050                payload[2] = *blksize;
1051            }
1052            SdoResponse::ConfirmBlockDownloadEnd => {
1053                payload[0] = ((ServerCommand::BlockDownload as u8) << 5)
1054                    | (BlockDownloadServerSubcommand::EndDownloadAck as u8);
1055            }
1056            SdoResponse::ConfirmDownloadSegment { t } => {
1057                payload[0] = ((ServerCommand::SegmentDownload as u8) << 5) | ((*t as u8) << 4);
1058            }
1059            SdoResponse::Abort {
1060                index,
1061                sub,
1062                abort_code,
1063            } => {
1064                payload[0] = (ServerCommand::Abort as u8) << 5;
1065                payload[1] = (index & 0xff) as u8;
1066                payload[2] = (index >> 8) as u8;
1067                payload[3] = *sub;
1068                payload[4..8].copy_from_slice(&abort_code.to_le_bytes());
1069            }
1070            SdoResponse::ConfirmBlockUpload {
1071                sc,
1072                s,
1073                index,
1074                sub,
1075                size,
1076            } => {
1077                payload[0] = ((ServerCommand::BlockUpload as u8) << 5)
1078                    | ((*sc as u8) << 2)
1079                    | ((*s as u8) << 1)
1080                    | (BlockUploadServerSubcommand::InitiateUpload as u8);
1081                payload[1] = (index & 0xff) as u8;
1082                payload[2] = (index >> 8) as u8;
1083                payload[3] = *sub;
1084                payload[4..8].copy_from_slice(&size.to_le_bytes());
1085            }
1086            SdoResponse::BlockUploadEnd { n, crc } => {
1087                payload[0] = ((ServerCommand::BlockUpload as u8) << 5)
1088                    | (*n << 2)
1089                    | (BlockUploadServerSubcommand::EndUpload as u8);
1090                payload[1..3].copy_from_slice(&crc.to_le_bytes());
1091            }
1092        }
1093        payload
1094    }
1095    /// Convert the response to a [CanMessage] using the provided COB ID
1096    pub fn to_can_message(self, id: CanId) -> CanMessage {
1097        CanMessage::new(id, &self.to_bytes())
1098    }
1099}