1use heapless::Vec;
24
25use super::SdoPayload;
26use crate::object_dictionary::Address;
27use crate::{Error, Result};
28
29const CS_6: u8 = 0xC0;
34const CS_5: u8 = 0xA0;
35const CS_MASK: u8 = 0xE0;
36
37const CRC_SUPPORTED: u8 = 0x04; const SIZE_INDICATED: u8 = 0x02; const SUBCMD_MASK: u8 = 0x03;
41const SUBCMD_INITIATE: u8 = 0x00;
42const SUBCMD_END: u8 = 0x01;
43const SUBCMD_RESPONSE: u8 = 0x02;
44const SUBCMD_START: u8 = 0x03;
45
46const LAST_SEGMENT: u8 = 0x80; const SEQNO_MASK: u8 = 0x7F;
48
49pub const SEGMENT_DATA_MAX: usize = 7;
51pub const MAX_BLKSIZE: u8 = 127;
53
54pub fn crc16(data: &[u8]) -> u16 {
57 let mut crc: u16 = 0;
58 for &byte in data {
59 crc ^= (byte as u16) << 8;
60 for _ in 0..8 {
61 crc = if crc & 0x8000 != 0 {
62 (crc << 1) ^ 0x1021
63 } else {
64 crc << 1
65 };
66 }
67 }
68 crc
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub struct BlockInitiate {
75 pub address: Address,
77 pub size: Option<u32>,
79 pub crc_support: bool,
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub struct BlockNegotiation {
87 pub address: Address,
89 pub blksize: u8,
91 pub crc_support: bool,
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub struct SubSegment<'a> {
98 pub seqno: u8,
100 pub last: bool,
102 pub data: &'a [u8],
105}
106
107fn address_of(p: &SdoPayload) -> Address {
108 Address::new(u16::from_le_bytes([p[1], p[2]]), p[3])
109}
110
111fn put_address(p: &mut SdoPayload, addr: Address) {
112 p[1..3].copy_from_slice(&addr.index.to_le_bytes());
113 p[3] = addr.subindex;
114}
115
116pub fn encode_download_initiate(addr: Address, size: Option<u32>, crc_support: bool) -> SdoPayload {
120 encode_initiate_with_size(CS_6, addr, size, crc_support)
121}
122
123pub fn decode_download_initiate(p: &SdoPayload) -> Result<BlockInitiate> {
125 decode_initiate_with_size(CS_6, p)
126}
127
128pub fn encode_download_initiate_response(
130 addr: Address,
131 blksize: u8,
132 crc_support: bool,
133) -> SdoPayload {
134 encode_initiate_with_blksize(CS_5, addr, blksize, crc_support)
135}
136
137pub fn decode_download_initiate_response(p: &SdoPayload) -> Result<BlockNegotiation> {
139 decode_initiate_with_blksize(CS_5, p)
140}
141
142pub fn encode_upload_initiate(addr: Address, blksize: u8, crc_support: bool) -> SdoPayload {
144 encode_initiate_with_blksize(CS_5, addr, blksize, crc_support)
145}
146
147pub fn decode_upload_initiate(p: &SdoPayload) -> Result<BlockNegotiation> {
149 decode_initiate_with_blksize(CS_5, p)
150}
151
152pub fn encode_upload_initiate_response(
154 addr: Address,
155 size: Option<u32>,
156 crc_support: bool,
157) -> SdoPayload {
158 encode_initiate_with_size(CS_6, addr, size, crc_support)
159}
160
161pub fn decode_upload_initiate_response(p: &SdoPayload) -> Result<BlockInitiate> {
163 decode_initiate_with_size(CS_6, p)
164}
165
166pub fn encode_upload_start() -> SdoPayload {
169 let mut p = [0u8; 8];
170 p[0] = CS_5 | SUBCMD_START;
171 p
172}
173
174pub fn decode_upload_start(p: &SdoPayload) -> Result<()> {
176 if p[0] & CS_MASK != CS_5 || p[0] & SUBCMD_MASK != SUBCMD_START {
177 return Err(Error::UnexpectedCommand);
178 }
179 Ok(())
180}
181
182fn encode_initiate_with_size(cs: u8, addr: Address, size: Option<u32>, crc: bool) -> SdoPayload {
183 let mut p = [0u8; 8];
184 p[0] = cs | SUBCMD_INITIATE;
185 if crc {
186 p[0] |= CRC_SUPPORTED;
187 }
188 if let Some(size) = size {
189 p[0] |= SIZE_INDICATED;
190 p[4..8].copy_from_slice(&size.to_le_bytes());
191 }
192 put_address(&mut p, addr);
193 p
194}
195
196fn decode_initiate_with_size(cs: u8, p: &SdoPayload) -> Result<BlockInitiate> {
197 if p[0] & CS_MASK != cs || p[0] & SUBCMD_END != 0 {
200 return Err(Error::UnexpectedCommand);
201 }
202 let size = (p[0] & SIZE_INDICATED != 0).then(|| u32::from_le_bytes([p[4], p[5], p[6], p[7]]));
203 Ok(BlockInitiate {
204 address: address_of(p),
205 size,
206 crc_support: p[0] & CRC_SUPPORTED != 0,
207 })
208}
209
210fn encode_initiate_with_blksize(cs: u8, addr: Address, blksize: u8, crc: bool) -> SdoPayload {
211 let mut p = [0u8; 8];
212 p[0] = cs | SUBCMD_INITIATE;
213 if crc {
214 p[0] |= CRC_SUPPORTED;
215 }
216 put_address(&mut p, addr);
217 p[4] = blksize;
218 p
219}
220
221fn decode_initiate_with_blksize(cs: u8, p: &SdoPayload) -> Result<BlockNegotiation> {
222 if p[0] & CS_MASK != cs || p[0] & SUBCMD_MASK != SUBCMD_INITIATE {
223 return Err(Error::UnexpectedCommand);
224 }
225 Ok(BlockNegotiation {
226 address: address_of(p),
227 blksize: p[4],
228 crc_support: p[0] & CRC_SUPPORTED != 0,
229 })
230}
231
232pub fn encode_sub_segment(seqno: u8, data: &[u8], last: bool) -> Result<SdoPayload> {
237 if seqno == 0 || seqno > MAX_BLKSIZE || data.is_empty() || data.len() > SEGMENT_DATA_MAX {
238 return Err(Error::BadLength);
239 }
240 let mut p = [0u8; 8];
241 p[0] = seqno;
242 if last {
243 p[0] |= LAST_SEGMENT;
244 }
245 p[1..1 + data.len()].copy_from_slice(data);
246 Ok(p)
247}
248
249pub fn decode_sub_segment(p: &SdoPayload) -> SubSegment<'_> {
251 SubSegment {
252 seqno: p[0] & SEQNO_MASK,
253 last: p[0] & LAST_SEGMENT != 0,
254 data: &p[1..8],
255 }
256}
257
258pub fn encode_sub_response(ackseq: u8, blksize: u8) -> SdoPayload {
262 let mut p = [0u8; 8];
263 p[0] = CS_5 | SUBCMD_RESPONSE;
264 p[1] = ackseq;
265 p[2] = blksize;
266 p
267}
268
269pub fn decode_sub_response(p: &SdoPayload) -> Result<(u8, u8)> {
271 if p[0] & CS_MASK != CS_5 || p[0] & SUBCMD_MASK != SUBCMD_RESPONSE {
272 return Err(Error::UnexpectedCommand);
273 }
274 Ok((p[1], p[2]))
275}
276
277pub fn encode_end(unused: u8, crc: u16) -> SdoPayload {
281 let mut p = [0u8; 8];
282 p[0] = CS_6 | ((unused & 0x07) << 2) | SUBCMD_END;
283 p[1..3].copy_from_slice(&crc.to_le_bytes());
284 p
285}
286
287pub fn decode_end(p: &SdoPayload) -> Result<(u8, u16)> {
289 if p[0] & CS_MASK != CS_6 || p[0] & SUBCMD_MASK != SUBCMD_END {
290 return Err(Error::UnexpectedCommand);
291 }
292 Ok(((p[0] >> 2) & 0x07, u16::from_le_bytes([p[1], p[2]])))
293}
294
295pub fn encode_end_response() -> SdoPayload {
298 let mut p = [0u8; 8];
299 p[0] = CS_5 | SUBCMD_END;
300 p
301}
302
303pub fn decode_end_response(p: &SdoPayload) -> Result<()> {
305 if p[0] & CS_MASK != CS_5 || p[0] & SUBCMD_MASK != SUBCMD_END {
306 return Err(Error::UnexpectedCommand);
307 }
308 Ok(())
309}
310
311#[derive(Debug)]
321pub struct BlockWriter<'a> {
322 data: &'a [u8],
323 pos: usize,
324 seqno: u8,
325 blksize: u8,
326}
327
328impl<'a> BlockWriter<'a> {
329 pub fn new(data: &'a [u8], blksize: u8) -> Self {
331 Self {
332 data,
333 pos: 0,
334 seqno: 1,
335 blksize,
336 }
337 }
338
339 pub const fn is_done(&self) -> bool {
341 self.pos >= self.data.len()
342 }
343
344 pub fn next_segment(&mut self) -> Option<SdoPayload> {
347 if self.is_done() || self.seqno > self.blksize {
348 return None;
349 }
350 let remaining = self.data.len() - self.pos;
351 let take = remaining.min(SEGMENT_DATA_MAX);
352 let last = remaining <= SEGMENT_DATA_MAX;
353 let segment = encode_sub_segment(self.seqno, &self.data[self.pos..self.pos + take], last)
354 .expect("seqno and length are in range");
355 self.pos += take;
356 self.seqno += 1;
357 Some(segment)
358 }
359
360 pub fn start_sub_block(&mut self, blksize: u8) {
362 self.seqno = 1;
363 self.blksize = blksize;
364 }
365
366 pub fn end_frame(&self, crc_support: bool) -> SdoPayload {
369 let last_len = match self.data.len() % SEGMENT_DATA_MAX {
370 0 if !self.data.is_empty() => SEGMENT_DATA_MAX,
371 r => r,
372 };
373 let unused = (SEGMENT_DATA_MAX - last_len) as u8;
374 let crc = if crc_support { crc16(self.data) } else { 0 };
375 encode_end(unused, crc)
376 }
377}
378
379#[derive(Debug)]
382pub struct BlockReceiver<const N: usize> {
383 buf: Vec<u8, N>,
384 done: bool,
385}
386
387impl<const N: usize> Default for BlockReceiver<N> {
388 fn default() -> Self {
389 Self::new()
390 }
391}
392
393impl<const N: usize> BlockReceiver<N> {
394 pub const fn new() -> Self {
396 Self {
397 buf: Vec::new(),
398 done: false,
399 }
400 }
401
402 pub const fn is_done(&self) -> bool {
404 self.done
405 }
406
407 pub fn push(&mut self, segment: &SubSegment) -> Result<()> {
412 if self.done {
413 return Err(Error::UnexpectedCommand);
414 }
415 self.buf
416 .extend_from_slice(segment.data)
417 .map_err(|_| Error::Overflow)?;
418 if segment.last {
419 self.done = true;
420 }
421 Ok(())
422 }
423
424 pub fn finish(&mut self, unused: u8, crc: u16, verify_crc: bool) -> Result<&[u8]> {
430 let unused = unused as usize;
431 if unused > self.buf.len() {
432 return Err(Error::BadLength);
433 }
434 self.buf.truncate(self.buf.len() - unused);
435 if verify_crc && crc16(&self.buf) != crc {
436 return Err(Error::CrcMismatch);
437 }
438 Ok(&self.buf)
439 }
440}
441
442#[cfg(test)]
443mod tests {
444 use super::*;
445
446 #[test]
447 fn crc16_known_vector() {
448 assert_eq!(crc16(b"123456789"), 0x31C3); assert_eq!(crc16(&[]), 0x0000);
450 }
451
452 #[test]
456 fn download_initiate_frames() {
457 let req = encode_download_initiate(Address::new(0x2000, 0), Some(100), true);
458 assert_eq!(req, [0xC6, 0x00, 0x20, 0x00, 100, 0, 0, 0]);
459 assert_eq!(
460 decode_download_initiate(&req).unwrap(),
461 BlockInitiate {
462 address: Address::new(0x2000, 0),
463 size: Some(100),
464 crc_support: true
465 }
466 );
467 let resp = encode_download_initiate_response(Address::new(0x2000, 0), 10, true);
468 assert_eq!(resp, [0xA4, 0x00, 0x20, 0x00, 10, 0, 0, 0]);
469 assert_eq!(
470 decode_download_initiate_response(&resp).unwrap(),
471 BlockNegotiation {
472 address: Address::new(0x2000, 0),
473 blksize: 10,
474 crc_support: true
475 }
476 );
477 }
478
479 #[test]
482 fn upload_initiate_and_start_frames() {
483 let req = encode_upload_initiate(Address::new(0x2000, 0), 5, false);
484 assert_eq!(req, [0xA0, 0x00, 0x20, 0x00, 5, 0, 0, 0]);
485 assert_eq!(decode_upload_initiate(&req).unwrap().blksize, 5);
486
487 let resp = encode_upload_initiate_response(Address::new(0x2000, 0), Some(42), true);
488 assert_eq!(resp, [0xC6, 0x00, 0x20, 0x00, 42, 0, 0, 0]);
489 assert_eq!(
490 decode_upload_initiate_response(&resp).unwrap().size,
491 Some(42)
492 );
493
494 assert_eq!(encode_upload_start(), [0xA3, 0, 0, 0, 0, 0, 0, 0]);
495 assert!(decode_upload_start(&encode_upload_start()).is_ok());
496 }
497
498 #[test]
499 fn shared_segment_and_ack_frames() {
500 let f = encode_sub_segment(1, &[1, 2, 3, 4, 5, 6, 7], false).unwrap();
501 assert_eq!(f, [0x01, 1, 2, 3, 4, 5, 6, 7]);
502 let last = encode_sub_segment(3, &[0xAA, 0xBB], true).unwrap();
503 assert_eq!(last, [0x83, 0xAA, 0xBB, 0, 0, 0, 0, 0]);
504 assert!(decode_sub_segment(&last).last);
505
506 assert_eq!(encode_sub_response(10, 20), [0xA2, 10, 20, 0, 0, 0, 0, 0]);
507 assert_eq!(
508 decode_sub_response(&encode_sub_response(10, 20)).unwrap(),
509 (10, 20)
510 );
511
512 assert_eq!(encode_end(5, 0xBEEF), [0xD5, 0xEF, 0xBE, 0, 0, 0, 0, 0]);
513 assert_eq!(decode_end(&encode_end(5, 0xBEEF)).unwrap(), (5, 0xBEEF));
514 assert_eq!(encode_end_response(), [0xA1, 0, 0, 0, 0, 0, 0, 0]);
515 assert!(decode_end_response(&encode_end_response()).is_ok());
516 }
517
518 fn roundtrip(data: &[u8], blksize: u8) {
522 let mut writer = BlockWriter::new(data, blksize);
523 let mut receiver = BlockReceiver::<128>::new();
524 loop {
525 while let Some(seg) = writer.next_segment() {
526 receiver.push(&decode_sub_segment(&seg)).unwrap();
527 }
528 if writer.is_done() {
529 break;
530 }
531 writer.start_sub_block(blksize);
532 }
533 assert!(receiver.is_done());
534 let (unused, crc) = decode_end(&writer.end_frame(true)).unwrap();
535 assert_eq!(receiver.finish(unused, crc, true).unwrap(), data);
536 }
537
538 #[test]
539 fn block_transfer_roundtrips() {
540 let data: [u8; 30] = core::array::from_fn(|i| i as u8);
541 roundtrip(&data, 2); roundtrip(&data, 127); roundtrip(&[0xAB; 7], 4); }
545
546 #[test]
547 fn crc_mismatch_is_detected() {
548 let data = [1u8, 2, 3, 4, 5];
549 let mut writer = BlockWriter::new(&data, 1);
550 let mut receiver = BlockReceiver::<16>::new();
551 while let Some(seg) = writer.next_segment() {
552 receiver.push(&decode_sub_segment(&seg)).unwrap();
553 }
554 let (unused, _) = decode_end(&writer.end_frame(true)).unwrap();
555 assert_eq!(
556 receiver.finish(unused, 0x0000, true),
557 Err(Error::CrcMismatch)
558 );
559 }
560}