emcyphal_core/
lib.rs

1//! Cyphal protocol core data types
2//!
3//! This crate provides basic data type definitions used by other Emcyphal crates.
4//! Emcyphal users should not depend on this crate directly. Use `emcyphal::core` reexport instead.
5#![no_std]
6
7#[derive(Debug)]
8#[cfg_attr(feature = "defmt", derive(defmt::Format))]
9pub struct InvalidValue;
10
11/// Transfer priority [1; 4.1.1.3]
12///
13/// The type has explicit numeric encoding to facilitate look-up table implementation.
14/// The encoding matches the CAN ID encoding [1; 4.2.1.1], thus the ordering is reversed:
15/// Optional > Exceptional
16///
17#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
18#[cfg_attr(feature = "defmt", derive(defmt::Format))]
19#[repr(u8)]
20pub enum Priority {
21    /// The bus designer can ignore these messages when calculating bus load since they should
22    /// only be sent when a total system failure has occurred. For example, a self-destruct message
23    /// on a rocket would use this priority. Another analogy is an NMI on a microcontroller.
24    Exceptional = 0,
25    /// Immediate is a "high priority message" but with additional latency constraints. Since
26    /// exceptional messages are not considered when designing a bus, the latency of immediate
27    /// messages can be determined by considering only immediate messages.
28    Immediate = 1,
29    /// Fast and immediate are both "high priority messages" but with additional latency
30    /// constraints. Since exceptional messages are not considered when designing a bus, the latency
31    /// of fast messages can be determined by considering only immediate and fast messages.
32    Fast = 2,
33    /// High priority messages are more important than nominal messages but have looser latency
34    /// requirements than fast messages. This priority is used so that, in the presence of rogue
35    /// nominal messages, important commands can be received. For example, one might envision a
36    /// failure mode where a temperature sensor starts to load a vehicle bus with nominal messages.
37    /// The vehicle remains operational (for a time) because the controller is exchanging fast and
38    /// immediate messages with sensors and actuators. A system safety monitor is able to detect the
39    /// distressed bus and command the vehicle to a safe state by sending high priority messages to
40    /// the controller.
41    High = 3,
42    /// This is what all messages should use by default. Specifically, heartbeat messages should
43    /// use this priority.
44    Nominal = 4,
45    /// Low priority messages are expected to be sent on a bus under all conditions but cannot
46    /// prevent the delivery of nominal messages. They are allowed to be delayed, but latency should
47    /// be constrained by the bus designer.
48    Low = 5,
49    /// Slow messages are low priority messages that have no time sensitivity at all. The bus
50    /// designer need only ensure that for all possible system states, these messages will
51    /// eventually be sent.
52    Slow = 6,
53    /// These messages might never be sent (theoretically) for some possible system states. The
54    /// system shall tolerate never exchanging optional messages in every possible state. The bus
55    /// designer can ignore these messages when calculating bus load. This should be the priority
56    /// used for diagnostic or debug messages that are not required on an operational system.
57    Optional = 7,
58}
59
60impl Priority {
61    pub const MIN: Priority = Priority::Exceptional;
62    pub const MAX: Priority = Priority::Optional;
63
64    pub const fn try_from_u8(code: u8) -> Option<Priority> {
65        if code <= Self::MAX.into_u8() {
66            Some(Priority::from_u8_truncating(code))
67        } else {
68            None
69        }
70    }
71
72    pub const fn from_u8_truncating(code: u8) -> Priority {
73        match code & 0x7 {
74            0 => Priority::Exceptional,
75            1 => Priority::Immediate,
76            2 => Priority::Fast,
77            3 => Priority::High,
78            4 => Priority::Nominal,
79            5 => Priority::Low,
80            6 => Priority::Slow,
81            7 => Priority::Optional,
82            _ => unreachable!(),
83        }
84    }
85
86    pub const fn into_u8(self) -> u8 {
87        self as u8
88    }
89
90    pub const fn next(self) -> Option<Self> {
91        Self::try_from_u8(self.into_u8() + 1)
92    }
93
94    pub const fn prev(self) -> Option<Self> {
95        if let Some(code) = self.into_u8().checked_sub(1) {
96            Some(Self::from_u8_truncating(code))
97        } else {
98            None
99        }
100    }
101}
102
103impl From<Priority> for u8 {
104    fn from(value: Priority) -> Self {
105        value.into_u8()
106    }
107}
108
109impl From<Priority> for usize {
110    fn from(value: Priority) -> Self {
111        u8::from(value).into()
112    }
113}
114
115impl TryFrom<u8> for Priority {
116    type Error = InvalidValue;
117
118    fn try_from(value: u8) -> Result<Self, Self::Error> {
119        Self::try_from_u8(value).ok_or(InvalidValue)
120    }
121}
122
123/// A set of priority values
124///
125/// Note that higher priority has a lower numerical value and is ordered first.
126/// Methods are named according to numerical priority values, e.g., `new_ge(Priority::Nominal)`
127/// returns a set containing `Nominal`, `Low`, `Slow`, and `Optional`.
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129#[cfg_attr(feature = "defmt", derive(defmt::Format))]
130pub struct PrioritySet(u8);
131
132impl PrioritySet {
133    pub const NONE: Self = Self(0);
134    pub const ALL: Self = Self(u8::MAX);
135
136    pub const fn from_bits(bits: u8) -> Self {
137        Self(bits)
138    }
139
140    pub const fn into_bits(self) -> u8 {
141        self.0
142    }
143
144    pub const fn complement(self) -> Self {
145        Self(!self.0)
146    }
147
148    pub const fn new_eq(priority: Priority) -> Self {
149        Self(1u8 << priority.into_u8())
150    }
151
152    pub const fn new_ge(priority: Priority) -> Self {
153        Self(u8::MAX << priority.into_u8())
154    }
155
156    pub const fn new_le(priority: Priority) -> Self {
157        Self(u8::MAX >> (Priority::MAX.into_u8() - priority.into_u8()))
158    }
159
160    pub const fn new_gt(priority: Priority) -> Self {
161        Self::new_le(priority).complement()
162    }
163
164    pub const fn new_lt(priority: Priority) -> Self {
165        Self::new_ge(priority).complement()
166    }
167
168    pub const fn contains(&self, priority: Priority) -> bool {
169        (self.0 >> priority.into_u8()) & 0x1 != 0
170    }
171
172    pub const fn insert(&mut self, priority: Priority) {
173        self.0 |= Self::new_eq(priority).0
174    }
175
176    pub const fn remove(&mut self, priority: Priority) {
177        self.0 &= Self::new_eq(priority).complement().0
178    }
179
180    pub const fn first(&self) -> Option<Priority> {
181        Priority::try_from_u8(self.0.trailing_zeros() as u8)
182    }
183
184    pub const fn last(&self) -> Option<Priority> {
185        let n = u8::BITS - self.0.leading_zeros();
186        Priority::try_from_u8((n as u8).wrapping_sub(1))
187    }
188
189    pub const fn is_empty(&self) -> bool {
190        self.0 == Self::NONE.0
191    }
192}
193
194impl Default for PrioritySet {
195    fn default() -> Self {
196        PrioritySet::NONE
197    }
198}
199
200impl core::ops::Not for PrioritySet {
201    type Output = Self;
202    fn not(self) -> Self::Output {
203        Self(!self.0)
204    }
205}
206
207impl core::ops::BitAnd<PrioritySet> for PrioritySet {
208    type Output = Self;
209    fn bitand(self, rhs: PrioritySet) -> Self::Output {
210        PrioritySet(self.0 & rhs.0)
211    }
212}
213
214impl core::ops::BitAndAssign<PrioritySet> for PrioritySet {
215    fn bitand_assign(&mut self, rhs: PrioritySet) {
216        self.0 &= rhs.0
217    }
218}
219
220impl core::ops::BitOr<PrioritySet> for PrioritySet {
221    type Output = Self;
222    fn bitor(self, rhs: PrioritySet) -> Self::Output {
223        PrioritySet(self.0 | rhs.0)
224    }
225}
226
227impl core::ops::BitOrAssign<PrioritySet> for PrioritySet {
228    fn bitor_assign(&mut self, rhs: PrioritySet) {
229        self.0 |= rhs.0;
230    }
231}
232
233impl core::iter::IntoIterator for PrioritySet {
234    type Item = Priority;
235    type IntoIter = PrioritySetIterator;
236    fn into_iter(self) -> Self::IntoIter {
237        PrioritySetIterator { residual: self }
238    }
239}
240
241pub struct PrioritySetIterator {
242    residual: PrioritySet,
243}
244
245impl core::iter::Iterator for PrioritySetIterator {
246    type Item = Priority;
247    fn next(&mut self) -> Option<Self::Item> {
248        let first = self.residual.first();
249        if let Some(priority) = first {
250            self.residual.remove(priority);
251        }
252        first
253    }
254}
255
256#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
257#[cfg_attr(feature = "defmt", derive(defmt::Format))]
258pub struct NodeId(u8);
259
260impl NodeId {
261    const MAX_VALUE: u8 = 0x7f;
262    pub const MAX: NodeId = NodeId(0x7f);
263
264    pub const fn new(value: u8) -> Option<Self> {
265        if value <= Self::MAX_VALUE {
266            Some(Self::from_u8_truncating(value))
267        } else {
268            None
269        }
270    }
271
272    pub const fn from_u8_truncating(value: u8) -> Self {
273        Self(value & Self::MAX_VALUE)
274    }
275
276    pub const fn into_u8(self) -> u8 {
277        self.0
278    }
279}
280
281impl From<NodeId> for u8 {
282    fn from(value: NodeId) -> Self {
283        value.into_u8()
284    }
285}
286
287impl From<NodeId> for usize {
288    fn from(value: NodeId) -> Self {
289        u8::from(value).into()
290    }
291}
292
293impl TryFrom<u8> for NodeId {
294    type Error = InvalidValue;
295
296    fn try_from(value: u8) -> Result<Self, Self::Error> {
297        Self::new(value).ok_or(InvalidValue)
298    }
299}
300
301#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
302#[cfg_attr(feature = "defmt", derive(defmt::Format))]
303pub struct SubjectId(u16);
304
305impl SubjectId {
306    const MAX_VALUE: u16 = 0x1fff;
307    pub const MAX: SubjectId = SubjectId(Self::MAX_VALUE);
308
309    pub const fn new(value: u16) -> Option<Self> {
310        if value <= Self::MAX_VALUE {
311            Some(Self::from_u16_truncating(value))
312        } else {
313            None
314        }
315    }
316
317    pub const fn from_u16_truncating(value: u16) -> Self {
318        Self(value & Self::MAX_VALUE)
319    }
320
321    pub const fn into_u16(self) -> u16 {
322        self.0
323    }
324}
325
326impl From<SubjectId> for u16 {
327    fn from(value: SubjectId) -> Self {
328        value.into_u16()
329    }
330}
331
332impl TryFrom<u16> for SubjectId {
333    type Error = InvalidValue;
334
335    fn try_from(value: u16) -> Result<Self, Self::Error> {
336        Self::new(value).ok_or(InvalidValue)
337    }
338}
339
340#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
341#[cfg_attr(feature = "defmt", derive(defmt::Format))]
342pub struct ServiceId(u16);
343
344impl ServiceId {
345    const MAX_VALUE: u16 = 0x1ff;
346    pub const MAX: SubjectId = SubjectId(Self::MAX_VALUE);
347
348    pub const fn new(value: u16) -> Option<Self> {
349        if value <= Self::MAX_VALUE {
350            Some(Self::from_u16_truncating(value))
351        } else {
352            None
353        }
354    }
355
356    pub const fn from_u16_truncating(value: u16) -> Self {
357        Self(value & Self::MAX_VALUE)
358    }
359
360    pub const fn into_u16(self) -> u16 {
361        self.0
362    }
363}
364
365impl From<ServiceId> for u16 {
366    fn from(value: ServiceId) -> Self {
367        value.into_u16()
368    }
369}
370
371impl TryFrom<u16> for ServiceId {
372    type Error = InvalidValue;
373
374    fn try_from(value: u16) -> Result<Self, Self::Error> {
375        Self::new(value).ok_or(InvalidValue)
376    }
377}
378
379#[derive(Debug, Copy, Clone, PartialEq, Eq)]
380#[cfg_attr(feature = "defmt", derive(defmt::Format))]
381pub struct TransferId(u8);
382
383impl TransferId {
384    const MAX_VALUE: u8 = 0x1f;
385    pub const MAX: TransferId = TransferId(Self::MAX_VALUE);
386
387    /// TransferId of the first transfer in the session, see [1; 4.1.1.7]
388    pub const SESSION_START: TransferId = TransferId(0);
389
390    pub const fn new(value: u8) -> Option<Self> {
391        if value <= Self::MAX_VALUE {
392            Some(Self::from_u8_truncating(value))
393        } else {
394            None
395        }
396    }
397
398    pub const fn from_u8_truncating(value: u8) -> Self {
399        Self(value & Self::MAX_VALUE)
400    }
401
402    pub const fn into_u8(self) -> u8 {
403        self.0
404    }
405
406    pub fn next(self) -> Self {
407        Self((self.0 + 1) & Self::MAX.0)
408    }
409}
410
411impl Default for TransferId {
412    fn default() -> Self {
413        Self::SESSION_START
414    }
415}
416
417impl From<TransferId> for u8 {
418    fn from(value: TransferId) -> Self {
419        value.into_u8()
420    }
421}
422
423impl TryFrom<u8> for TransferId {
424    type Error = InvalidValue;
425
426    fn try_from(value: u8) -> Result<Self, Self::Error> {
427        Self::new(value).ok_or(InvalidValue)
428    }
429}
430
431#[cfg(test)]
432mod tests {
433    use super::*;
434
435    #[test]
436    fn test_priority_set() {
437        let mut set = PrioritySet::NONE;
438        set.insert(Priority::Fast);
439        set.insert(Priority::Nominal);
440
441        assert_eq!(set.first(), Some(Priority::Fast));
442        assert_eq!(set.last(), Some(Priority::Nominal));
443    }
444
445    #[test]
446    fn test_priority_set_ge() {
447        let set = PrioritySet::new_ge(Priority::Optional);
448        assert!(!set.contains(Priority::Slow));
449        assert!(set.contains(Priority::Optional));
450
451        let set = PrioritySet::new_ge(Priority::Exceptional);
452        assert_eq!(set, PrioritySet::ALL);
453    }
454
455    #[test]
456    fn test_priority_set_le() {
457        let set = PrioritySet::new_le(Priority::Optional);
458        assert_eq!(set, PrioritySet::ALL);
459
460        let set = PrioritySet::new_le(Priority::Exceptional);
461        assert!(set.contains(Priority::Exceptional));
462        assert!(!set.contains(Priority::Immediate));
463    }
464
465    #[test]
466    fn test_priority_set_gt() {
467        let set = PrioritySet::new_gt(Priority::Optional);
468        assert_eq!(set, PrioritySet::NONE);
469
470        let set = PrioritySet::new_gt(Priority::Exceptional);
471        assert!(!set.contains(Priority::Exceptional));
472        assert!(set.contains(Priority::Immediate));
473    }
474
475    #[test]
476    fn test_priority_set_lt() {
477        let set = PrioritySet::new_lt(Priority::Optional);
478        assert!(set.contains(Priority::Slow));
479        assert!(!set.contains(Priority::Optional));
480
481        let set = PrioritySet::new_lt(Priority::Exceptional);
482        assert_eq!(set, PrioritySet::NONE);
483    }
484}