anachro_icd/
lib.rs

1//! # Anachro ICD
2//!
3//! This is the Interface Control Document (ICD) for the Anachro PC
4//! communication protocol.
5//!
6//! This library defines the types used on by clients and servers of
7//! the anachro protocol.
8//!
9//! This library is currently primarily focused on a Pub/Sub style
10//! protocol, but will add support for Object Store and Mailbox
11//! style communication in the future.
12#![no_std]
13
14use core::{hash::Hash, marker::PhantomData, str::FromStr};
15use heapless::{consts, ArrayLength, String};
16use serde::{
17    de::{Deserializer, Visitor},
18    ser::Serializer,
19    Deserialize, Serialize,
20};
21
22pub mod arbitrator;
23pub mod component;
24
25/// A type alias for the Maximum Pub/Sub Path
26pub type MaxPathLen = consts::U127;
27
28/// A type alias for the maximum device name
29pub type MaxNameLen = consts::U32;
30
31/// Publish/Subscribe Path - Short or Long
32#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
33pub enum PubSubPath<'a> {
34    /// A long form, UTF-8 Path
35    #[serde(borrow)]
36    Long(Path<'a>),
37
38    /// A short form, 'memoized' path
39    ///
40    /// Pre-registered with the server
41    Short(u16),
42}
43
44/// Device version - SemVer
45#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Copy)]
46pub struct Version {
47    /// Major Semver Version
48    pub major: u8,
49
50    /// Minor Semver Version
51    pub minor: u8,
52
53    /// Trivial Semver Version
54    pub trivial: u8,
55
56    /// Misc Version
57    ///
58    /// Could be build number, etc.
59    pub misc: u8,
60}
61
62/// A Pub/Sub Path as a Managed String
63pub type Path<'a> = ManagedString<'a, MaxPathLen>;
64
65/// A device name as a Managed String
66pub type Name<'a> = ManagedString<'a, MaxNameLen>;
67
68/// A borrowed or owned string
69///
70/// Basically like CoW, but with heapless::String
71#[derive(Debug, Eq, PartialEq, Clone)]
72pub enum ManagedString<'a, T>
73where
74    T: ArrayLength<u8>,
75{
76    Owned(String<T>),
77    Borrow(&'a str),
78}
79
80impl<'a, T> Serialize for ManagedString<'a, T>
81where
82    T: ArrayLength<u8>,
83{
84    /// We can serialize our managed string as a str
85    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
86    where
87        S: Serializer,
88    {
89        serializer.serialize_str(self.as_str())
90    }
91}
92
93impl<'a, 'de: 'a, T> Deserialize<'de> for ManagedString<'a, T>
94where
95    T: ArrayLength<u8> + Sized,
96{
97    /// We can deserialize as a borrowed str
98    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
99    where
100        D: Deserializer<'de>,
101    {
102        deserializer.deserialize_str(ManagedStringVisitor::new())
103    }
104}
105
106struct ManagedStringVisitor<'a, T>
107where
108    T: ArrayLength<u8> + Sized,
109{
110    _t: PhantomData<T>,
111    _lt: PhantomData<&'a ()>,
112}
113
114impl<'a, T> ManagedStringVisitor<'a, T>
115where
116    T: ArrayLength<u8> + Sized,
117{
118    fn new() -> ManagedStringVisitor<'a, T> {
119        Self {
120            _t: PhantomData,
121            _lt: PhantomData,
122        }
123    }
124}
125
126impl<'de: 'a, 'a, T> Visitor<'de> for ManagedStringVisitor<'a, T>
127where
128    T: ArrayLength<u8> + Sized,
129{
130    type Value = ManagedString<'a, T>;
131
132    fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
133        write!(formatter, "a borrowable str")
134    }
135
136    fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E>
137    where
138        E: serde::de::Error,
139    {
140        // Always borrow
141        Ok(ManagedString::Borrow(value))
142    }
143}
144
145impl<'a, T> ManagedString<'a, T>
146where
147    T: ArrayLength<u8>,
148{
149    /// Obtain self as a string slice
150    pub fn as_str(&self) -> &str {
151        match self {
152            ManagedString::Owned(o) => o.as_str(),
153            ManagedString::Borrow(s) => s,
154        }
155    }
156
157    /// Retrieve a borrowed copy of the string
158    pub fn as_borrowed(&'a self) -> ManagedString<'a, T> {
159        ManagedString::Borrow(self.as_str())
160    }
161
162    /// Attempt to create an Owned string from a given str
163    ///
164    /// May fail if the heapless::String is not large enough to
165    /// contain this slice
166    pub fn try_from_str(input: &str) -> Result<ManagedString<'static, T>, ()> {
167        Ok(ManagedString::Owned(String::from_str(input)?))
168    }
169
170    /// Create a Borrowed string from a given str
171    pub fn borrow_from_str(input: &str) -> ManagedString<T> {
172        ManagedString::Borrow(input)
173    }
174
175    /// Attempt to convert to an Owned string from the current state
176    ///
177    /// May fail if the heapless::String is not large enough to
178    /// contain this slice
179    pub fn try_to_owned(&self) -> Result<ManagedString<'static, T>, ()> {
180        ManagedString::try_from_str(self.as_str())
181    }
182}
183
184/// A UUID as a block of 16 bytes
185#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Copy, Hash)]
186pub struct Uuid([u8; 16]);
187
188impl Uuid {
189    /// Create a new UUID from an array of bytes
190    pub fn from_bytes(by: [u8; 16]) -> Self {
191        Uuid(by)
192    }
193
194    /// Obtain the UUID contents as a borrowed array of bytes
195    pub fn as_bytes(&self) -> &[u8; 16] {
196        &self.0
197    }
198
199    /// Obtain the UUID contents as a slice of bytes
200    pub fn as_slice(&self) -> &[u8] {
201        &self.0[..]
202    }
203}
204
205/// A function for matching pub/sub paths
206///
207/// ## Examples
208///
209/// ```
210/// # use anachro_icd::matches;
211/// #
212/// assert!(matches(
213///  "/+/temperature/#",
214///  "/dev_1/temperature/front",
215/// ));
216/// ```
217pub fn matches(subscr: &str, publ: &str) -> bool {
218    if subscr.is_empty() || publ.is_empty() {
219        return false;
220    }
221
222    let mut s_iter = subscr.split('/');
223    let mut p_iter = publ.split('/');
224
225    loop {
226        match (s_iter.next(), p_iter.next()) {
227            (Some("+"), Some(_)) => continue,
228            (Some("#"), _) | (None, None) => return true,
229            (Some(lhs), Some(rhs)) if lhs == rhs => continue,
230            _ => return false,
231        }
232    }
233}