1use heapless::Vec;
39
40use crate::datatypes::{DataType, Value};
41use crate::object_dictionary::Address;
42use crate::types::NodeId;
43use crate::{Error, Result};
44
45pub mod block;
46pub mod client;
47pub mod server;
48
49pub use client::{SdoClient, SdoEvent};
50pub use server::SdoServer;
51
52pub const SDO_REQUEST_COB_BASE: u16 = 0x600;
54pub const SDO_RESPONSE_COB_BASE: u16 = 0x580;
56
57pub type SdoPayload = [u8; 8];
59
60pub const fn request_cob_id(node: NodeId) -> u16 {
62 SDO_REQUEST_COB_BASE + node.raw() as u16
63}
64
65pub const fn response_cob_id(node: NodeId) -> u16 {
67 SDO_RESPONSE_COB_BASE + node.raw() as u16
68}
69
70const CCS_DOWNLOAD_SEGMENT: u8 = 0x00; const CCS_DOWNLOAD_INITIATE: u8 = 0x20; const CCS_UPLOAD_INITIATE: u8 = 0x40; const CCS_UPLOAD_SEGMENT: u8 = 0x60; const SCS_DOWNLOAD_SEGMENT: u8 = 0x20; const SCS_UPLOAD_INITIATE: u8 = 0x40; const SCS_DOWNLOAD_INITIATE: u8 = 0x60; const CS_ABORT: u8 = 0x80; const CS_MASK: u8 = 0xE0;
84
85const EXPEDITED: u8 = 0x02; const SIZE_INDICATED: u8 = 0x01; const EXPEDITED_SIZED: u8 = EXPEDITED | SIZE_INDICATED; const TOGGLE: u8 = 0x10; const NO_MORE_SEGMENTS: u8 = 0x01; pub const SEGMENT_DATA_MAX: usize = 7;
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98#[non_exhaustive]
99pub enum SdoAbortCode {
100 ToggleBitNotAlternated = 0x0503_0000,
102 ProtocolTimedOut = 0x0504_0000,
104 CommandInvalid = 0x0504_0001,
106 UnsupportedAccess = 0x0601_0000,
108 ReadOfWriteOnly = 0x0601_0001,
110 WriteOfReadOnly = 0x0601_0002,
112 ObjectDoesNotExist = 0x0602_0000,
114 DataTypeMismatchLengthHigh = 0x0607_0012,
116 DataTypeMismatchLengthLow = 0x0607_0013,
118 SubIndexDoesNotExist = 0x0609_0011,
120 General = 0x0800_0000,
122}
123
124pub fn encode_download_expedited(addr: Address, value: &Value) -> Result<SdoPayload> {
130 let len = value.size();
131 if len == 0 || len > 4 {
134 return Err(Error::UnsupportedTransfer);
135 }
136 let mut p = [0u8; 8];
137 p[0] = CCS_DOWNLOAD_INITIATE | (((4 - len) as u8) << 2) | EXPEDITED_SIZED;
139 p[1..3].copy_from_slice(&addr.index.to_le_bytes());
140 p[3] = addr.subindex;
141 value.encode_le(&mut p[4..4 + len])?;
142 Ok(p)
143}
144
145pub fn encode_download_response(addr: Address) -> SdoPayload {
147 let mut p = [0u8; 8];
148 p[0] = SCS_DOWNLOAD_INITIATE;
149 p[1..3].copy_from_slice(&addr.index.to_le_bytes());
150 p[3] = addr.subindex;
151 p
152}
153
154pub fn decode_download_response(p: &SdoPayload) -> Result<Address> {
156 if p[0] != SCS_DOWNLOAD_INITIATE {
157 return Err(Error::UnexpectedCommand);
158 }
159 Ok(Address::new(u16::from_le_bytes([p[1], p[2]]), p[3]))
160}
161
162pub fn encode_upload_request(addr: Address) -> SdoPayload {
164 let mut p = [0u8; 8];
165 p[0] = CCS_UPLOAD_INITIATE;
166 p[1..3].copy_from_slice(&addr.index.to_le_bytes());
167 p[3] = addr.subindex;
168 p
169}
170
171pub fn encode_upload_expedited_response(addr: Address, value: &Value) -> Result<SdoPayload> {
176 let len = value.size();
177 if len == 0 || len > 4 {
179 return Err(Error::UnsupportedTransfer);
180 }
181 let mut p = [0u8; 8];
182 p[0] = SCS_UPLOAD_INITIATE | (((4 - len) as u8) << 2) | EXPEDITED_SIZED;
183 p[1..3].copy_from_slice(&addr.index.to_le_bytes());
184 p[3] = addr.subindex;
185 value.encode_le(&mut p[4..4 + len])?;
186 Ok(p)
187}
188
189pub fn decode_upload_expedited_response(
197 p: &SdoPayload,
198 data_type: DataType,
199) -> Result<(Address, Value)> {
200 let cmd = p[0];
201 if cmd & 0xE0 != SCS_UPLOAD_INITIATE || cmd & EXPEDITED_SIZED != EXPEDITED_SIZED {
203 return Err(Error::UnexpectedCommand);
204 }
205 let n = (cmd >> 2) & 0x03;
206 let len = 4 - n as usize;
207 if let Some(fixed) = data_type.fixed_size() {
210 if len != fixed {
211 return Err(Error::TypeMismatch);
212 }
213 }
214 let addr = Address::new(u16::from_le_bytes([p[1], p[2]]), p[3]);
215 let value = Value::decode_le(data_type, &p[4..4 + len])?;
216 Ok((addr, value))
217}
218
219pub fn encode_abort(addr: Address, code: SdoAbortCode) -> SdoPayload {
221 let mut p = [0u8; 8];
222 p[0] = CS_ABORT;
223 p[1..3].copy_from_slice(&addr.index.to_le_bytes());
224 p[3] = addr.subindex;
225 p[4..8].copy_from_slice(&(code as u32).to_le_bytes());
226 p
227}
228
229pub fn decode_abort(p: &SdoPayload) -> Result<(Address, u32)> {
231 if p[0] != CS_ABORT {
232 return Err(Error::UnexpectedCommand);
233 }
234 let addr = Address::new(u16::from_le_bytes([p[1], p[2]]), p[3]);
235 let code = u32::from_le_bytes([p[4], p[5], p[6], p[7]]);
236 Ok((addr, code))
237}
238
239#[derive(Debug, Clone, Copy, PartialEq, Eq)]
259pub struct Segment<'a> {
260 pub toggle: bool,
262 pub last: bool,
264 pub data: &'a [u8],
266}
267
268pub fn encode_download_initiate_segmented(addr: Address, size: u32) -> SdoPayload {
271 let mut p = [0u8; 8];
272 p[0] = CCS_DOWNLOAD_INITIATE | SIZE_INDICATED;
273 p[1..3].copy_from_slice(&addr.index.to_le_bytes());
274 p[3] = addr.subindex;
275 p[4..8].copy_from_slice(&size.to_le_bytes());
276 p
277}
278
279pub fn decode_download_initiate_segmented(p: &SdoPayload) -> Result<(Address, u32)> {
282 if p[0] & CS_MASK != CCS_DOWNLOAD_INITIATE
283 || p[0] & EXPEDITED != 0
284 || p[0] & SIZE_INDICATED == 0
285 {
286 return Err(Error::UnexpectedCommand);
287 }
288 let addr = Address::new(u16::from_le_bytes([p[1], p[2]]), p[3]);
289 Ok((addr, u32::from_le_bytes([p[4], p[5], p[6], p[7]])))
290}
291
292pub fn encode_upload_initiate_segmented_response(addr: Address, size: u32) -> SdoPayload {
295 let mut p = [0u8; 8];
296 p[0] = SCS_UPLOAD_INITIATE | SIZE_INDICATED;
297 p[1..3].copy_from_slice(&addr.index.to_le_bytes());
298 p[3] = addr.subindex;
299 p[4..8].copy_from_slice(&size.to_le_bytes());
300 p
301}
302
303pub fn decode_upload_initiate_segmented_response(p: &SdoPayload) -> Result<(Address, u32)> {
309 if p[0] & CS_MASK != SCS_UPLOAD_INITIATE || p[0] & EXPEDITED != 0 || p[0] & SIZE_INDICATED == 0
310 {
311 return Err(Error::UnexpectedCommand);
312 }
313 let addr = Address::new(u16::from_le_bytes([p[1], p[2]]), p[3]);
314 Ok((addr, u32::from_le_bytes([p[4], p[5], p[6], p[7]])))
315}
316
317pub fn encode_data_segment(data: &[u8], toggle: bool, last: bool) -> Result<SdoPayload> {
325 if data.len() > SEGMENT_DATA_MAX {
326 return Err(Error::BadLength);
327 }
328 let mut p = [0u8; 8];
329 let n = (SEGMENT_DATA_MAX - data.len()) as u8;
331 p[0] = (n << 1) & 0x0E;
332 if toggle {
333 p[0] |= TOGGLE;
334 }
335 if last {
336 p[0] |= NO_MORE_SEGMENTS;
337 }
338 p[1..1 + data.len()].copy_from_slice(data);
339 Ok(p)
340}
341
342pub fn decode_data_segment(p: &SdoPayload) -> Result<Segment<'_>> {
344 if p[0] & CS_MASK != CCS_DOWNLOAD_SEGMENT {
345 return Err(Error::UnexpectedCommand);
346 }
347 let n = ((p[0] >> 1) & 0x07) as usize;
348 if n > SEGMENT_DATA_MAX {
349 return Err(Error::BadLength);
350 }
351 Ok(Segment {
352 toggle: p[0] & TOGGLE != 0,
353 last: p[0] & NO_MORE_SEGMENTS != 0,
354 data: &p[1..1 + (SEGMENT_DATA_MAX - n)],
355 })
356}
357
358pub fn encode_download_segment_response(toggle: bool) -> SdoPayload {
361 let mut p = [0u8; 8];
362 p[0] = SCS_DOWNLOAD_SEGMENT | if toggle { TOGGLE } else { 0 };
363 p
364}
365
366pub fn decode_download_segment_response(p: &SdoPayload) -> Result<bool> {
368 if p[0] & CS_MASK != SCS_DOWNLOAD_SEGMENT {
369 return Err(Error::UnexpectedCommand);
370 }
371 Ok(p[0] & TOGGLE != 0)
372}
373
374pub fn encode_upload_segment_request(toggle: bool) -> SdoPayload {
377 let mut p = [0u8; 8];
378 p[0] = CCS_UPLOAD_SEGMENT | if toggle { TOGGLE } else { 0 };
379 p
380}
381
382pub fn decode_upload_segment_request(p: &SdoPayload) -> Result<bool> {
384 if p[0] & CS_MASK != CCS_UPLOAD_SEGMENT {
385 return Err(Error::UnexpectedCommand);
386 }
387 Ok(p[0] & TOGGLE != 0)
388}
389
390#[derive(Debug)]
394pub struct SegmentWriter<'a> {
395 data: &'a [u8],
396 pos: usize,
397 toggle: bool,
398}
399
400impl<'a> SegmentWriter<'a> {
401 pub const fn new(data: &'a [u8]) -> Self {
404 Self {
405 data,
406 pos: 0,
407 toggle: false,
408 }
409 }
410
411 pub const fn is_done(&self) -> bool {
413 self.pos >= self.data.len()
414 }
415
416 pub fn next_segment(&mut self) -> Option<SdoPayload> {
418 if self.is_done() {
419 return None;
420 }
421 let remaining = self.data.len() - self.pos;
422 let len = remaining.min(SEGMENT_DATA_MAX);
423 let last = remaining <= SEGMENT_DATA_MAX;
424 let frame = encode_data_segment(&self.data[self.pos..self.pos + len], self.toggle, last)
425 .expect("len is 1..=7");
426 self.pos += len;
427 self.toggle = !self.toggle;
428 Some(frame)
429 }
430}
431
432#[derive(Debug)]
436pub struct SegmentReader<const N: usize> {
437 buf: Vec<u8, N>,
438 toggle: bool,
439 done: bool,
440}
441
442impl<const N: usize> Default for SegmentReader<N> {
443 fn default() -> Self {
444 Self::new()
445 }
446}
447
448impl<const N: usize> SegmentReader<N> {
449 pub const fn new() -> Self {
451 Self {
452 buf: Vec::new(),
453 toggle: false,
454 done: false,
455 }
456 }
457
458 pub const fn is_done(&self) -> bool {
460 self.done
461 }
462
463 pub fn data(&self) -> &[u8] {
465 &self.buf
466 }
467
468 pub fn push(&mut self, segment: &Segment) -> Result<()> {
474 if self.done {
475 return Err(Error::UnexpectedCommand);
476 }
477 if segment.toggle != self.toggle {
478 return Err(Error::ToggleMismatch);
479 }
480 self.buf
481 .extend_from_slice(segment.data)
482 .map_err(|_| Error::Overflow)?;
483 self.toggle = !self.toggle;
484 if segment.last {
485 self.done = true;
486 }
487 Ok(())
488 }
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494
495 #[test]
497 fn cob_ids_follow_convention() {
498 let node = NodeId::new(0x05).unwrap();
499 assert_eq!(request_cob_id(node), 0x605);
500 assert_eq!(response_cob_id(node), 0x585);
501 }
502
503 #[test]
508 fn download_u32_matches_known_frame() {
509 let f = encode_download_expedited(Address::new(0x2000, 0), &Value::Unsigned32(0x1234_5678))
510 .unwrap();
511 assert_eq!(f, [0x23, 0x00, 0x20, 0x00, 0x78, 0x56, 0x34, 0x12]);
512 }
513
514 #[test]
517 fn download_u8_matches_known_frame() {
518 let f =
519 encode_download_expedited(Address::new(0x2001, 0), &Value::Unsigned8(0x7F)).unwrap();
520 assert_eq!(f, [0x2F, 0x01, 0x20, 0x00, 0x7F, 0x00, 0x00, 0x00]);
521 }
522
523 #[test]
524 fn download_i16_matches_known_frame() {
525 let f = encode_download_expedited(Address::new(0x6000, 1), &Value::Integer16(-2)).unwrap();
527 assert_eq!(f, [0x2B, 0x00, 0x60, 0x01, 0xFE, 0xFF, 0x00, 0x00]);
528 }
529
530 #[test]
531 fn download_response_roundtrips() {
532 let f = encode_download_response(Address::new(0x2000, 0));
533 assert_eq!(f, [0x60, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00]);
534 assert_eq!(
535 decode_download_response(&f).unwrap(),
536 Address::new(0x2000, 0)
537 );
538 }
539
540 #[test]
541 fn value_too_large_for_expedited_rejected() {
542 assert_eq!(
543 encode_download_expedited(Address::new(0x2000, 0), &Value::Unsigned64(1)),
544 Err(Error::UnsupportedTransfer)
545 );
546 }
547
548 #[test]
551 fn upload_request_matches_known_frame() {
552 let f = encode_upload_request(Address::new(0x1000, 0));
553 assert_eq!(f, [0x40, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00]);
554 }
555
556 #[test]
560 fn upload_response_device_type_decodes() {
561 let f = [0x43, 0x00, 0x10, 0x00, 0x92, 0x01, 0x00, 0x00];
562 let (addr, value) = decode_upload_expedited_response(&f, DataType::Unsigned32).unwrap();
563 assert_eq!(addr, Address::new(0x1000, 0));
564 assert_eq!(value, Value::Unsigned32(0x0000_0192));
565 }
566
567 #[test]
568 fn upload_response_encode_matches_known_frame() {
569 let f = encode_upload_expedited_response(
570 Address::new(0x1000, 0),
571 &Value::Unsigned32(0x0000_0192),
572 )
573 .unwrap();
574 assert_eq!(f, [0x43, 0x00, 0x10, 0x00, 0x92, 0x01, 0x00, 0x00]);
575 }
576
577 #[test]
578 fn upload_response_wrong_type_size_errors() {
579 let f = [0x43, 0x00, 0x10, 0x00, 0x92, 0x01, 0x00, 0x00];
581 assert_eq!(
582 decode_upload_expedited_response(&f, DataType::Unsigned16),
583 Err(Error::TypeMismatch)
584 );
585 }
586
587 #[test]
588 fn decode_upload_rejects_non_upload_frame() {
589 let f = encode_download_response(Address::new(0x1000, 0));
590 assert_eq!(
591 decode_upload_expedited_response(&f, DataType::Unsigned32),
592 Err(Error::UnexpectedCommand)
593 );
594 }
595
596 #[test]
600 fn abort_object_missing_matches_known_frame() {
601 let f = encode_abort(Address::new(0x1000, 0), SdoAbortCode::ObjectDoesNotExist);
602 assert_eq!(f, [0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x06]);
603 let (addr, code) = decode_abort(&f).unwrap();
604 assert_eq!(addr, Address::new(0x1000, 0));
605 assert_eq!(code, 0x0602_0000);
606 }
607
608 #[test]
613 fn download_initiate_segmented_matches_known_frame() {
614 let f = encode_download_initiate_segmented(Address::new(0x2000, 0), 20);
615 assert_eq!(f, [0x21, 0x00, 0x20, 0x00, 20, 0x00, 0x00, 0x00]);
616 assert_eq!(
617 decode_download_initiate_segmented(&f).unwrap(),
618 (Address::new(0x2000, 0), 20)
619 );
620 }
621
622 #[test]
625 fn upload_initiate_segmented_response_matches_known_frame() {
626 let f = encode_upload_initiate_segmented_response(Address::new(0x2000, 0), 20);
627 assert_eq!(f, [0x41, 0x00, 0x20, 0x00, 20, 0x00, 0x00, 0x00]);
628 assert_eq!(
629 decode_upload_initiate_segmented_response(&f).unwrap(),
630 (Address::new(0x2000, 0), 20)
631 );
632 }
633
634 #[test]
636 fn segmented_initiate_rejects_expedited_frame() {
637 let expedited = [0x43, 0x00, 0x10, 0x00, 0x92, 0x01, 0x00, 0x00];
638 assert_eq!(
639 decode_upload_initiate_segmented_response(&expedited),
640 Err(Error::UnexpectedCommand)
641 );
642 }
643
644 #[test]
647 fn data_segment_full_matches_known_frame() {
648 let f = encode_data_segment(&[1, 2, 3, 4, 5, 6, 7], false, false).unwrap();
649 assert_eq!(f, [0x00, 1, 2, 3, 4, 5, 6, 7]);
650 }
651
652 #[test]
655 fn data_segment_last_matches_known_frame() {
656 let f = encode_data_segment(&[0xAA, 0xBB, 0xCC], true, true).unwrap();
657 assert_eq!(f, [0x19, 0xAA, 0xBB, 0xCC, 0x00, 0x00, 0x00, 0x00]);
658 let seg = decode_data_segment(&f).unwrap();
659 assert!(seg.toggle);
660 assert!(seg.last);
661 assert_eq!(seg.data, &[0xAA, 0xBB, 0xCC]);
662 }
663
664 #[test]
665 fn segment_ack_and_poll_toggle_roundtrip() {
666 assert_eq!(
667 encode_download_segment_response(false),
668 [0x20, 0, 0, 0, 0, 0, 0, 0]
669 );
670 assert_eq!(
671 encode_download_segment_response(true),
672 [0x30, 0, 0, 0, 0, 0, 0, 0]
673 );
674 assert!(decode_download_segment_response(&encode_download_segment_response(true)).unwrap());
675
676 assert_eq!(
677 encode_upload_segment_request(false),
678 [0x60, 0, 0, 0, 0, 0, 0, 0]
679 );
680 assert_eq!(
681 encode_upload_segment_request(true),
682 [0x70, 0, 0, 0, 0, 0, 0, 0]
683 );
684 assert!(decode_upload_segment_request(&encode_upload_segment_request(true)).unwrap());
685 }
686
687 #[test]
691 fn segment_writer_reader_roundtrip() {
692 let payload: [u8; 12] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120];
693 let mut writer = SegmentWriter::new(&payload);
694 let mut reader = SegmentReader::<64>::new();
695
696 let f1 = writer.next_segment().unwrap();
697 assert_eq!(f1[0] & TOGGLE, 0); reader.push(&decode_data_segment(&f1).unwrap()).unwrap();
699 assert!(!reader.is_done());
700
701 let f2 = writer.next_segment().unwrap();
702 assert_ne!(f2[0] & TOGGLE, 0); reader.push(&decode_data_segment(&f2).unwrap()).unwrap();
704
705 assert!(reader.is_done());
706 assert!(writer.next_segment().is_none());
707 assert_eq!(reader.data(), &payload);
708 }
709
710 #[test]
711 fn reader_rejects_toggle_out_of_sequence() {
712 let mut reader = SegmentReader::<16>::new();
713 reader
715 .push(&Segment {
716 toggle: false,
717 last: false,
718 data: &[1, 2, 3],
719 })
720 .unwrap();
721 assert_eq!(
722 reader.push(&Segment {
723 toggle: false,
724 last: true,
725 data: &[4, 5]
726 }),
727 Err(Error::ToggleMismatch)
728 );
729 }
730
731 #[test]
732 fn reader_overflow_is_reported() {
733 let mut reader = SegmentReader::<4>::new();
734 assert_eq!(
735 reader.push(&Segment {
736 toggle: false,
737 last: false,
738 data: &[1, 2, 3, 4, 5]
739 }),
740 Err(Error::Overflow)
741 );
742 }
743}