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