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)]
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        /// Index of the object to upload
333        index: u16,
334        /// Sub index of the object to upload
335        sub: u8,
336        /// Number of segments per block
337        blksize: u8,
338        /// Protocol switch threshold
339        ///
340        /// Specifies when the server may switch the upload protocol to segmented or expedited
341        /// pst = 0: Change of protocol not allowed
342        /// pst > 0: If the size of the data is <= pst the server may switch the protocol
343        pst: u8,
344    },
345    /// End a block upload
346    EndBlockUpload,
347    /// Request server to start sending upload block
348    StartBlockUpload,
349    /// Confirm reciept of a block from a block upload
350    ConfirmBlock {
351        /// The sequence number of the last successfully received block
352        ackseq: u8,
353        /// The number of segments to use for the next upload block
354        blksize: u8,
355    },
356
357    /// Sent by client to abort an ongoing transaction
358    Abort {
359        /// The object index of the active transaction
360        index: u16,
361        /// The sub object of the active transaction
362        sub: u8,
363        /// The abort reason
364        abort_code: u32,
365    },
366}
367
368impl SdoRequest {
369    /// Create an abort message
370    pub fn abort(index: u16, sub: u8, abort_code: AbortCode) -> Self {
371        SdoRequest::Abort {
372            index,
373            sub,
374            abort_code: abort_code as u32,
375        }
376    }
377
378    /// Create an initiate download request
379    pub fn initiate_download(index: u16, sub: u8, size: Option<u32>) -> Self {
380        let data = size.unwrap_or(0).to_le_bytes();
381
382        SdoRequest::InitiateDownload {
383            n: 0,
384            e: false,
385            s: size.is_some(),
386            index,
387            sub,
388            data,
389        }
390    }
391
392    /// Create an initiate block download request
393    pub fn initiate_block_download(index: u16, sub: u8, crc_supported: bool, size: u32) -> Self {
394        SdoRequest::InitiateBlockDownload {
395            cc: crc_supported,
396            s: true,
397            index,
398            sub,
399            size,
400        }
401    }
402
403    /// Create an end block download request
404    ///
405    /// # Arguments
406    ///
407    /// * `n` - Number of bytes in the last segment which do not contain valid data
408    /// * `crc` - The CRC computed by client for the downloaded data
409    pub fn end_block_download(n: u8, crc: u16) -> Self {
410        SdoRequest::EndBlockDownload { n, crc }
411    }
412
413    /// Creat a `DownloadSegment` requests
414    pub fn download_segment(toggle: bool, last_segment: bool, segment_data: &[u8]) -> Self {
415        let mut data = [0; 7];
416        data[0..segment_data.len()].copy_from_slice(segment_data);
417        SdoRequest::DownloadSegment {
418            t: toggle,
419            n: 7 - segment_data.len() as u8,
420            c: last_segment,
421            data,
422        }
423    }
424
425    /// Create an expedited download message
426    pub fn expedited_download(index: u16, sub: u8, data: &[u8]) -> Self {
427        let mut msg_data = [0; 4];
428        msg_data[0..data.len()].copy_from_slice(data);
429
430        SdoRequest::InitiateDownload {
431            n: (4 - data.len()) as u8,
432            e: true,
433            s: true,
434            index,
435            sub,
436            data: msg_data,
437        }
438    }
439
440    /// Creata an `InitiateUpload` request
441    pub fn initiate_upload(index: u16, sub: u8) -> Self {
442        SdoRequest::InitiateUpload { index, sub }
443    }
444
445    /// Create a `ReqUploadSegment` request
446    pub fn upload_segment_request(toggle: bool) -> Self {
447        SdoRequest::ReqUploadSegment { t: toggle }
448    }
449
450    /// Convert the request to message payload bytes
451    pub fn to_bytes(self) -> [u8; 8] {
452        let mut payload = [0; 8];
453
454        match self {
455            SdoRequest::InitiateDownload {
456                n,
457                e,
458                s,
459                index,
460                sub,
461                data,
462            } => {
463                payload[0] = ((ClientCommand::InitiateDownload as u8) << 5)
464                    | (n << 2)
465                    | ((e as u8) << 1)
466                    | s as u8;
467                payload[1] = (index & 0xff) as u8;
468                payload[2] = (index >> 8) as u8;
469                payload[3] = sub;
470                payload[4..8].copy_from_slice(&data);
471            }
472            SdoRequest::DownloadSegment { t, n, c, data } => {
473                payload[0] = ((ClientCommand::DownloadSegment as u8) << 5)
474                    | ((t as u8) << 4)
475                    | ((n & 7) << 1)
476                    | (c as u8);
477
478                payload[1..8].copy_from_slice(&data);
479            }
480            SdoRequest::InitiateUpload { index, sub } => {
481                payload[0] = (ClientCommand::InitiateUpload as u8) << 5;
482                payload[1] = (index & 0xff) as u8;
483                payload[2] = (index >> 8) as u8;
484                payload[3] = sub;
485            }
486            SdoRequest::ReqUploadSegment { t } => {
487                payload[0] = ((ClientCommand::ReqUploadSegment as u8) << 5) | ((t as u8) << 4);
488            }
489            SdoRequest::Abort {
490                index,
491                sub,
492                abort_code,
493            } => {
494                payload[0] = (ClientCommand::Abort as u8) << 5;
495                payload[1] = (index & 0xff) as u8;
496                payload[2] = (index >> 8) as u8;
497                payload[3] = sub;
498                payload[4..8].copy_from_slice(&abort_code.to_le_bytes());
499            }
500            SdoRequest::InitiateBlockDownload {
501                cc,
502                s,
503                index,
504                sub,
505                size,
506            } => {
507                payload[0] = ((ClientCommand::BlockDownload as u8) << 5)
508                    | ((cc as u8) << 2)
509                    | ((s as u8) << 1);
510                payload[1] = (index & 0xff) as u8;
511                payload[2] = (index >> 8) as u8;
512                payload[3] = sub;
513                payload[4..8].copy_from_slice(&size.to_le_bytes());
514            }
515            SdoRequest::EndBlockDownload { n, crc } => {
516                payload[0] = ((ClientCommand::BlockDownload as u8) << 5)
517                    | (n << 2)
518                    | BlockDownloadClientSubcommand::EndDownload as u8;
519                payload[1..3].copy_from_slice(&crc.to_le_bytes());
520            }
521            SdoRequest::InitiateBlockUpload {
522                index: _,
523                sub: _,
524                blksize: _,
525                pst: _,
526            } => todo!(),
527            SdoRequest::EndBlockUpload => todo!(),
528            SdoRequest::StartBlockUpload => todo!(),
529            SdoRequest::ConfirmBlock {
530                ackseq: _,
531                blksize: _,
532            } => todo!(),
533        }
534        payload
535    }
536
537    /// Convert the request to a CanMessage using the provided COB ID
538    pub fn to_can_message(self, id: CanId) -> CanMessage {
539        let payload = self.to_bytes();
540        CanMessage::new(id, &payload)
541    }
542}
543
544impl TryFrom<&[u8]> for SdoRequest {
545    type Error = AbortCode;
546
547    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
548        if value.len() < 8 {
549            return Err(AbortCode::DataTypeMismatchLengthLow);
550        }
551        let ccs = value[0] >> 5;
552        let ccs: ClientCommand = match ccs.try_into() {
553            Ok(ccs) => ccs,
554            Err(_) => return Err(AbortCode::InvalidCommandSpecifier),
555        };
556
557        match ccs {
558            ClientCommand::DownloadSegment => {
559                let t = (value[0] & (1 << 4)) != 0;
560                let n = (value[0] >> 1) & 0x7;
561                let c = (value[0] & (1 << 0)) != 0;
562                let data = value[1..8].try_into().unwrap();
563                Ok(SdoRequest::DownloadSegment { t, n, c, data })
564            }
565            ClientCommand::InitiateDownload => {
566                let n = (value[0] >> 2) & 0x3;
567                let e = (value[0] & (1 << 1)) != 0;
568                let s = (value[0] & (1 << 0)) != 0;
569                let index = value[1] as u16 | ((value[2] as u16) << 8);
570                let sub = value[3];
571                let data = value[4..8].try_into().unwrap();
572                Ok(SdoRequest::InitiateDownload {
573                    n,
574                    e,
575                    s,
576                    index,
577                    sub,
578                    data,
579                })
580            }
581            ClientCommand::InitiateUpload => {
582                let index = value[1] as u16 | ((value[2] as u16) << 8);
583                let sub = value[3];
584                Ok(SdoRequest::InitiateUpload { index, sub })
585            }
586            ClientCommand::ReqUploadSegment => {
587                let t = (((value[0]) >> 4) & 1) != 0;
588                Ok(SdoRequest::ReqUploadSegment { t })
589            }
590            ClientCommand::Abort => {
591                let index = value[1] as u16 | ((value[2] as u16) << 8);
592                let sub = value[3];
593                let abort_code = u32::from_le_bytes(value[4..8].try_into().unwrap());
594                Ok(SdoRequest::Abort {
595                    index,
596                    sub,
597                    abort_code,
598                })
599            }
600            ClientCommand::BlockUpload => {
601                let csc = value[0] & 3;
602                let subcommand = match BlockUploadClientSubcommand::try_from(csc) {
603                    Ok(subcommand) => subcommand,
604                    Err(_) => return Err(AbortCode::InvalidCommandSpecifier),
605                };
606                match subcommand {
607                    BlockUploadClientSubcommand::InitiateUpload => {
608                        let index = value[1] as u16 | ((value[2] as u16) << 8);
609                        let sub = value[3];
610                        let blksize = value[4];
611                        let pst = value[5];
612                        Ok(SdoRequest::InitiateBlockUpload {
613                            index,
614                            sub,
615                            blksize,
616                            pst,
617                        })
618                    }
619                    BlockUploadClientSubcommand::EndUpload => Ok(SdoRequest::EndBlockUpload),
620                    BlockUploadClientSubcommand::ConfirmBlock => {
621                        let ackseq = value[1];
622                        let blksize = value[2];
623                        Ok(SdoRequest::ConfirmBlock { ackseq, blksize })
624                    }
625                    BlockUploadClientSubcommand::StartUpload => Ok(SdoRequest::StartBlockUpload),
626                }
627            }
628            ClientCommand::BlockDownload => {
629                let csc = value[0] & 0x1;
630                let subcommand = match BlockDownloadClientSubcommand::try_from(csc) {
631                    Ok(subcommand) => subcommand,
632                    Err(_) => return Err(AbortCode::InvalidCommandSpecifier),
633                };
634                match subcommand {
635                    BlockDownloadClientSubcommand::InitiateDownload => {
636                        let cc = (value[0] & (1 << 2)) != 0;
637                        let s = (value[0] & (1 << 1)) != 0;
638                        let index = value[1] as u16 | ((value[2] as u16) << 8);
639                        let sub = value[3];
640                        let size = u32::from_le_bytes(value[4..8].try_into().unwrap());
641                        Ok(SdoRequest::InitiateBlockDownload {
642                            cc,
643                            s,
644                            index,
645                            sub,
646
647                            size,
648                        })
649                    }
650                    BlockDownloadClientSubcommand::EndDownload => {
651                        let n = (value[0] >> 2) & 7;
652                        let crc = u16::from_le_bytes(value[1..3].try_into().unwrap());
653                        Ok(SdoRequest::EndBlockDownload { n, crc })
654                    }
655                }
656            }
657        }
658    }
659}
660
661/// Represents a response from SDO server to client
662#[derive(Copy, Clone, Debug, PartialEq)]
663#[cfg_attr(feature = "defmt", derive(defmt::Format))]
664pub enum SdoResponse {
665    /// Response to an [`SdoRequest::InitiateUpload`]
666    ConfirmUpload {
667        /// Number of unused bytes in data
668        n: u8,
669        /// Expedited flag
670        e: bool,
671        /// size flag
672        s: bool,
673        /// The index of the object being uploaded
674        index: u16,
675        /// The sub object being uploaded
676        sub: u8,
677        /// Value if e=1, or size if s=1
678        data: [u8; 4],
679    },
680    /// Send an upload segment
681    UploadSegment {
682        /// The toggle bit
683        t: bool,
684        /// The number of unused bytes in data
685        n: u8,
686        /// Flag indicating this is the final segment
687        c: bool,
688        /// object data
689        data: [u8; 7],
690    },
691    /// Response to a [`SdoRequest::InitiateDownload`]
692    ConfirmDownload {
693        /// The index of the object to be written to
694        index: u16,
695        /// The sub object to be written to
696        sub: u8,
697    },
698    /// Response to a [`SdoRequest::DownloadSegment`]
699    ConfirmDownloadSegment {
700        /// Toggle flag
701        t: bool,
702    },
703    /// Confirm a block download initiation
704    ConfirmBlockDownload {
705        /// Flag indicating server supports CRC generation
706        sc: bool,
707        /// Index of the object being downloaded
708        index: u16,
709        /// Sub index of the object being downloaded
710        sub: u8,
711        /// Number of segments for client to send in the next block
712        blksize: u8,
713    },
714    /// Confirm completion of a block
715    ConfirmBlock {
716        /// Sequence number of the last segment successfully received by the server
717        ackseq: u8,
718        /// Number of segments for the client to send in the next block
719        blksize: u8,
720    },
721    /// Confirm completion of a block download
722    ConfirmBlockDownloadEnd,
723    /// Confirm a block upload initiation
724    ConfirmBlockUpload {
725        /// Flag indicating server supports CRC on block transfer
726        sc: bool,
727        /// Size flag - indicates a valid size is stored in size field
728        s: bool,
729        /// Index of the object being uploaded
730        index: u16,
731        /// Sub index of the object being uploaded
732        sub: u8,
733        /// Size of the object to be uploaded
734        size: u32,
735    },
736    /// Send by server to end block upload
737    BlockUploadEnd {
738        /// Indicates the number of bytes in the last segment which are not valid
739        n: u8,
740        /// The CRC of the block upload. Valid only if both server and client indicated support for
741        /// CRC
742        crc: u16,
743    },
744    /// Sent by server to abort an ongoing transaction
745    Abort {
746        /// Object index of the active transfer
747        index: u16,
748        /// Sub object of the active transfer
749        sub: u8,
750        /// Abort reason
751        abort_code: u32,
752    },
753}
754
755impl TryFrom<CanMessage> for SdoResponse {
756    type Error = ();
757    fn try_from(msg: CanMessage) -> Result<Self, Self::Error> {
758        let scs = msg.data[0] >> 5;
759        let command: ServerCommand = scs.try_into()?;
760        match command {
761            ServerCommand::SegmentUpload => {
762                let t = (msg.data[0] & (1 << 4)) != 0;
763                let n = (msg.data[0] >> 1) & 7;
764                let c = (msg.data[0] & (1 << 0)) != 0;
765                let data: [u8; 7] = msg.data[1..8].try_into().unwrap();
766
767                Ok(SdoResponse::UploadSegment { t, n, c, data })
768            }
769            ServerCommand::SegmentDownload => {
770                let t = (msg.data[0] & (1 << 4)) != 0;
771                Ok(SdoResponse::ConfirmDownloadSegment { t })
772            }
773            ServerCommand::Upload => {
774                let n = (msg.data[0] >> 2) & 0x3;
775                let e = (msg.data[0] & (1 << 1)) != 0;
776                let s = (msg.data[0] & (1 << 0)) != 0;
777                let index = u16::from_le_bytes(msg.data[1..3].try_into().unwrap());
778                let sub = msg.data[3];
779                let data: [u8; 4] = msg.data[4..8].try_into().unwrap();
780                Ok(SdoResponse::ConfirmUpload {
781                    n,
782                    e,
783                    s,
784                    index,
785                    sub,
786                    data,
787                })
788            }
789            ServerCommand::Download => {
790                let index = u16::from_le_bytes(msg.data[1..3].try_into().unwrap());
791                let sub = msg.data[3];
792                Ok(SdoResponse::ConfirmDownload { index, sub })
793            }
794            ServerCommand::BlockDownload => {
795                match BlockDownloadServerSubcommand::try_from(msg.data[0] & 0x3)? {
796                    BlockDownloadServerSubcommand::ConfirmBlock => {
797                        let ackseq = msg.data[1];
798                        let blksize = msg.data[2];
799                        Ok(SdoResponse::ConfirmBlock { ackseq, blksize })
800                    }
801                    BlockDownloadServerSubcommand::InitiateDownloadAck => {
802                        let sc = (msg.data[0] & (1 << 2)) != 0;
803                        let index = u16::from_le_bytes(msg.data[1..3].try_into().unwrap());
804                        let sub = msg.data[3];
805                        let blksize = msg.data[4];
806                        Ok(SdoResponse::ConfirmBlockDownload {
807                            sc,
808                            index,
809                            sub,
810                            blksize,
811                        })
812                    }
813                    BlockDownloadServerSubcommand::EndDownloadAck => {
814                        Ok(SdoResponse::ConfirmBlockDownloadEnd)
815                    }
816                }
817            }
818            ServerCommand::BlockUpload => {
819                match BlockUploadServerSubcommand::try_from(msg.data[0] & 0x3)? {
820                    BlockUploadServerSubcommand::InitiateUpload => {
821                        let s = (msg.data[0] & (1 << 1)) != 0;
822                        let sc = (msg.data[0] & (1 << 2)) != 0;
823                        let index = u16::from_le_bytes(msg.data[1..3].try_into().unwrap());
824                        let sub = msg.data[3];
825                        let size = u32::from_le_bytes(msg.data[4..8].try_into().unwrap());
826                        Ok(SdoResponse::ConfirmBlockUpload {
827                            sc,
828                            s,
829                            index,
830                            sub,
831                            size,
832                        })
833                    }
834                    BlockUploadServerSubcommand::EndUpload => {
835                        let n = (msg.data[0] >> 2) & 7;
836                        let crc = u16::from_le_bytes(msg.data[1..3].try_into().unwrap());
837                        Ok(SdoResponse::BlockUploadEnd { n, crc })
838                    }
839                }
840            }
841            ServerCommand::Abort => {
842                let index = u16::from_le_bytes(msg.data[1..3].try_into().unwrap());
843                let sub = msg.data[3];
844                let abort_code = u32::from_le_bytes(msg.data[4..8].try_into().unwrap());
845                Ok(SdoResponse::Abort {
846                    index,
847                    sub,
848                    abort_code,
849                })
850            }
851        }
852    }
853}
854impl SdoResponse {
855    /// Create a `ConfirmUpload` response for an expedited upload
856    pub fn expedited_upload(index: u16, sub: u8, data: &[u8]) -> SdoResponse {
857        if data.len() > 4 {
858            panic!("Cannot create expedited upload with more than 4 bytes");
859        }
860
861        let mut msg_data = [0; 4];
862        msg_data[0..data.len()].copy_from_slice(data);
863
864        let s;
865        let n;
866        // For 0 length uploads, set the size bit to 0, to indicate that this is an empty response.
867        // It's not clear if this is CiA301 compatible, but it is how zencan does it!
868        if data.is_empty() {
869            s = false;
870            n = 0;
871        } else {
872            s = true;
873            n = 4 - data.len() as u8;
874        }
875        SdoResponse::ConfirmUpload {
876            index,
877            sub,
878            e: true,
879            s,
880            n,
881            data: msg_data,
882        }
883    }
884
885    /// Create a `ConfirmUpload` response for a segmented upload
886    pub fn upload_acknowledge(index: u16, sub: u8, size: Option<u32>) -> SdoResponse {
887        SdoResponse::ConfirmUpload {
888            n: 0,
889            e: false,
890            s: size.is_some(),
891            index,
892            sub,
893            data: size.unwrap_or(0).to_le_bytes(),
894        }
895    }
896
897    /// Create an `UploadSegment` response
898    pub fn upload_segment(t: bool, c: bool, data: &[u8]) -> SdoResponse {
899        let n = (7 - data.len()) as u8;
900        let mut buf = [0; 7];
901        buf[0..data.len()].copy_from_slice(data);
902        SdoResponse::UploadSegment { t, n, c, data: buf }
903    }
904
905    /// Create a `ConfirmDownload` response
906    pub fn download_acknowledge(index: u16, sub: u8) -> SdoResponse {
907        SdoResponse::ConfirmDownload { index, sub }
908    }
909
910    /// Create a `ConfirmDownloadSegment` response
911    pub fn download_segment_acknowledge(t: bool) -> SdoResponse {
912        SdoResponse::ConfirmDownloadSegment { t }
913    }
914
915    /// Create a ConfirmBlockDownload response
916    pub fn block_download_acknowledge(sc: bool, index: u16, sub: u8, blksize: u8) -> SdoResponse {
917        SdoResponse::ConfirmBlockDownload {
918            sc,
919            index,
920            sub,
921            blksize,
922        }
923    }
924
925    /// Create a ConfirmBlock response
926    pub fn confirm_block(ackseq: u8, blksize: u8) -> SdoResponse {
927        SdoResponse::ConfirmBlock { ackseq, blksize }
928    }
929
930    /// Create an abort response
931    pub fn abort(index: u16, sub: u8, abort_code: AbortCode) -> SdoResponse {
932        let abort_code = abort_code as u32;
933        SdoResponse::Abort {
934            index,
935            sub,
936            abort_code,
937        }
938    }
939
940    /// Convert the response to a [CanMessage] using the provided COB ID
941    pub fn to_can_message(self, id: CanId) -> CanMessage {
942        let mut payload = [0; 8];
943
944        match self {
945            SdoResponse::ConfirmUpload {
946                n,
947                e,
948                s,
949                index,
950                sub,
951                data,
952            } => {
953                payload[0] = ((ServerCommand::Upload as u8) << 5)
954                    | ((n & 0x3) << 2)
955                    | ((e as u8) << 1)
956                    | (s as u8);
957                payload[1] = (index & 0xff) as u8;
958                payload[2] = (index >> 8) as u8;
959                payload[3] = sub;
960                payload[4..8].copy_from_slice(&data);
961            }
962            SdoResponse::ConfirmDownload { index, sub } => {
963                payload[0] = (ServerCommand::Download as u8) << 5;
964                payload[1] = (index & 0xff) as u8;
965                payload[2] = (index >> 8) as u8;
966                payload[3] = sub;
967            }
968            SdoResponse::UploadSegment { t, n, c, data } => {
969                payload[0] = ((ServerCommand::SegmentUpload as u8) << 5)
970                    | ((t as u8) << 4)
971                    | (n << 1)
972                    | c as u8;
973                payload[1..8].copy_from_slice(&data);
974            }
975            SdoResponse::ConfirmBlockDownload {
976                sc,
977                index,
978                sub,
979                blksize,
980            } => {
981                payload[0] = ((ServerCommand::BlockDownload as u8) << 5)
982                    | ((sc as u8) << 2)
983                    | (BlockDownloadServerSubcommand::InitiateDownloadAck as u8);
984                payload[1] = (index & 0xff) as u8;
985                payload[2] = (index >> 8) as u8;
986                payload[3] = sub;
987                payload[4] = blksize;
988            }
989            SdoResponse::ConfirmBlock { ackseq, blksize } => {
990                payload[0] = ((ServerCommand::BlockDownload as u8) << 5)
991                    | (BlockDownloadServerSubcommand::ConfirmBlock as u8);
992                payload[1] = ackseq;
993                payload[2] = blksize;
994            }
995            SdoResponse::ConfirmBlockDownloadEnd => {
996                payload[0] = ((ServerCommand::BlockDownload as u8) << 5)
997                    | (BlockDownloadServerSubcommand::EndDownloadAck as u8);
998            }
999            SdoResponse::ConfirmDownloadSegment { t } => {
1000                payload[0] = ((ServerCommand::SegmentDownload as u8) << 5) | ((t as u8) << 4);
1001            }
1002            SdoResponse::Abort {
1003                index,
1004                sub,
1005                abort_code,
1006            } => {
1007                payload[0] = (ServerCommand::Abort as u8) << 5;
1008                payload[1] = (index & 0xff) as u8;
1009                payload[2] = (index >> 8) as u8;
1010                payload[3] = sub;
1011                payload[4..8].copy_from_slice(&abort_code.to_le_bytes());
1012            }
1013            SdoResponse::ConfirmBlockUpload {
1014                sc: _,
1015                s: _,
1016                index: _,
1017                sub: _,
1018                size: _,
1019            } => todo!(),
1020            SdoResponse::BlockUploadEnd { n: _, crc: _ } => todo!(),
1021        }
1022        CanMessage::new(id, &payload)
1023    }
1024}