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}