canadensis_encoding/
lib.rs

1//!
2//! # Cyphal data type serialization and deserialization
3//!
4
5#![cfg_attr(not(test), no_std)]
6#![deny(missing_docs)]
7
8extern crate half;
9extern crate zerocopy;
10
11pub mod bits;
12mod cursor;
13
14pub use crate::cursor::deserialize::ReadCursor;
15pub use crate::cursor::serialize::WriteCursor;
16use core::cmp;
17use zerocopy::{AsBytes, FromBytes};
18
19/// Trait for types that can be encoded into Cyphal transfers, or decoded from transfers
20pub trait DataType {
21    /// The sealed or delimited property of this type
22    const EXTENT_BYTES: Option<u32>;
23}
24
25/// Trait for types that can be serialized into Cyphal transfers
26pub trait Serialize: DataType {
27    /// Returns the size of the encoded form of this value, in bits
28    ///
29    /// The returned value may not be a multiple of 8.
30    fn size_bits(&self) -> usize;
31
32    /// Serializes this value into a buffer
33    ///
34    /// The provided cursor will allow writing at least the number of bits returned by the
35    /// [`size_bits()`](#tymethod.size_bits) function.
36    fn serialize(&self, cursor: &mut WriteCursor<'_>);
37
38    /// A convenience function that creates a cursor around the provided bytes and calls
39    /// [`serialize`](#tymethod.serialize)
40    fn serialize_to_bytes(&self, bytes: &mut [u8]) {
41        let mut cursor = WriteCursor::new(bytes);
42        self.serialize(&mut cursor);
43    }
44}
45
46/// Trait for types that can be deserialized from Cyphal transfers
47pub trait Deserialize: DataType {
48    /// Deserializes a value and returns it
49    fn deserialize(cursor: &mut ReadCursor<'_>) -> Result<Self, DeserializeError>
50    where
51        Self: Sized;
52
53    /// Deserializes a value from a slice of bytes and returns it
54    ///
55    /// This is available only for types that implement [`Sized`], [`AsBytes`], and [`FromBytes`].
56    ///
57    /// # Panics
58    ///
59    /// This function panics if the provided cursor is not aligned to a byte boundary.
60    fn deserialize_zero_copy(cursor: &mut ReadCursor<'_>) -> Self
61    where
62        Self: Sized + AsBytes + FromBytes,
63    {
64        // This isn't quite zero-copy. It's one-copy, but it eliminates handling each field
65        // individually.
66        let cursor_bytes = cursor.as_bytes().expect("Cursor not aligned");
67        let mut value = Self::new_zeroed();
68
69        let value_bytes = value.as_bytes_mut();
70        // To apply implicit truncation and zero extension, copy whatever bytes we can
71        let bytes_to_copy = cmp::min(value_bytes.len(), cursor_bytes.len());
72        value_bytes[..bytes_to_copy].copy_from_slice(&cursor_bytes[..bytes_to_copy]);
73
74        cursor.advance_bytes(bytes_to_copy);
75
76        value
77    }
78
79    /// A convenience function that creates a cursor around the provided bytes and calls
80    /// [`deserialize`](#tymethod.deserialize)
81    fn deserialize_from_bytes(bytes: &[u8]) -> Result<Self, DeserializeError>
82    where
83        Self: Sized,
84    {
85        let mut cursor = ReadCursor::new(bytes);
86        Self::deserialize(&mut cursor)
87    }
88}
89
90/// Marker for message data types
91pub trait Message {}
92/// Marker for service request data types
93pub trait Request {}
94/// Marker for service response data types
95pub trait Response {}
96
97/// Errors that can occur when deserializing
98#[non_exhaustive]
99#[derive(Debug)]
100pub enum DeserializeError {
101    /// A variable-length array length field was greater than the maximum allowed length
102    ArrayLength,
103    /// A union tag field did not correspond to a known variant
104    UnionTag,
105    /// A delimiter header had a length that was not valid for the expected type
106    DelimitedLength,
107}