1use heapless::Vec;
12
13use super::{
14 decode_data_segment, decode_download_initiate_segmented, encode_abort, encode_data_segment,
15 encode_download_response, encode_download_segment_response, encode_upload_expedited_response,
16 encode_upload_initiate_segmented_response, request_cob_id, response_cob_id, SdoAbortCode,
17 SdoPayload, CCS_DOWNLOAD_INITIATE, CCS_DOWNLOAD_SEGMENT, CCS_UPLOAD_INITIATE,
18 CCS_UPLOAD_SEGMENT, CS_ABORT, CS_MASK, EXPEDITED, SEGMENT_DATA_MAX, SIZE_INDICATED, TOGGLE,
19};
20use crate::datatypes::{DataType, Value, MAX_STRING_LEN};
21use crate::object_dictionary::{Address, ObjectDictionary};
22use crate::types::NodeId;
23use crate::Error;
24
25#[derive(Debug)]
31enum Transfer {
32 Upload {
34 addr: Address,
35 data: [u8; MAX_STRING_LEN],
36 len: usize,
37 pos: usize,
38 toggle: bool,
39 },
40 Download {
42 addr: Address,
43 data_type: DataType,
44 buf: Vec<u8, MAX_STRING_LEN>,
45 declared: usize,
46 toggle: bool,
47 },
48}
49
50#[derive(Debug)]
53pub struct SdoServer {
54 node: NodeId,
55 transfer: Option<Transfer>,
56 last_write: Option<Address>,
57}
58
59impl SdoServer {
60 pub const fn new(node: NodeId) -> Self {
62 Self {
63 node,
64 transfer: None,
65 last_write: None,
66 }
67 }
68
69 pub fn request_cob_id(&self) -> u16 {
71 request_cob_id(self.node)
72 }
73
74 pub fn response_cob_id(&self) -> u16 {
76 response_cob_id(self.node)
77 }
78
79 pub fn is_busy(&self) -> bool {
81 self.transfer.is_some()
82 }
83
84 pub fn take_write(&mut self) -> Option<Address> {
89 self.last_write.take()
90 }
91
92 pub fn handle<const N: usize>(
97 &mut self,
98 od: &mut ObjectDictionary<N>,
99 req: &SdoPayload,
100 ) -> Option<SdoPayload> {
101 match req[0] & CS_MASK {
102 CCS_DOWNLOAD_INITIATE => self.on_download_initiate(od, req),
103 CCS_UPLOAD_INITIATE => self.on_upload_initiate(od, req),
104 CCS_DOWNLOAD_SEGMENT => self.on_download_segment(od, req),
105 CCS_UPLOAD_SEGMENT => self.on_upload_segment(req),
106 CS_ABORT => {
107 self.transfer = None;
108 None
109 }
110 _ => abort(req_address(req), SdoAbortCode::CommandInvalid),
111 }
112 }
113
114 fn on_download_initiate<const N: usize>(
115 &mut self,
116 od: &mut ObjectDictionary<N>,
117 req: &SdoPayload,
118 ) -> Option<SdoPayload> {
119 let addr = req_address(req);
120 let (data_type, writable) = match od.entry(addr) {
121 Some(e) => (e.value.data_type(), e.access.is_writable()),
122 None => return abort(addr, SdoAbortCode::ObjectDoesNotExist),
123 };
124 if !writable {
125 return abort(addr, SdoAbortCode::WriteOfReadOnly);
126 }
127
128 if req[0] & EXPEDITED != 0 {
129 let len = if req[0] & SIZE_INDICATED != 0 {
131 4 - ((req[0] >> 2) & 0x03) as usize
132 } else {
133 data_type.fixed_size().unwrap_or(0)
134 };
135 if len > 4 {
139 return abort(addr, SdoAbortCode::DataTypeMismatchLengthHigh);
140 }
141 if let Some(fixed) = data_type.fixed_size() {
144 if len != fixed {
145 return abort(addr, SdoAbortCode::DataTypeMismatchLengthHigh);
146 }
147 }
148 let value = match Value::decode_le(data_type, &req[4..4 + len]) {
149 Ok(v) => v,
150 Err(_) => return abort(addr, SdoAbortCode::General),
151 };
152 write_value(od, addr, value, &mut self.last_write)
153 } else {
154 let (_, size) = match decode_download_initiate_segmented(req) {
156 Ok(x) => x,
157 Err(_) => return abort(addr, SdoAbortCode::CommandInvalid),
158 };
159 let ok = match data_type.fixed_size() {
162 Some(fixed) => size as usize == fixed,
163 None => size as usize <= MAX_STRING_LEN,
164 };
165 if !ok {
166 return abort(addr, SdoAbortCode::DataTypeMismatchLengthHigh);
167 }
168 self.transfer = Some(Transfer::Download {
169 addr,
170 data_type,
171 buf: Vec::new(),
172 declared: size as usize,
173 toggle: false,
174 });
175 Some(encode_download_response(addr))
176 }
177 }
178
179 fn on_upload_initiate<const N: usize>(
180 &mut self,
181 od: &mut ObjectDictionary<N>,
182 req: &SdoPayload,
183 ) -> Option<SdoPayload> {
184 let addr = req_address(req);
185 let value = match od.read(addr) {
186 Ok(v) => v,
187 Err(Error::ObjectNotFound) => return abort(addr, SdoAbortCode::ObjectDoesNotExist),
188 Err(Error::WriteOnly) => return abort(addr, SdoAbortCode::ReadOfWriteOnly),
189 Err(_) => return abort(addr, SdoAbortCode::General),
190 };
191 let size = value.size();
192 if (1..=4).contains(&size) {
193 Some(encode_upload_expedited_response(addr, &value).expect("size 1..=4"))
194 } else {
195 let mut data = [0u8; MAX_STRING_LEN];
196 value
197 .encode_le(&mut data[..size])
198 .expect("size <= MAX_STRING_LEN");
199 self.transfer = Some(Transfer::Upload {
200 addr,
201 data,
202 len: size,
203 pos: 0,
204 toggle: false,
205 });
206 Some(encode_upload_initiate_segmented_response(addr, size as u32))
207 }
208 }
209
210 fn on_upload_segment(&mut self, req: &SdoPayload) -> Option<SdoPayload> {
211 let want_toggle = req[0] & TOGGLE != 0;
212 match self.transfer.take() {
213 Some(Transfer::Upload {
214 addr,
215 data,
216 len,
217 pos,
218 toggle,
219 }) => {
220 if want_toggle != toggle {
221 return abort(addr, SdoAbortCode::ToggleBitNotAlternated);
222 }
223 let remaining = len - pos;
224 let n = remaining.min(SEGMENT_DATA_MAX);
225 let last = remaining <= SEGMENT_DATA_MAX;
226 let seg = encode_data_segment(&data[pos..pos + n], toggle, last).expect("1..=7");
227 if !last {
228 self.transfer = Some(Transfer::Upload {
229 addr,
230 data,
231 len,
232 pos: pos + n,
233 toggle: !toggle,
234 });
235 }
236 Some(seg)
237 }
238 _ => abort(Address::new(0, 0), SdoAbortCode::CommandInvalid),
239 }
240 }
241
242 fn on_download_segment<const N: usize>(
243 &mut self,
244 od: &mut ObjectDictionary<N>,
245 req: &SdoPayload,
246 ) -> Option<SdoPayload> {
247 let seg = match decode_data_segment(req) {
248 Ok(s) => s,
249 Err(_) => return abort(Address::new(0, 0), SdoAbortCode::CommandInvalid),
250 };
251 match self.transfer.take() {
252 Some(Transfer::Download {
253 addr,
254 data_type,
255 mut buf,
256 declared,
257 toggle,
258 }) => {
259 if seg.toggle != toggle {
260 return abort(addr, SdoAbortCode::ToggleBitNotAlternated);
261 }
262 if buf.extend_from_slice(seg.data).is_err() {
263 return abort(addr, SdoAbortCode::DataTypeMismatchLengthHigh);
264 }
265 let ack = encode_download_segment_response(seg.toggle);
266 if seg.last {
267 if buf.len() != declared {
268 return abort(addr, SdoAbortCode::DataTypeMismatchLengthLow);
269 }
270 let value = match Value::decode_le(data_type, &buf) {
271 Ok(v) => v,
272 Err(_) => return abort(addr, SdoAbortCode::General),
273 };
274 match od.write(addr, value) {
277 Ok(()) => {
278 self.last_write = Some(addr);
279 Some(ack)
280 }
281 Err(Error::ReadOnly) => abort(addr, SdoAbortCode::WriteOfReadOnly),
282 Err(_) => abort(addr, SdoAbortCode::General),
283 }
284 } else {
285 self.transfer = Some(Transfer::Download {
286 addr,
287 data_type,
288 buf,
289 declared,
290 toggle: !toggle,
291 });
292 Some(ack)
293 }
294 }
295 _ => abort(Address::new(0, 0), SdoAbortCode::CommandInvalid),
296 }
297 }
298}
299
300fn write_value<const N: usize>(
303 od: &mut ObjectDictionary<N>,
304 addr: Address,
305 value: Value,
306 last_write: &mut Option<Address>,
307) -> Option<SdoPayload> {
308 match od.write(addr, value) {
309 Ok(()) => {
310 *last_write = Some(addr);
311 Some(encode_download_response(addr))
312 }
313 Err(Error::ReadOnly) => abort(addr, SdoAbortCode::WriteOfReadOnly),
314 Err(Error::TypeMismatch) => abort(addr, SdoAbortCode::DataTypeMismatchLengthHigh),
315 Err(_) => abort(addr, SdoAbortCode::General),
316 }
317}
318
319fn req_address(req: &SdoPayload) -> Address {
320 Address::new(u16::from_le_bytes([req[1], req[2]]), req[3])
321}
322
323fn abort(addr: Address, code: SdoAbortCode) -> Option<SdoPayload> {
324 Some(encode_abort(addr, code))
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330 use crate::object_dictionary::Entry;
331
332 fn server() -> SdoServer {
333 SdoServer::new(NodeId::new(1).unwrap())
334 }
335
336 fn od() -> ObjectDictionary<8> {
337 let mut od = ObjectDictionary::new();
338 od.insert(
339 Address::new(0x1000, 0),
340 Entry::constant(Value::Unsigned32(0x0000_0192)),
341 )
342 .unwrap();
343 od.insert(Address::new(0x1017, 0), Entry::rw(Value::Unsigned16(1000)))
344 .unwrap();
345 od.insert(Address::new(0x2000, 0), Entry::rw(Value::Unsigned64(0)))
346 .unwrap();
347 od
348 }
349
350 #[test]
351 fn cob_ids_track_node() {
352 let s = SdoServer::new(NodeId::new(5).unwrap());
353 assert_eq!(s.request_cob_id(), 0x605);
354 assert_eq!(s.response_cob_id(), 0x585);
355 }
356
357 #[test]
358 fn expedited_read_returns_value() {
359 let mut od = od();
360 let req = super::super::encode_upload_request(Address::new(0x1000, 0));
361 let resp = server().handle(&mut od, &req).unwrap();
362 let (_, value) =
363 super::super::decode_upload_expedited_response(&resp, DataType::Unsigned32).unwrap();
364 assert_eq!(value, Value::Unsigned32(0x192));
365 }
366
367 #[test]
368 fn expedited_write_updates_od() {
369 let mut od = od();
370 let req = super::super::encode_download_expedited(
371 Address::new(0x1017, 0),
372 &Value::Unsigned16(1234),
373 )
374 .unwrap();
375 let resp = server().handle(&mut od, &req).unwrap();
376 assert!(super::super::decode_download_response(&resp).is_ok());
377 assert_eq!(
378 od.read(Address::new(0x1017, 0)).unwrap(),
379 Value::Unsigned16(1234)
380 );
381 }
382
383 #[test]
384 fn take_write_reports_the_written_object_once() {
385 let mut od = od();
386 let mut s = server();
387 assert_eq!(s.take_write(), None);
389
390 let req = super::super::encode_download_expedited(
391 Address::new(0x1017, 0),
392 &Value::Unsigned16(1234),
393 )
394 .unwrap();
395 s.handle(&mut od, &req).unwrap();
396 assert_eq!(s.take_write(), Some(Address::new(0x1017, 0)));
398 assert_eq!(s.take_write(), None);
399
400 let ro =
402 super::super::encode_download_expedited(Address::new(0x1000, 0), &Value::Unsigned32(1))
403 .unwrap();
404 s.handle(&mut od, &ro).unwrap();
405 assert_eq!(s.take_write(), None);
406 }
407
408 #[test]
409 fn read_missing_object_aborts() {
410 let mut od = od();
411 let req = super::super::encode_upload_request(Address::new(0x9999, 0));
412 let resp = server().handle(&mut od, &req).unwrap();
413 let (_, code) = super::super::decode_abort(&resp).unwrap();
414 assert_eq!(code, 0x0602_0000);
415 }
416
417 #[test]
418 fn write_read_only_aborts() {
419 let mut od = od();
420 let req =
421 super::super::encode_download_expedited(Address::new(0x1000, 0), &Value::Unsigned32(1))
422 .unwrap();
423 let resp = server().handle(&mut od, &req).unwrap();
424 let (_, code) = super::super::decode_abort(&resp).unwrap();
425 assert_eq!(code, 0x0601_0002); }
427
428 #[test]
429 fn client_abort_clears_state_without_reply() {
430 let mut od = od();
431 let mut s = server();
432 let init = super::super::encode_download_initiate_segmented(Address::new(0x2000, 0), 8);
434 s.handle(&mut od, &init).unwrap();
435 assert!(s.is_busy());
436 let abort_frame =
437 super::super::encode_abort(Address::new(0x2000, 0), SdoAbortCode::General);
438 assert!(s.handle(&mut od, &abort_frame).is_none());
439 assert!(!s.is_busy());
440 }
441}