zencan_node/object_dict/
objects.rs

1//! Traits and types for implementing objects in the OD
2
3use zencan_common::{
4    objects::{AccessType, DataType, ObjectCode, SubInfo},
5    sdo::AbortCode,
6    AtomicCell,
7};
8
9use super::{ObjectFlagAccess, SubObjectAccess};
10
11/// A trait for accessing objects
12///
13/// Any struct which implements an object in the object dictionary must implement this trait
14pub trait ObjectAccess: Sync + Send {
15    /// Read raw bytes from a subobject
16    ///
17    /// If the specified read goes out of range (i.e. offset + buf.len() > current_size) an error is
18    /// returned. All implementers are required to allow reading a subset of the object bytes, i.e.
19    /// offset may be non-zero, and/or the buf length may be shorter than the object data
20    fn read(&self, sub: u8, offset: usize, buf: &mut [u8]) -> Result<usize, AbortCode>;
21
22    /// Get the number of bytes available for a read
23    fn read_size(&self, sub: u8) -> Result<usize, AbortCode>;
24
25    /// Write raw bytes to a subobject
26    ///
27    /// The length of `data` must match the size of the object, or else it will fail with either
28    /// [`AbortCode::DataTypeMismatchLengthLow`] or [`AbortCode::DataTypeMismatchLengthHigh`].
29    ///
30    /// If the sub is does not exist, it shall fail with [`AbortCode::NoSuchSubIndex`].
31    ///
32    /// If the sub exists but is not writeable, it shall fail with [`AbortCode::ReadOnly`].
33    fn write(&self, sub: u8, data: &[u8]) -> Result<(), AbortCode>;
34
35    /// Initialize a new partial write
36    ///
37    /// This must be called before performing calls to `partial_write`.
38    ///
39    /// A default implementation is provided which returns an appropriate error:
40    /// - [`AbortCode::NoSuchSubIndex`] if the sub object does not exist
41    /// - [`AbortCode::ReadOnly`] if the sub object is read-only
42    /// - [`AbortCode::UnsupportedAccess`] if the sub object does not support partial writes
43    ///
44    /// Objects which support partial writing must override the default implementation.
45    fn begin_partial(&self, sub: u8) -> Result<(), AbortCode> {
46        if let Ok(sub_info) = self.sub_info(sub) {
47            if sub_info.access_type.is_writable() {
48                Err(AbortCode::UnsupportedAccess)
49            } else {
50                Err(AbortCode::ReadOnly)
51            }
52        } else {
53            Err(AbortCode::NoSuchSubIndex)
54        }
55    }
56
57    /// Perform a partial write of bytes to a subobject
58    ///
59    /// Most objects do not support partial writes. But in some cases, such as DOMAINs, or very
60    /// large string objects, it is unavoidable and these must support it.
61    ///
62    /// Partial writes MUST be done sequentially, and implementers may assume that this is the case.
63    /// Executing multiple concurrent partial writes to the same sub object is not supported. It is
64    /// up to the application to ensure that this is not done.
65    fn write_partial(&self, _sub: u8, _buf: &[u8]) -> Result<(), AbortCode> {
66        // All callers should have failed at begin_partial, so this should never be called
67        Err(AbortCode::GeneralError)
68    }
69
70    /// Finalize a previous partial write
71    ///
72    /// This must always be called after using partial_write, after all partial_write calls have
73    /// been completed.
74    fn end_partial(&self, _sub: u8) -> Result<(), AbortCode> {
75        Err(AbortCode::GeneralError)
76    }
77
78    /// Get the type of this object
79    fn object_code(&self) -> ObjectCode;
80
81    /// Get metadata about a sub object
82    fn sub_info(&self, sub: u8) -> Result<SubInfo, AbortCode>;
83
84    /// Get the highest sub index available in this object
85    fn max_sub_number(&self) -> u8 {
86        match self.object_code() {
87            ObjectCode::Null => 0,
88            ObjectCode::Domain => 0,
89            ObjectCode::DefType => 0,
90            ObjectCode::DefStruct => 0,
91            ObjectCode::Var => 0,
92            ObjectCode::Array => self.read_u8(0).unwrap(),
93            ObjectCode::Record => self.read_u8(0).unwrap(),
94        }
95    }
96
97    /// Set an event flag for the specified sub object on this object
98    ///
99    /// Event flags are used for triggering PDOs. This is optional, as not all objects support PDOs
100    /// or PDO triggering.
101    fn set_event_flag(&self, _sub: u8) -> Result<(), AbortCode> {
102        Err(AbortCode::UnsupportedAccess)
103    }
104
105    /// Read an event flag for the specified sub object
106    ///
107    /// This is optional as not all objects support events
108    fn read_event_flag(&self, _sub: u8) -> bool {
109        false
110    }
111
112    /// Clear event flags for all sub objects
113    ///
114    /// This is optional as not all objects support events
115    fn clear_events(&self) {}
116
117    /// Get the access type of a specific sub object
118    fn access_type(&self, sub: u8) -> Result<AccessType, AbortCode> {
119        Ok(self.sub_info(sub)?.access_type)
120    }
121
122    /// Get the data type of a specific sub object
123    fn data_type(&self, sub: u8) -> Result<DataType, AbortCode> {
124        Ok(self.sub_info(sub)?.data_type)
125    }
126
127    /// Get the maximum size of an sub object
128    ///
129    /// For most sub objects, this matches the current_size, but for strings the size of the
130    /// currently stored value (returned by `current_size()`) may be smaller.
131    fn size(&self, sub: u8) -> Result<usize, AbortCode> {
132        Ok(self.sub_info(sub)?.size)
133    }
134
135    /// Get the current size of a sub object
136    ///
137    /// Note that this is not necessarily the allocated size of the object, as some objects (such as
138    /// strings) may have values shorter than their maximum size. As such, this gives the maximum
139    /// number of bytes which may be read, but not necessarily the number of bytes which may be
140    /// written.
141    fn current_size(&self, sub: u8) -> Result<usize, AbortCode> {
142        const CHUNK_SIZE: usize = 8;
143
144        let size = self.size(sub)?;
145        if self.data_type(sub)?.is_str() {
146            // Look for first 0
147            let mut chunk = 0;
148            let mut buf = [0; CHUNK_SIZE];
149            while chunk < size / CHUNK_SIZE + 1 {
150                let offset = chunk * CHUNK_SIZE;
151                let bytes_to_read = (size - offset).min(CHUNK_SIZE);
152                self.read(sub, offset, &mut buf[0..bytes_to_read])?;
153
154                if let Some(zero_pos) = buf[0..bytes_to_read].iter().position(|b| *b == 0) {
155                    return Ok(zero_pos + chunk * CHUNK_SIZE);
156                }
157                chunk += 1;
158            }
159        }
160        // not a string type or no null-terminator was found
161        Ok(size)
162    }
163
164    /// Read a sub object as a u32
165    fn read_u32(&self, sub: u8) -> Result<u32, AbortCode> {
166        let mut buf = [0; 4];
167        self.read(sub, 0, &mut buf)?;
168        Ok(u32::from_le_bytes(buf))
169    }
170
171    /// Read a sub object as a u16
172    fn read_u16(&self, sub: u8) -> Result<u16, AbortCode> {
173        let mut buf = [0; 2];
174        self.read(sub, 0, &mut buf)?;
175        Ok(u16::from_le_bytes(buf))
176    }
177
178    /// Read a sub object as a u8
179    fn read_u8(&self, sub: u8) -> Result<u8, AbortCode> {
180        let mut buf = [0; 1];
181        self.read(sub, 0, &mut buf)?;
182        Ok(buf[0])
183    }
184
185    /// Read a sub object as an i32
186    fn read_i32(&self, sub: u8) -> Result<i32, AbortCode> {
187        let mut buf = [0; 4];
188        self.read(sub, 0, &mut buf)?;
189        Ok(i32::from_le_bytes(buf))
190    }
191
192    /// Read a sub object as an i16
193    fn read_i16(&self, sub: u8) -> Result<i16, AbortCode> {
194        let mut buf = [0; 2];
195        self.read(sub, 0, &mut buf)?;
196        Ok(i16::from_le_bytes(buf))
197    }
198
199    /// Read a sub object as an i8
200    fn read_i8(&self, sub: u8) -> Result<i8, AbortCode> {
201        let mut buf = [0; 1];
202        self.read(sub, 0, &mut buf)?;
203        Ok(buf[0] as i8)
204    }
205}
206/// A trait for structs which represent Objects to implement
207///
208/// Implementing this type allows a type sub object which implements [`SubObjectAccess`] to
209/// implement [`ObjectAccess`] simply by implementing this trait to provide a sub object for each
210/// sub index.
211pub trait ProvidesSubObjects {
212    /// Get a sub object by index
213    ///
214    /// This is used for objects which have a fixed number of sub objects, such as arrays or records.
215    ///
216    /// It should return None if the sub object does not exist, and when it does exist it returns a
217    /// tuple containing a [`SubInfo`] with metadata about the sub object, and [`&dyn
218    /// SubObjectAccess`](SubObjectAccess) which provides read/write access to the sub object data.
219    fn get_sub_object(&self, sub: u8) -> Option<(SubInfo, &dyn SubObjectAccess)>;
220
221    /// Get the object flags for this object
222    ///
223    /// Notification flags are supported by some objects to indicate changes made in the object by
224    /// the application -- for example, to trigger the transmission of a mapped PDO.
225    ///
226    /// If the object supports flags, it should override this method to return a reference to them
227    fn flags(&self) -> Option<&dyn ObjectFlagAccess> {
228        None
229    }
230
231    /// What type of object is this
232    fn object_code(&self) -> ObjectCode;
233}
234
235// Implement ObjectAccess for any type that implements ProvidesSubObjects
236impl<T: ProvidesSubObjects + Sync + Send> ObjectAccess for T {
237    fn read(&self, sub: u8, offset: usize, buf: &mut [u8]) -> Result<usize, AbortCode> {
238        if let Some((info, access)) = self.get_sub_object(sub) {
239            if info.access_type.is_readable() {
240                access.read(offset, buf)
241            } else {
242                Err(AbortCode::WriteOnly)
243            }
244        } else {
245            Err(AbortCode::NoSuchSubIndex)
246        }
247    }
248
249    fn read_size(&self, sub: u8) -> Result<usize, AbortCode> {
250        if let Some((_info, access)) = self.get_sub_object(sub) {
251            Ok(access.read_size())
252        } else {
253            Err(AbortCode::NoSuchSubIndex)
254        }
255    }
256
257    fn write(&self, sub: u8, data: &[u8]) -> Result<(), AbortCode> {
258        if let Some((info, access)) = self.get_sub_object(sub) {
259            if info.access_type.is_writable() {
260                access.write(data)
261            } else {
262                Err(AbortCode::ReadOnly)
263            }
264        } else {
265            Err(AbortCode::NoSuchSubIndex)
266        }
267    }
268
269    fn begin_partial(&self, sub: u8) -> Result<(), AbortCode> {
270        if let Some((info, access)) = self.get_sub_object(sub) {
271            if info.access_type.is_writable() {
272                access.begin_partial()
273            } else {
274                Err(AbortCode::ReadOnly)
275            }
276        } else {
277            Err(AbortCode::NoSuchSubIndex)
278        }
279    }
280
281    fn write_partial(&self, sub: u8, buf: &[u8]) -> Result<(), AbortCode> {
282        if let Some((_, access)) = self.get_sub_object(sub) {
283            access.write_partial(buf)
284        } else {
285            Err(AbortCode::NoSuchSubIndex)
286        }
287    }
288
289    fn end_partial(&self, sub: u8) -> Result<(), AbortCode> {
290        if let Some((_, access)) = self.get_sub_object(sub) {
291            access.end_partial()
292        } else {
293            Err(AbortCode::NoSuchSubIndex)
294        }
295    }
296
297    fn set_event_flag(&self, sub: u8) -> Result<(), AbortCode> {
298        if let Some(flags) = self.flags() {
299            flags.set_flag(sub);
300            Ok(())
301        } else {
302            Err(AbortCode::UnsupportedAccess)
303        }
304    }
305
306    fn read_event_flag(&self, sub: u8) -> bool {
307        if let Some(flags) = self.flags() {
308            flags.get_flag(sub)
309        } else {
310            false
311        }
312    }
313
314    fn object_code(&self) -> ObjectCode {
315        self.object_code()
316    }
317
318    fn sub_info(&self, sub: u8) -> Result<SubInfo, AbortCode> {
319        if let Some((info, _access)) = self.get_sub_object(sub) {
320            Ok(info)
321        } else {
322            Err(AbortCode::NoSuchSubIndex)
323        }
324    }
325}
326
327/// OD placeholder for an object which will have a handler registered at runtime
328#[allow(missing_debug_implementations)]
329pub struct CallbackObject<'a> {
330    obj: AtomicCell<Option<&'a dyn ObjectAccess>>,
331    object_code: ObjectCode,
332}
333
334impl CallbackObject<'_> {
335    /// Create a new callback
336    pub fn new(object_code: ObjectCode) -> Self {
337        Self {
338            obj: AtomicCell::new(None),
339            object_code,
340        }
341    }
342}
343
344impl ObjectAccess for CallbackObject<'_> {
345    fn read(&self, sub: u8, offset: usize, buf: &mut [u8]) -> Result<usize, AbortCode> {
346        if let Some(obj) = self.obj.load() {
347            obj.read(sub, offset, buf)
348        } else {
349            Err(AbortCode::ResourceNotAvailable)
350        }
351    }
352
353    fn read_size(&self, sub: u8) -> Result<usize, AbortCode> {
354        if let Some(obj) = self.obj.load() {
355            obj.read_size(sub)
356        } else {
357            Err(AbortCode::ResourceNotAvailable)
358        }
359    }
360
361    fn write(&self, sub: u8, data: &[u8]) -> Result<(), AbortCode> {
362        if let Some(obj) = self.obj.load() {
363            obj.write(sub, data)
364        } else {
365            Err(AbortCode::ResourceNotAvailable)
366        }
367    }
368
369    fn write_partial(&self, sub: u8, buf: &[u8]) -> Result<(), AbortCode> {
370        if let Some(obj) = self.obj.load() {
371            obj.write_partial(sub, buf)
372        } else {
373            Err(AbortCode::ResourceNotAvailable)
374        }
375    }
376
377    fn end_partial(&self, sub: u8) -> Result<(), AbortCode> {
378        if let Some(obj) = self.obj.load() {
379            obj.end_partial(sub)
380        } else {
381            Err(AbortCode::ResourceNotAvailable)
382        }
383    }
384
385    fn object_code(&self) -> ObjectCode {
386        self.object_code
387    }
388
389    fn sub_info(&self, sub: u8) -> Result<SubInfo, AbortCode> {
390        if let Some(obj) = self.obj.load() {
391            obj.sub_info(sub)
392        } else {
393            Err(AbortCode::ResourceNotAvailable)
394        }
395    }
396}
397
398/// Represents one item in the in-memory table of objects
399#[allow(missing_debug_implementations)]
400pub struct ODEntry<'a> {
401    /// The object index
402    pub index: u16,
403    /// The object implementation
404    pub data: &'a dyn ObjectAccess,
405}
406
407/// Lookup an object from the Object dictionary table
408///
409/// Note: `table` must be sorted by index
410pub fn find_object<'a>(table: &[ODEntry<'a>], index: u16) -> Option<&'a dyn ObjectAccess> {
411    find_object_entry(table, index).map(|entry| entry.data)
412}
413
414/// Lookup an entry from the object dictionary table
415///
416/// The same as [find_object], except that it returned the `&ODEntry` instead of the `&ObjectData`
417/// it owns
418///
419/// Note: `table` must be sorted by index
420pub fn find_object_entry<'a, 'b>(table: &'b [ODEntry<'a>], index: u16) -> Option<&'b ODEntry<'a>> {
421    table
422        .binary_search_by_key(&index, |e| e.index)
423        .ok()
424        .map(|i| &table[i])
425}