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 > 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 > 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 let Some(fixed) = data_type.fixed_size() {
207 if len != fixed {
208 return Err(Error::TypeMismatch);
209 }
210 }
211 let addr = Address::new(u16::from_le_bytes([p[1], p[2]]), p[3]);
212 let value = Value::decode_le(data_type, &p[4..4 + len])?;
213 Ok((addr, value))
214}
215
216pub fn encode_abort(addr: Address, code: SdoAbortCode) -> SdoPayload {
218 let mut p = [0u8; 8];
219 p[0] = CS_ABORT;
220 p[1..3].copy_from_slice(&addr.index.to_le_bytes());
221 p[3] = addr.subindex;
222 p[4..8].copy_from_slice(&(code as u32).to_le_bytes());
223 p
224}
225
226pub fn decode_abort(p: &SdoPayload) -> Result<(Address, u32)> {
228 if p[0] != CS_ABORT {
229 return Err(Error::UnexpectedCommand);
230 }
231 let addr = Address::new(u16::from_le_bytes([p[1], p[2]]), p[3]);
232 let code = u32::from_le_bytes([p[4], p[5], p[6], p[7]]);
233 Ok((addr, code))
234}
235
236#[derive(Debug, Clone, Copy, PartialEq, Eq)]
256pub struct Segment<'a> {
257 pub toggle: bool,
259 pub last: bool,
261 pub data: &'a [u8],
263}
264
265pub fn encode_download_initiate_segmented(addr: Address, size: u32) -> SdoPayload {
268 let mut p = [0u8; 8];
269 p[0] = CCS_DOWNLOAD_INITIATE | SIZE_INDICATED;
270 p[1..3].copy_from_slice(&addr.index.to_le_bytes());
271 p[3] = addr.subindex;
272 p[4..8].copy_from_slice(&size.to_le_bytes());
273 p
274}
275
276pub fn decode_download_initiate_segmented(p: &SdoPayload) -> Result<(Address, u32)> {
279 if p[0] & CS_MASK != CCS_DOWNLOAD_INITIATE
280 || p[0] & EXPEDITED != 0
281 || p[0] & SIZE_INDICATED == 0
282 {
283 return Err(Error::UnexpectedCommand);
284 }
285 let addr = Address::new(u16::from_le_bytes([p[1], p[2]]), p[3]);
286 Ok((addr, u32::from_le_bytes([p[4], p[5], p[6], p[7]])))
287}
288
289pub fn encode_upload_initiate_segmented_response(addr: Address, size: u32) -> SdoPayload {
292 let mut p = [0u8; 8];
293 p[0] = SCS_UPLOAD_INITIATE | SIZE_INDICATED;
294 p[1..3].copy_from_slice(&addr.index.to_le_bytes());
295 p[3] = addr.subindex;
296 p[4..8].copy_from_slice(&size.to_le_bytes());
297 p
298}
299
300pub fn decode_upload_initiate_segmented_response(p: &SdoPayload) -> Result<(Address, u32)> {
306 if p[0] & CS_MASK != SCS_UPLOAD_INITIATE || p[0] & EXPEDITED != 0 || p[0] & SIZE_INDICATED == 0
307 {
308 return Err(Error::UnexpectedCommand);
309 }
310 let addr = Address::new(u16::from_le_bytes([p[1], p[2]]), p[3]);
311 Ok((addr, u32::from_le_bytes([p[4], p[5], p[6], p[7]])))
312}
313
314pub fn encode_data_segment(data: &[u8], toggle: bool, last: bool) -> Result<SdoPayload> {
321 if data.is_empty() || data.len() > SEGMENT_DATA_MAX {
322 return Err(Error::BadLength);
323 }
324 let mut p = [0u8; 8];
325 let n = (SEGMENT_DATA_MAX - data.len()) as u8;
327 p[0] = (n << 1) & 0x0E;
328 if toggle {
329 p[0] |= TOGGLE;
330 }
331 if last {
332 p[0] |= NO_MORE_SEGMENTS;
333 }
334 p[1..1 + data.len()].copy_from_slice(data);
335 Ok(p)
336}
337
338pub fn decode_data_segment(p: &SdoPayload) -> Result<Segment<'_>> {
340 if p[0] & CS_MASK != CCS_DOWNLOAD_SEGMENT {
341 return Err(Error::UnexpectedCommand);
342 }
343 let n = ((p[0] >> 1) & 0x07) as usize;
344 if n > SEGMENT_DATA_MAX {
345 return Err(Error::BadLength);
346 }
347 Ok(Segment {
348 toggle: p[0] & TOGGLE != 0,
349 last: p[0] & NO_MORE_SEGMENTS != 0,
350 data: &p[1..1 + (SEGMENT_DATA_MAX - n)],
351 })
352}
353
354pub fn encode_download_segment_response(toggle: bool) -> SdoPayload {
357 let mut p = [0u8; 8];
358 p[0] = SCS_DOWNLOAD_SEGMENT | if toggle { TOGGLE } else { 0 };
359 p
360}
361
362pub fn decode_download_segment_response(p: &SdoPayload) -> Result<bool> {
364 if p[0] & CS_MASK != SCS_DOWNLOAD_SEGMENT {
365 return Err(Error::UnexpectedCommand);
366 }
367 Ok(p[0] & TOGGLE != 0)
368}
369
370pub fn encode_upload_segment_request(toggle: bool) -> SdoPayload {
373 let mut p = [0u8; 8];
374 p[0] = CCS_UPLOAD_SEGMENT | if toggle { TOGGLE } else { 0 };
375 p
376}
377
378pub fn decode_upload_segment_request(p: &SdoPayload) -> Result<bool> {
380 if p[0] & CS_MASK != CCS_UPLOAD_SEGMENT {
381 return Err(Error::UnexpectedCommand);
382 }
383 Ok(p[0] & TOGGLE != 0)
384}
385
386#[derive(Debug)]
390pub struct SegmentWriter<'a> {
391 data: &'a [u8],
392 pos: usize,
393 toggle: bool,
394}
395
396impl<'a> SegmentWriter<'a> {
397 pub const fn new(data: &'a [u8]) -> Self {
400 Self {
401 data,
402 pos: 0,
403 toggle: false,
404 }
405 }
406
407 pub const fn is_done(&self) -> bool {
409 self.pos >= self.data.len()
410 }
411
412 pub fn next_segment(&mut self) -> Option<SdoPayload> {
414 if self.is_done() {
415 return None;
416 }
417 let remaining = self.data.len() - self.pos;
418 let len = remaining.min(SEGMENT_DATA_MAX);
419 let last = remaining <= SEGMENT_DATA_MAX;
420 let frame = encode_data_segment(&self.data[self.pos..self.pos + len], self.toggle, last)
421 .expect("len is 1..=7");
422 self.pos += len;
423 self.toggle = !self.toggle;
424 Some(frame)
425 }
426}
427
428#[derive(Debug)]
432pub struct SegmentReader<const N: usize> {
433 buf: Vec<u8, N>,
434 toggle: bool,
435 done: bool,
436}
437
438impl<const N: usize> Default for SegmentReader<N> {
439 fn default() -> Self {
440 Self::new()
441 }
442}
443
444impl<const N: usize> SegmentReader<N> {
445 pub const fn new() -> Self {
447 Self {
448 buf: Vec::new(),
449 toggle: false,
450 done: false,
451 }
452 }
453
454 pub const fn is_done(&self) -> bool {
456 self.done
457 }
458
459 pub fn data(&self) -> &[u8] {
461 &self.buf
462 }
463
464 pub fn push(&mut self, segment: &Segment) -> Result<()> {
470 if self.done {
471 return Err(Error::UnexpectedCommand);
472 }
473 if segment.toggle != self.toggle {
474 return Err(Error::ToggleMismatch);
475 }
476 self.buf
477 .extend_from_slice(segment.data)
478 .map_err(|_| Error::Overflow)?;
479 self.toggle = !self.toggle;
480 if segment.last {
481 self.done = true;
482 }
483 Ok(())
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490
491 #[test]
493 fn cob_ids_follow_convention() {
494 let node = NodeId::new(0x05).unwrap();
495 assert_eq!(request_cob_id(node), 0x605);
496 assert_eq!(response_cob_id(node), 0x585);
497 }
498
499 #[test]
504 fn download_u32_matches_known_frame() {
505 let f = encode_download_expedited(Address::new(0x2000, 0), &Value::Unsigned32(0x1234_5678))
506 .unwrap();
507 assert_eq!(f, [0x23, 0x00, 0x20, 0x00, 0x78, 0x56, 0x34, 0x12]);
508 }
509
510 #[test]
513 fn download_u8_matches_known_frame() {
514 let f =
515 encode_download_expedited(Address::new(0x2001, 0), &Value::Unsigned8(0x7F)).unwrap();
516 assert_eq!(f, [0x2F, 0x01, 0x20, 0x00, 0x7F, 0x00, 0x00, 0x00]);
517 }
518
519 #[test]
520 fn download_i16_matches_known_frame() {
521 let f = encode_download_expedited(Address::new(0x6000, 1), &Value::Integer16(-2)).unwrap();
523 assert_eq!(f, [0x2B, 0x00, 0x60, 0x01, 0xFE, 0xFF, 0x00, 0x00]);
524 }
525
526 #[test]
527 fn download_response_roundtrips() {
528 let f = encode_download_response(Address::new(0x2000, 0));
529 assert_eq!(f, [0x60, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00]);
530 assert_eq!(
531 decode_download_response(&f).unwrap(),
532 Address::new(0x2000, 0)
533 );
534 }
535
536 #[test]
537 fn value_too_large_for_expedited_rejected() {
538 assert_eq!(
539 encode_download_expedited(Address::new(0x2000, 0), &Value::Unsigned64(1)),
540 Err(Error::UnsupportedTransfer)
541 );
542 }
543
544 #[test]
547 fn upload_request_matches_known_frame() {
548 let f = encode_upload_request(Address::new(0x1000, 0));
549 assert_eq!(f, [0x40, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00]);
550 }
551
552 #[test]
556 fn upload_response_device_type_decodes() {
557 let f = [0x43, 0x00, 0x10, 0x00, 0x92, 0x01, 0x00, 0x00];
558 let (addr, value) = decode_upload_expedited_response(&f, DataType::Unsigned32).unwrap();
559 assert_eq!(addr, Address::new(0x1000, 0));
560 assert_eq!(value, Value::Unsigned32(0x0000_0192));
561 }
562
563 #[test]
564 fn upload_response_encode_matches_known_frame() {
565 let f = encode_upload_expedited_response(
566 Address::new(0x1000, 0),
567 &Value::Unsigned32(0x0000_0192),
568 )
569 .unwrap();
570 assert_eq!(f, [0x43, 0x00, 0x10, 0x00, 0x92, 0x01, 0x00, 0x00]);
571 }
572
573 #[test]
574 fn upload_response_wrong_type_size_errors() {
575 let f = [0x43, 0x00, 0x10, 0x00, 0x92, 0x01, 0x00, 0x00];
577 assert_eq!(
578 decode_upload_expedited_response(&f, DataType::Unsigned16),
579 Err(Error::TypeMismatch)
580 );
581 }
582
583 #[test]
584 fn decode_upload_rejects_non_upload_frame() {
585 let f = encode_download_response(Address::new(0x1000, 0));
586 assert_eq!(
587 decode_upload_expedited_response(&f, DataType::Unsigned32),
588 Err(Error::UnexpectedCommand)
589 );
590 }
591
592 #[test]
596 fn abort_object_missing_matches_known_frame() {
597 let f = encode_abort(Address::new(0x1000, 0), SdoAbortCode::ObjectDoesNotExist);
598 assert_eq!(f, [0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x06]);
599 let (addr, code) = decode_abort(&f).unwrap();
600 assert_eq!(addr, Address::new(0x1000, 0));
601 assert_eq!(code, 0x0602_0000);
602 }
603
604 #[test]
609 fn download_initiate_segmented_matches_known_frame() {
610 let f = encode_download_initiate_segmented(Address::new(0x2000, 0), 20);
611 assert_eq!(f, [0x21, 0x00, 0x20, 0x00, 20, 0x00, 0x00, 0x00]);
612 assert_eq!(
613 decode_download_initiate_segmented(&f).unwrap(),
614 (Address::new(0x2000, 0), 20)
615 );
616 }
617
618 #[test]
621 fn upload_initiate_segmented_response_matches_known_frame() {
622 let f = encode_upload_initiate_segmented_response(Address::new(0x2000, 0), 20);
623 assert_eq!(f, [0x41, 0x00, 0x20, 0x00, 20, 0x00, 0x00, 0x00]);
624 assert_eq!(
625 decode_upload_initiate_segmented_response(&f).unwrap(),
626 (Address::new(0x2000, 0), 20)
627 );
628 }
629
630 #[test]
632 fn segmented_initiate_rejects_expedited_frame() {
633 let expedited = [0x43, 0x00, 0x10, 0x00, 0x92, 0x01, 0x00, 0x00];
634 assert_eq!(
635 decode_upload_initiate_segmented_response(&expedited),
636 Err(Error::UnexpectedCommand)
637 );
638 }
639
640 #[test]
643 fn data_segment_full_matches_known_frame() {
644 let f = encode_data_segment(&[1, 2, 3, 4, 5, 6, 7], false, false).unwrap();
645 assert_eq!(f, [0x00, 1, 2, 3, 4, 5, 6, 7]);
646 }
647
648 #[test]
651 fn data_segment_last_matches_known_frame() {
652 let f = encode_data_segment(&[0xAA, 0xBB, 0xCC], true, true).unwrap();
653 assert_eq!(f, [0x19, 0xAA, 0xBB, 0xCC, 0x00, 0x00, 0x00, 0x00]);
654 let seg = decode_data_segment(&f).unwrap();
655 assert!(seg.toggle);
656 assert!(seg.last);
657 assert_eq!(seg.data, &[0xAA, 0xBB, 0xCC]);
658 }
659
660 #[test]
661 fn segment_ack_and_poll_toggle_roundtrip() {
662 assert_eq!(
663 encode_download_segment_response(false),
664 [0x20, 0, 0, 0, 0, 0, 0, 0]
665 );
666 assert_eq!(
667 encode_download_segment_response(true),
668 [0x30, 0, 0, 0, 0, 0, 0, 0]
669 );
670 assert!(decode_download_segment_response(&encode_download_segment_response(true)).unwrap());
671
672 assert_eq!(
673 encode_upload_segment_request(false),
674 [0x60, 0, 0, 0, 0, 0, 0, 0]
675 );
676 assert_eq!(
677 encode_upload_segment_request(true),
678 [0x70, 0, 0, 0, 0, 0, 0, 0]
679 );
680 assert!(decode_upload_segment_request(&encode_upload_segment_request(true)).unwrap());
681 }
682
683 #[test]
687 fn segment_writer_reader_roundtrip() {
688 let payload: [u8; 12] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120];
689 let mut writer = SegmentWriter::new(&payload);
690 let mut reader = SegmentReader::<64>::new();
691
692 let f1 = writer.next_segment().unwrap();
693 assert_eq!(f1[0] & TOGGLE, 0); reader.push(&decode_data_segment(&f1).unwrap()).unwrap();
695 assert!(!reader.is_done());
696
697 let f2 = writer.next_segment().unwrap();
698 assert_ne!(f2[0] & TOGGLE, 0); reader.push(&decode_data_segment(&f2).unwrap()).unwrap();
700
701 assert!(reader.is_done());
702 assert!(writer.next_segment().is_none());
703 assert_eq!(reader.data(), &payload);
704 }
705
706 #[test]
707 fn reader_rejects_toggle_out_of_sequence() {
708 let mut reader = SegmentReader::<16>::new();
709 reader
711 .push(&Segment {
712 toggle: false,
713 last: false,
714 data: &[1, 2, 3],
715 })
716 .unwrap();
717 assert_eq!(
718 reader.push(&Segment {
719 toggle: false,
720 last: true,
721 data: &[4, 5]
722 }),
723 Err(Error::ToggleMismatch)
724 );
725 }
726
727 #[test]
728 fn reader_overflow_is_reported() {
729 let mut reader = SegmentReader::<4>::new();
730 assert_eq!(
731 reader.push(&Segment {
732 toggle: false,
733 last: false,
734 data: &[1, 2, 3, 4, 5]
735 }),
736 Err(Error::Overflow)
737 );
738 }
739}