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 client;
46pub mod server;
47
48pub use client::{SdoClient, SdoEvent};
49pub use server::SdoServer;
50
51pub const SDO_REQUEST_COB_BASE: u16 = 0x600;
53pub const SDO_RESPONSE_COB_BASE: u16 = 0x580;
55
56pub type SdoPayload = [u8; 8];
58
59pub const fn request_cob_id(node: NodeId) -> u16 {
61 SDO_REQUEST_COB_BASE + node.raw() as u16
62}
63
64pub const fn response_cob_id(node: NodeId) -> u16 {
66 SDO_RESPONSE_COB_BASE + node.raw() as u16
67}
68
69const 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;
83
84const 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;
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97#[non_exhaustive]
98pub enum SdoAbortCode {
99 ToggleBitNotAlternated = 0x0503_0000,
101 ProtocolTimedOut = 0x0504_0000,
103 CommandInvalid = 0x0504_0001,
105 UnsupportedAccess = 0x0601_0000,
107 ReadOfWriteOnly = 0x0601_0001,
109 WriteOfReadOnly = 0x0601_0002,
111 ObjectDoesNotExist = 0x0602_0000,
113 DataTypeMismatchLengthHigh = 0x0607_0012,
115 DataTypeMismatchLengthLow = 0x0607_0013,
117 SubIndexDoesNotExist = 0x0609_0011,
119 General = 0x0800_0000,
121}
122
123pub fn encode_download_expedited(addr: Address, value: &Value) -> Result<SdoPayload> {
129 let len = value.size();
130 if len == 0 || len > 4 {
131 return Err(Error::UnsupportedTransfer);
132 }
133 let mut p = [0u8; 8];
134 p[0] = CCS_DOWNLOAD_INITIATE | (((4 - len) as u8) << 2) | EXPEDITED_SIZED;
136 p[1..3].copy_from_slice(&addr.index.to_le_bytes());
137 p[3] = addr.subindex;
138 value.encode_le(&mut p[4..4 + len])?;
139 Ok(p)
140}
141
142pub fn encode_download_response(addr: Address) -> SdoPayload {
144 let mut p = [0u8; 8];
145 p[0] = SCS_DOWNLOAD_INITIATE;
146 p[1..3].copy_from_slice(&addr.index.to_le_bytes());
147 p[3] = addr.subindex;
148 p
149}
150
151pub fn decode_download_response(p: &SdoPayload) -> Result<Address> {
153 if p[0] != SCS_DOWNLOAD_INITIATE {
154 return Err(Error::UnexpectedCommand);
155 }
156 Ok(Address::new(u16::from_le_bytes([p[1], p[2]]), p[3]))
157}
158
159pub fn encode_upload_request(addr: Address) -> SdoPayload {
161 let mut p = [0u8; 8];
162 p[0] = CCS_UPLOAD_INITIATE;
163 p[1..3].copy_from_slice(&addr.index.to_le_bytes());
164 p[3] = addr.subindex;
165 p
166}
167
168pub fn encode_upload_expedited_response(addr: Address, value: &Value) -> Result<SdoPayload> {
173 let len = value.size();
174 if len == 0 || len > 4 {
175 return Err(Error::UnsupportedTransfer);
176 }
177 let mut p = [0u8; 8];
178 p[0] = SCS_UPLOAD_INITIATE | (((4 - len) as u8) << 2) | EXPEDITED_SIZED;
179 p[1..3].copy_from_slice(&addr.index.to_le_bytes());
180 p[3] = addr.subindex;
181 value.encode_le(&mut p[4..4 + len])?;
182 Ok(p)
183}
184
185pub fn decode_upload_expedited_response(
193 p: &SdoPayload,
194 data_type: DataType,
195) -> Result<(Address, Value)> {
196 let cmd = p[0];
197 if cmd & 0xE0 != SCS_UPLOAD_INITIATE || cmd & EXPEDITED_SIZED != EXPEDITED_SIZED {
199 return Err(Error::UnexpectedCommand);
200 }
201 let n = (cmd >> 2) & 0x03;
202 let len = 4 - n as usize;
203 if len != data_type.size() {
204 return Err(Error::TypeMismatch);
205 }
206 let addr = Address::new(u16::from_le_bytes([p[1], p[2]]), p[3]);
207 let value = Value::decode_le(data_type, &p[4..4 + len])?;
208 Ok((addr, value))
209}
210
211pub fn encode_abort(addr: Address, code: SdoAbortCode) -> SdoPayload {
213 let mut p = [0u8; 8];
214 p[0] = CS_ABORT;
215 p[1..3].copy_from_slice(&addr.index.to_le_bytes());
216 p[3] = addr.subindex;
217 p[4..8].copy_from_slice(&(code as u32).to_le_bytes());
218 p
219}
220
221pub fn decode_abort(p: &SdoPayload) -> Result<(Address, u32)> {
223 if p[0] != CS_ABORT {
224 return Err(Error::UnexpectedCommand);
225 }
226 let addr = Address::new(u16::from_le_bytes([p[1], p[2]]), p[3]);
227 let code = u32::from_le_bytes([p[4], p[5], p[6], p[7]]);
228 Ok((addr, code))
229}
230
231#[derive(Debug, Clone, Copy, PartialEq, Eq)]
251pub struct Segment<'a> {
252 pub toggle: bool,
254 pub last: bool,
256 pub data: &'a [u8],
258}
259
260pub fn encode_download_initiate_segmented(addr: Address, size: u32) -> SdoPayload {
263 let mut p = [0u8; 8];
264 p[0] = CCS_DOWNLOAD_INITIATE | SIZE_INDICATED;
265 p[1..3].copy_from_slice(&addr.index.to_le_bytes());
266 p[3] = addr.subindex;
267 p[4..8].copy_from_slice(&size.to_le_bytes());
268 p
269}
270
271pub fn decode_download_initiate_segmented(p: &SdoPayload) -> Result<(Address, u32)> {
274 if p[0] & CS_MASK != CCS_DOWNLOAD_INITIATE
275 || p[0] & EXPEDITED != 0
276 || p[0] & SIZE_INDICATED == 0
277 {
278 return Err(Error::UnexpectedCommand);
279 }
280 let addr = Address::new(u16::from_le_bytes([p[1], p[2]]), p[3]);
281 Ok((addr, u32::from_le_bytes([p[4], p[5], p[6], p[7]])))
282}
283
284pub fn encode_upload_initiate_segmented_response(addr: Address, size: u32) -> SdoPayload {
287 let mut p = [0u8; 8];
288 p[0] = SCS_UPLOAD_INITIATE | SIZE_INDICATED;
289 p[1..3].copy_from_slice(&addr.index.to_le_bytes());
290 p[3] = addr.subindex;
291 p[4..8].copy_from_slice(&size.to_le_bytes());
292 p
293}
294
295pub fn decode_upload_initiate_segmented_response(p: &SdoPayload) -> Result<(Address, u32)> {
301 if p[0] & CS_MASK != SCS_UPLOAD_INITIATE || p[0] & EXPEDITED != 0 || p[0] & SIZE_INDICATED == 0
302 {
303 return Err(Error::UnexpectedCommand);
304 }
305 let addr = Address::new(u16::from_le_bytes([p[1], p[2]]), p[3]);
306 Ok((addr, u32::from_le_bytes([p[4], p[5], p[6], p[7]])))
307}
308
309pub fn encode_data_segment(data: &[u8], toggle: bool, last: bool) -> Result<SdoPayload> {
316 if data.is_empty() || data.len() > SEGMENT_DATA_MAX {
317 return Err(Error::BadLength);
318 }
319 let mut p = [0u8; 8];
320 let n = (SEGMENT_DATA_MAX - data.len()) as u8;
322 p[0] = (n << 1) & 0x0E;
323 if toggle {
324 p[0] |= TOGGLE;
325 }
326 if last {
327 p[0] |= NO_MORE_SEGMENTS;
328 }
329 p[1..1 + data.len()].copy_from_slice(data);
330 Ok(p)
331}
332
333pub fn decode_data_segment(p: &SdoPayload) -> Result<Segment<'_>> {
335 if p[0] & CS_MASK != CCS_DOWNLOAD_SEGMENT {
336 return Err(Error::UnexpectedCommand);
337 }
338 let n = ((p[0] >> 1) & 0x07) as usize;
339 if n > SEGMENT_DATA_MAX {
340 return Err(Error::BadLength);
341 }
342 Ok(Segment {
343 toggle: p[0] & TOGGLE != 0,
344 last: p[0] & NO_MORE_SEGMENTS != 0,
345 data: &p[1..1 + (SEGMENT_DATA_MAX - n)],
346 })
347}
348
349pub fn encode_download_segment_response(toggle: bool) -> SdoPayload {
352 let mut p = [0u8; 8];
353 p[0] = SCS_DOWNLOAD_SEGMENT | if toggle { TOGGLE } else { 0 };
354 p
355}
356
357pub fn decode_download_segment_response(p: &SdoPayload) -> Result<bool> {
359 if p[0] & CS_MASK != SCS_DOWNLOAD_SEGMENT {
360 return Err(Error::UnexpectedCommand);
361 }
362 Ok(p[0] & TOGGLE != 0)
363}
364
365pub fn encode_upload_segment_request(toggle: bool) -> SdoPayload {
368 let mut p = [0u8; 8];
369 p[0] = CCS_UPLOAD_SEGMENT | if toggle { TOGGLE } else { 0 };
370 p
371}
372
373pub fn decode_upload_segment_request(p: &SdoPayload) -> Result<bool> {
375 if p[0] & CS_MASK != CCS_UPLOAD_SEGMENT {
376 return Err(Error::UnexpectedCommand);
377 }
378 Ok(p[0] & TOGGLE != 0)
379}
380
381#[derive(Debug)]
385pub struct SegmentWriter<'a> {
386 data: &'a [u8],
387 pos: usize,
388 toggle: bool,
389}
390
391impl<'a> SegmentWriter<'a> {
392 pub const fn new(data: &'a [u8]) -> Self {
395 Self {
396 data,
397 pos: 0,
398 toggle: false,
399 }
400 }
401
402 pub const fn is_done(&self) -> bool {
404 self.pos >= self.data.len()
405 }
406
407 pub fn next_segment(&mut self) -> Option<SdoPayload> {
409 if self.is_done() {
410 return None;
411 }
412 let remaining = self.data.len() - self.pos;
413 let len = remaining.min(SEGMENT_DATA_MAX);
414 let last = remaining <= SEGMENT_DATA_MAX;
415 let frame = encode_data_segment(&self.data[self.pos..self.pos + len], self.toggle, last)
416 .expect("len is 1..=7");
417 self.pos += len;
418 self.toggle = !self.toggle;
419 Some(frame)
420 }
421}
422
423#[derive(Debug)]
427pub struct SegmentReader<const N: usize> {
428 buf: Vec<u8, N>,
429 toggle: bool,
430 done: bool,
431}
432
433impl<const N: usize> Default for SegmentReader<N> {
434 fn default() -> Self {
435 Self::new()
436 }
437}
438
439impl<const N: usize> SegmentReader<N> {
440 pub const fn new() -> Self {
442 Self {
443 buf: Vec::new(),
444 toggle: false,
445 done: false,
446 }
447 }
448
449 pub const fn is_done(&self) -> bool {
451 self.done
452 }
453
454 pub fn data(&self) -> &[u8] {
456 &self.buf
457 }
458
459 pub fn push(&mut self, segment: &Segment) -> Result<()> {
465 if self.done {
466 return Err(Error::UnexpectedCommand);
467 }
468 if segment.toggle != self.toggle {
469 return Err(Error::ToggleMismatch);
470 }
471 self.buf
472 .extend_from_slice(segment.data)
473 .map_err(|_| Error::Overflow)?;
474 self.toggle = !self.toggle;
475 if segment.last {
476 self.done = true;
477 }
478 Ok(())
479 }
480}
481
482#[cfg(test)]
483mod tests {
484 use super::*;
485
486 #[test]
488 fn cob_ids_follow_convention() {
489 let node = NodeId::new(0x05).unwrap();
490 assert_eq!(request_cob_id(node), 0x605);
491 assert_eq!(response_cob_id(node), 0x585);
492 }
493
494 #[test]
499 fn download_u32_matches_known_frame() {
500 let f = encode_download_expedited(Address::new(0x2000, 0), &Value::Unsigned32(0x1234_5678))
501 .unwrap();
502 assert_eq!(f, [0x23, 0x00, 0x20, 0x00, 0x78, 0x56, 0x34, 0x12]);
503 }
504
505 #[test]
508 fn download_u8_matches_known_frame() {
509 let f =
510 encode_download_expedited(Address::new(0x2001, 0), &Value::Unsigned8(0x7F)).unwrap();
511 assert_eq!(f, [0x2F, 0x01, 0x20, 0x00, 0x7F, 0x00, 0x00, 0x00]);
512 }
513
514 #[test]
515 fn download_i16_matches_known_frame() {
516 let f = encode_download_expedited(Address::new(0x6000, 1), &Value::Integer16(-2)).unwrap();
518 assert_eq!(f, [0x2B, 0x00, 0x60, 0x01, 0xFE, 0xFF, 0x00, 0x00]);
519 }
520
521 #[test]
522 fn download_response_roundtrips() {
523 let f = encode_download_response(Address::new(0x2000, 0));
524 assert_eq!(f, [0x60, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00]);
525 assert_eq!(
526 decode_download_response(&f).unwrap(),
527 Address::new(0x2000, 0)
528 );
529 }
530
531 #[test]
532 fn value_too_large_for_expedited_rejected() {
533 assert_eq!(
534 encode_download_expedited(Address::new(0x2000, 0), &Value::Unsigned64(1)),
535 Err(Error::UnsupportedTransfer)
536 );
537 }
538
539 #[test]
542 fn upload_request_matches_known_frame() {
543 let f = encode_upload_request(Address::new(0x1000, 0));
544 assert_eq!(f, [0x40, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00]);
545 }
546
547 #[test]
551 fn upload_response_device_type_decodes() {
552 let f = [0x43, 0x00, 0x10, 0x00, 0x92, 0x01, 0x00, 0x00];
553 let (addr, value) = decode_upload_expedited_response(&f, DataType::Unsigned32).unwrap();
554 assert_eq!(addr, Address::new(0x1000, 0));
555 assert_eq!(value, Value::Unsigned32(0x0000_0192));
556 }
557
558 #[test]
559 fn upload_response_encode_matches_known_frame() {
560 let f = encode_upload_expedited_response(
561 Address::new(0x1000, 0),
562 &Value::Unsigned32(0x0000_0192),
563 )
564 .unwrap();
565 assert_eq!(f, [0x43, 0x00, 0x10, 0x00, 0x92, 0x01, 0x00, 0x00]);
566 }
567
568 #[test]
569 fn upload_response_wrong_type_size_errors() {
570 let f = [0x43, 0x00, 0x10, 0x00, 0x92, 0x01, 0x00, 0x00];
572 assert_eq!(
573 decode_upload_expedited_response(&f, DataType::Unsigned16),
574 Err(Error::TypeMismatch)
575 );
576 }
577
578 #[test]
579 fn decode_upload_rejects_non_upload_frame() {
580 let f = encode_download_response(Address::new(0x1000, 0));
581 assert_eq!(
582 decode_upload_expedited_response(&f, DataType::Unsigned32),
583 Err(Error::UnexpectedCommand)
584 );
585 }
586
587 #[test]
591 fn abort_object_missing_matches_known_frame() {
592 let f = encode_abort(Address::new(0x1000, 0), SdoAbortCode::ObjectDoesNotExist);
593 assert_eq!(f, [0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x06]);
594 let (addr, code) = decode_abort(&f).unwrap();
595 assert_eq!(addr, Address::new(0x1000, 0));
596 assert_eq!(code, 0x0602_0000);
597 }
598
599 #[test]
604 fn download_initiate_segmented_matches_known_frame() {
605 let f = encode_download_initiate_segmented(Address::new(0x2000, 0), 20);
606 assert_eq!(f, [0x21, 0x00, 0x20, 0x00, 20, 0x00, 0x00, 0x00]);
607 assert_eq!(
608 decode_download_initiate_segmented(&f).unwrap(),
609 (Address::new(0x2000, 0), 20)
610 );
611 }
612
613 #[test]
616 fn upload_initiate_segmented_response_matches_known_frame() {
617 let f = encode_upload_initiate_segmented_response(Address::new(0x2000, 0), 20);
618 assert_eq!(f, [0x41, 0x00, 0x20, 0x00, 20, 0x00, 0x00, 0x00]);
619 assert_eq!(
620 decode_upload_initiate_segmented_response(&f).unwrap(),
621 (Address::new(0x2000, 0), 20)
622 );
623 }
624
625 #[test]
627 fn segmented_initiate_rejects_expedited_frame() {
628 let expedited = [0x43, 0x00, 0x10, 0x00, 0x92, 0x01, 0x00, 0x00];
629 assert_eq!(
630 decode_upload_initiate_segmented_response(&expedited),
631 Err(Error::UnexpectedCommand)
632 );
633 }
634
635 #[test]
638 fn data_segment_full_matches_known_frame() {
639 let f = encode_data_segment(&[1, 2, 3, 4, 5, 6, 7], false, false).unwrap();
640 assert_eq!(f, [0x00, 1, 2, 3, 4, 5, 6, 7]);
641 }
642
643 #[test]
646 fn data_segment_last_matches_known_frame() {
647 let f = encode_data_segment(&[0xAA, 0xBB, 0xCC], true, true).unwrap();
648 assert_eq!(f, [0x19, 0xAA, 0xBB, 0xCC, 0x00, 0x00, 0x00, 0x00]);
649 let seg = decode_data_segment(&f).unwrap();
650 assert!(seg.toggle);
651 assert!(seg.last);
652 assert_eq!(seg.data, &[0xAA, 0xBB, 0xCC]);
653 }
654
655 #[test]
656 fn segment_ack_and_poll_toggle_roundtrip() {
657 assert_eq!(
658 encode_download_segment_response(false),
659 [0x20, 0, 0, 0, 0, 0, 0, 0]
660 );
661 assert_eq!(
662 encode_download_segment_response(true),
663 [0x30, 0, 0, 0, 0, 0, 0, 0]
664 );
665 assert!(decode_download_segment_response(&encode_download_segment_response(true)).unwrap());
666
667 assert_eq!(
668 encode_upload_segment_request(false),
669 [0x60, 0, 0, 0, 0, 0, 0, 0]
670 );
671 assert_eq!(
672 encode_upload_segment_request(true),
673 [0x70, 0, 0, 0, 0, 0, 0, 0]
674 );
675 assert!(decode_upload_segment_request(&encode_upload_segment_request(true)).unwrap());
676 }
677
678 #[test]
682 fn segment_writer_reader_roundtrip() {
683 let payload: [u8; 12] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120];
684 let mut writer = SegmentWriter::new(&payload);
685 let mut reader = SegmentReader::<64>::new();
686
687 let f1 = writer.next_segment().unwrap();
688 assert_eq!(f1[0] & TOGGLE, 0); reader.push(&decode_data_segment(&f1).unwrap()).unwrap();
690 assert!(!reader.is_done());
691
692 let f2 = writer.next_segment().unwrap();
693 assert_ne!(f2[0] & TOGGLE, 0); reader.push(&decode_data_segment(&f2).unwrap()).unwrap();
695
696 assert!(reader.is_done());
697 assert!(writer.next_segment().is_none());
698 assert_eq!(reader.data(), &payload);
699 }
700
701 #[test]
702 fn reader_rejects_toggle_out_of_sequence() {
703 let mut reader = SegmentReader::<16>::new();
704 reader
706 .push(&Segment {
707 toggle: false,
708 last: false,
709 data: &[1, 2, 3],
710 })
711 .unwrap();
712 assert_eq!(
713 reader.push(&Segment {
714 toggle: false,
715 last: true,
716 data: &[4, 5]
717 }),
718 Err(Error::ToggleMismatch)
719 );
720 }
721
722 #[test]
723 fn reader_overflow_is_reported() {
724 let mut reader = SegmentReader::<4>::new();
725 assert_eq!(
726 reader.push(&Segment {
727 toggle: false,
728 last: false,
729 data: &[1, 2, 3, 4, 5]
730 }),
731 Err(Error::Overflow)
732 );
733 }
734}