zenoh_protocol/core/
mod.rs

1//
2// Copyright (c) 2023 ZettaScale Technology
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7// which is available at https://www.apache.org/licenses/LICENSE-2.0.
8//
9// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10//
11// Contributors:
12//   ZettaScale Zenoh Team, <zenoh@zettascale.tech>
13//
14
15use alloc::{
16    boxed::Box,
17    format,
18    string::{String, ToString},
19};
20use core::{
21    convert::{From, TryFrom, TryInto},
22    fmt::{self, Display},
23    hash::Hash,
24    ops::{Deref, RangeInclusive},
25    str::FromStr,
26};
27
28use serde::{Deserialize, Serialize};
29pub use uhlc::{Timestamp, NTP64};
30use zenoh_keyexpr::OwnedKeyExpr;
31use zenoh_result::{bail, zerror};
32
33/// The unique Id of the [`HLC`](uhlc::HLC) that generated the concerned [`Timestamp`].
34pub type TimestampId = uhlc::ID;
35
36/// Constants and helpers for zenoh `whatami` flags.
37pub mod whatami;
38pub use whatami::*;
39pub use zenoh_keyexpr::key_expr;
40
41pub mod wire_expr;
42pub use wire_expr::*;
43
44mod cowstr;
45pub use cowstr::CowStr;
46pub mod encoding;
47pub use encoding::{Encoding, EncodingId};
48
49pub mod locator;
50pub use locator::*;
51
52pub mod endpoint;
53pub use endpoint::*;
54
55pub mod resolution;
56pub use resolution::*;
57
58pub mod parameters;
59pub use parameters::Parameters;
60
61/// The global unique id of a zenoh peer.
62#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
63#[repr(transparent)]
64pub struct ZenohIdProto(uhlc::ID);
65
66impl ZenohIdProto {
67    pub const MAX_SIZE: usize = 16;
68
69    #[inline]
70    pub fn size(&self) -> usize {
71        self.0.size()
72    }
73
74    #[inline]
75    pub fn to_le_bytes(&self) -> [u8; uhlc::ID::MAX_SIZE] {
76        self.0.to_le_bytes()
77    }
78
79    #[doc(hidden)]
80    pub fn rand() -> ZenohIdProto {
81        ZenohIdProto(uhlc::ID::rand())
82    }
83
84    pub fn into_keyexpr(self) -> OwnedKeyExpr {
85        self.into()
86    }
87}
88
89impl Default for ZenohIdProto {
90    fn default() -> Self {
91        Self::rand()
92    }
93}
94
95// Mimics uhlc::SizeError,
96#[derive(Debug, Clone, Copy)]
97pub struct SizeError(usize);
98
99#[cfg(feature = "std")]
100impl std::error::Error for SizeError {}
101#[cfg(not(feature = "std"))]
102impl zenoh_result::IError for SizeError {}
103
104impl From<uhlc::SizeError> for SizeError {
105    fn from(val: uhlc::SizeError) -> Self {
106        Self(val.0)
107    }
108}
109
110// Taken from uhlc::SizeError
111impl fmt::Display for SizeError {
112    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
113        write!(
114            f,
115            "Maximum ID size ({} bytes) exceeded: {}",
116            uhlc::ID::MAX_SIZE,
117            self.0
118        )
119    }
120}
121
122macro_rules! derive_tryfrom {
123    ($T: ty) => {
124        impl TryFrom<$T> for ZenohIdProto {
125            type Error = zenoh_result::Error;
126            fn try_from(val: $T) -> Result<Self, Self::Error> {
127                match val.try_into() {
128                    Ok(ok) => Ok(Self(ok)),
129                    Err(err) => Err(Box::<SizeError>::new(err.into())),
130                }
131            }
132        }
133    };
134}
135derive_tryfrom!([u8; 1]);
136derive_tryfrom!(&[u8; 1]);
137derive_tryfrom!([u8; 2]);
138derive_tryfrom!(&[u8; 2]);
139derive_tryfrom!([u8; 3]);
140derive_tryfrom!(&[u8; 3]);
141derive_tryfrom!([u8; 4]);
142derive_tryfrom!(&[u8; 4]);
143derive_tryfrom!([u8; 5]);
144derive_tryfrom!(&[u8; 5]);
145derive_tryfrom!([u8; 6]);
146derive_tryfrom!(&[u8; 6]);
147derive_tryfrom!([u8; 7]);
148derive_tryfrom!(&[u8; 7]);
149derive_tryfrom!([u8; 8]);
150derive_tryfrom!(&[u8; 8]);
151derive_tryfrom!([u8; 9]);
152derive_tryfrom!(&[u8; 9]);
153derive_tryfrom!([u8; 10]);
154derive_tryfrom!(&[u8; 10]);
155derive_tryfrom!([u8; 11]);
156derive_tryfrom!(&[u8; 11]);
157derive_tryfrom!([u8; 12]);
158derive_tryfrom!(&[u8; 12]);
159derive_tryfrom!([u8; 13]);
160derive_tryfrom!(&[u8; 13]);
161derive_tryfrom!([u8; 14]);
162derive_tryfrom!(&[u8; 14]);
163derive_tryfrom!([u8; 15]);
164derive_tryfrom!(&[u8; 15]);
165derive_tryfrom!([u8; 16]);
166derive_tryfrom!(&[u8; 16]);
167derive_tryfrom!(&[u8]);
168
169impl FromStr for ZenohIdProto {
170    type Err = zenoh_result::Error;
171
172    fn from_str(s: &str) -> Result<Self, Self::Err> {
173        if s.contains(|c: char| c.is_ascii_uppercase()) {
174            bail!(
175                "Invalid id: {} - uppercase hexadecimal is not accepted, use lowercase",
176                s
177            );
178        }
179        let u: uhlc::ID = s
180            .parse()
181            .map_err(|e: uhlc::ParseIDError| zerror!("Invalid id: {} - {}", s, e.cause))?;
182        Ok(ZenohIdProto(u))
183    }
184}
185
186impl fmt::Debug for ZenohIdProto {
187    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
188        write!(f, "{}", self.0)
189    }
190}
191
192impl fmt::Display for ZenohIdProto {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        fmt::Debug::fmt(self, f)
195    }
196}
197
198// A PeerID can be converted into a Timestamp's ID
199impl From<&ZenohIdProto> for uhlc::ID {
200    fn from(zid: &ZenohIdProto) -> Self {
201        zid.0
202    }
203}
204
205impl From<ZenohIdProto> for uhlc::ID {
206    fn from(zid: ZenohIdProto) -> Self {
207        zid.0
208    }
209}
210
211impl From<ZenohIdProto> for OwnedKeyExpr {
212    fn from(zid: ZenohIdProto) -> Self {
213        // SAFETY: zid.to_string() returns an stringified hexadecimal
214        // representation of the zid. Therefore, building a OwnedKeyExpr
215        // by calling from_string_unchecked() is safe because it is
216        // guaranteed that no wildcards nor reserved chars will be present.
217        unsafe { OwnedKeyExpr::from_string_unchecked(zid.to_string()) }
218    }
219}
220
221impl From<&ZenohIdProto> for OwnedKeyExpr {
222    fn from(zid: &ZenohIdProto) -> Self {
223        (*zid).into()
224    }
225}
226
227impl serde::Serialize for ZenohIdProto {
228    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
229    where
230        S: serde::Serializer,
231    {
232        serializer.serialize_str(self.to_string().as_str())
233    }
234}
235
236impl<'de> serde::Deserialize<'de> for ZenohIdProto {
237    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
238    where
239        D: serde::Deserializer<'de>,
240    {
241        struct ZenohIdVisitor;
242
243        impl<'de> serde::de::Visitor<'de> for ZenohIdVisitor {
244            type Value = ZenohIdProto;
245
246            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
247                formatter.write_str(&format!(
248                    "An hex string of 1-{} bytes",
249                    ZenohIdProto::MAX_SIZE
250                ))
251            }
252
253            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
254            where
255                E: serde::de::Error,
256            {
257                v.parse().map_err(serde::de::Error::custom)
258            }
259
260            fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
261            where
262                E: serde::de::Error,
263            {
264                self.visit_str(v)
265            }
266
267            fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
268            where
269                E: serde::de::Error,
270            {
271                self.visit_str(&v)
272            }
273        }
274
275        deserializer.deserialize_str(ZenohIdVisitor)
276    }
277}
278
279/// The unique id of a zenoh entity inside it's parent `Session`.
280pub type EntityId = u32;
281
282/// The global unique id of a zenoh entity.
283#[derive(Debug, Default, Copy, Clone, Eq, Hash, PartialEq)]
284pub struct EntityGlobalIdProto {
285    pub zid: ZenohIdProto,
286    pub eid: EntityId,
287}
288
289impl EntityGlobalIdProto {
290    #[cfg(feature = "test")]
291    #[doc(hidden)]
292    pub fn rand() -> Self {
293        use rand::Rng;
294        Self {
295            zid: ZenohIdProto::rand(),
296            eid: rand::thread_rng().gen(),
297        }
298    }
299}
300
301#[repr(u8)]
302#[derive(Debug, Default, Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize)]
303pub enum Priority {
304    Control = 0,
305    RealTime = 1,
306    InteractiveHigh = 2,
307    InteractiveLow = 3,
308    DataHigh = 4,
309    #[default]
310    Data = 5,
311    DataLow = 6,
312    Background = 7,
313}
314
315impl Display for Priority {
316    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317        f.write_str(match self {
318            Priority::Control => "control",
319            Priority::RealTime => "real-time",
320            Priority::InteractiveHigh => "interactive-high",
321            Priority::InteractiveLow => "interactive-low",
322            Priority::Data => "data",
323            Priority::DataHigh => "data-high",
324            Priority::DataLow => "data-low",
325            Priority::Background => "background",
326        })
327    }
328}
329
330#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize)]
331/// A [`Priority`] range bounded inclusively below and above.
332pub struct PriorityRange(RangeInclusive<Priority>);
333
334impl Deref for PriorityRange {
335    type Target = RangeInclusive<Priority>;
336
337    fn deref(&self) -> &Self::Target {
338        &self.0
339    }
340}
341
342impl PriorityRange {
343    pub fn new(range: RangeInclusive<Priority>) -> Self {
344        Self(range)
345    }
346
347    /// Returns `true` if `self` is a superset of `other`.
348    pub fn includes(&self, other: &PriorityRange) -> bool {
349        self.start() <= other.start() && other.end() <= self.end()
350    }
351
352    pub fn len(&self) -> usize {
353        *self.end() as usize - *self.start() as usize + 1
354    }
355
356    pub fn is_empty(&self) -> bool {
357        false
358    }
359
360    #[cfg(feature = "test")]
361    #[doc(hidden)]
362    pub fn rand() -> Self {
363        use rand::Rng;
364        let mut rng = rand::thread_rng();
365        let start = rng.gen_range(Priority::MAX as u8..Priority::MIN as u8);
366        let end = rng.gen_range((start + 1)..=Priority::MIN as u8);
367
368        Self(Priority::try_from(start).unwrap()..=Priority::try_from(end).unwrap())
369    }
370}
371
372impl Display for PriorityRange {
373    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
374        write!(f, "{}-{}", *self.start() as u8, *self.end() as u8)
375    }
376}
377
378#[derive(Debug, PartialEq)]
379pub enum InvalidPriorityRange {
380    InvalidSyntax { found: String },
381    InvalidBound { message: String },
382}
383
384impl Display for InvalidPriorityRange {
385    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386        match self {
387            InvalidPriorityRange::InvalidSyntax { found } => write!(f, "invalid priority range string, expected an range of the form `start-end` but found {found}"),
388            InvalidPriorityRange::InvalidBound { message } => write!(f, "invalid priority range bound: {message}"),
389        }
390    }
391}
392
393#[cfg(feature = "std")]
394impl std::error::Error for InvalidPriorityRange {}
395
396impl FromStr for PriorityRange {
397    type Err = InvalidPriorityRange;
398
399    fn from_str(s: &str) -> Result<Self, Self::Err> {
400        const SEPARATOR: &str = "-";
401        let mut metadata = s.split(SEPARATOR);
402
403        let start = metadata
404            .next()
405            .ok_or_else(|| InvalidPriorityRange::InvalidSyntax {
406                found: s.to_string(),
407            })?
408            .parse::<u8>()
409            .map(Priority::try_from)
410            .map_err(|err| InvalidPriorityRange::InvalidBound {
411                message: err.to_string(),
412            })?
413            .map_err(|err| InvalidPriorityRange::InvalidBound {
414                message: err.to_string(),
415            })?;
416
417        match metadata.next() {
418            Some(slice) => {
419                let end = slice
420                    .parse::<u8>()
421                    .map(Priority::try_from)
422                    .map_err(|err| InvalidPriorityRange::InvalidBound {
423                        message: err.to_string(),
424                    })?
425                    .map_err(|err| InvalidPriorityRange::InvalidBound {
426                        message: err.to_string(),
427                    })?;
428
429                if metadata.next().is_some() {
430                    return Err(InvalidPriorityRange::InvalidSyntax {
431                        found: s.to_string(),
432                    });
433                };
434
435                Ok(PriorityRange::new(start..=end))
436            }
437            None => Ok(PriorityRange::new(start..=start)),
438        }
439    }
440}
441
442impl Priority {
443    /// Default
444    pub const DEFAULT: Self = Self::Data;
445    /// The lowest Priority
446    pub const MIN: Self = Self::Background;
447    /// The highest Priority
448    pub const MAX: Self = Self::Control;
449    /// The number of available priorities
450    pub const NUM: usize = 1 + Self::MIN as usize - Self::MAX as usize;
451}
452
453impl TryFrom<u8> for Priority {
454    type Error = zenoh_result::Error;
455
456    fn try_from(v: u8) -> Result<Self, Self::Error> {
457        match v {
458            0 => Ok(Priority::Control),
459            1 => Ok(Priority::RealTime),
460            2 => Ok(Priority::InteractiveHigh),
461            3 => Ok(Priority::InteractiveLow),
462            4 => Ok(Priority::DataHigh),
463            5 => Ok(Priority::Data),
464            6 => Ok(Priority::DataLow),
465            7 => Ok(Priority::Background),
466            unknown => bail!(
467                "{} is not a valid priority value. Admitted values are: [{}-{}].",
468                unknown,
469                Self::MAX as u8,
470                Self::MIN as u8
471            ),
472        }
473    }
474}
475
476/// Reliability guarantees for message delivery.
477#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
478#[repr(u8)]
479pub enum Reliability {
480    /// Messages may be lost.
481    BestEffort = 0,
482    /// Messages are guaranteed to be delivered.
483    #[default]
484    Reliable = 1,
485}
486
487impl Reliability {
488    pub const DEFAULT: Self = Self::Reliable;
489
490    #[cfg(feature = "test")]
491    #[doc(hidden)]
492    pub fn rand() -> Self {
493        use rand::Rng;
494
495        let mut rng = rand::thread_rng();
496
497        if rng.gen_bool(0.5) {
498            Reliability::Reliable
499        } else {
500            Reliability::BestEffort
501        }
502    }
503}
504
505impl From<bool> for Reliability {
506    fn from(value: bool) -> Self {
507        if value {
508            Reliability::Reliable
509        } else {
510            Reliability::BestEffort
511        }
512    }
513}
514
515impl From<Reliability> for bool {
516    fn from(value: Reliability) -> Self {
517        match value {
518            Reliability::BestEffort => false,
519            Reliability::Reliable => true,
520        }
521    }
522}
523
524impl Display for Reliability {
525    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
526        write!(f, "{}", *self as u8)
527    }
528}
529
530#[derive(Debug)]
531pub struct InvalidReliability {
532    found: String,
533}
534
535impl Display for InvalidReliability {
536    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
537        write!(
538            f,
539            "invalid Reliability string, expected `{}` or `{}` but found {}",
540            Reliability::Reliable as u8,
541            Reliability::BestEffort as u8,
542            self.found
543        )
544    }
545}
546
547#[cfg(feature = "std")]
548impl std::error::Error for InvalidReliability {}
549
550impl FromStr for Reliability {
551    type Err = InvalidReliability;
552
553    fn from_str(s: &str) -> Result<Self, Self::Err> {
554        let Ok(desc) = s.parse::<u8>() else {
555            return Err(InvalidReliability {
556                found: s.to_string(),
557            });
558        };
559
560        if desc == Reliability::BestEffort as u8 {
561            Ok(Reliability::BestEffort)
562        } else if desc == Reliability::Reliable as u8 {
563            Ok(Reliability::Reliable)
564        } else {
565            Err(InvalidReliability {
566                found: s.to_string(),
567            })
568        }
569    }
570}
571
572#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
573pub struct Channel {
574    pub priority: Priority,
575    pub reliability: Reliability,
576}
577
578impl Channel {
579    pub const DEFAULT: Self = Self {
580        priority: Priority::DEFAULT,
581        reliability: Reliability::DEFAULT,
582    };
583}
584
585/// Congestion control strategy.
586#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash, Deserialize)]
587#[repr(u8)]
588pub enum CongestionControl {
589    #[default]
590    /// When transmitting a message in a node with a full queue, the node may drop the message.
591    Drop = 0,
592    /// When transmitting a message in a node with a full queue, the node will wait for queue to
593    /// progress.
594    Block = 1,
595    #[cfg(feature = "unstable")]
596    /// When transmitting a message in a node with a full queue, the node will wait for queue to
597    /// progress, but only for the first message sent with this strategy; other messages will be
598    /// dropped.
599    BlockFirst = 2,
600}
601
602impl CongestionControl {
603    pub const DEFAULT: Self = Self::Drop;
604
605    #[cfg(feature = "internal")]
606    pub const DEFAULT_PUSH: Self = Self::Drop;
607    #[cfg(not(feature = "internal"))]
608    pub(crate) const DEFAULT_PUSH: Self = Self::Drop;
609
610    #[cfg(feature = "internal")]
611    pub const DEFAULT_REQUEST: Self = Self::Block;
612    #[cfg(not(feature = "internal"))]
613    pub(crate) const DEFAULT_REQUEST: Self = Self::Block;
614
615    #[cfg(feature = "internal")]
616    pub const DEFAULT_RESPONSE: Self = Self::Block;
617    #[cfg(not(feature = "internal"))]
618    pub(crate) const DEFAULT_RESPONSE: Self = Self::Block;
619
620    #[cfg(feature = "internal")]
621    pub const DEFAULT_DECLARE: Self = Self::Block;
622    #[cfg(not(feature = "internal"))]
623    pub(crate) const DEFAULT_DECLARE: Self = Self::Block;
624
625    #[cfg(feature = "internal")]
626    pub const DEFAULT_OAM: Self = Self::Block;
627    #[cfg(not(feature = "internal"))]
628    pub(crate) const DEFAULT_OAM: Self = Self::Block;
629}
630
631#[cfg(test)]
632mod tests {
633    use core::str::FromStr;
634
635    use crate::core::{Priority, PriorityRange};
636
637    #[test]
638    fn test_priority_range() {
639        assert_eq!(
640            PriorityRange::from_str("2-3"),
641            Ok(PriorityRange::new(
642                Priority::InteractiveHigh..=Priority::InteractiveLow
643            ))
644        );
645
646        assert_eq!(
647            PriorityRange::from_str("7"),
648            Ok(PriorityRange::new(
649                Priority::Background..=Priority::Background
650            ))
651        );
652
653        assert!(PriorityRange::from_str("1-").is_err());
654        assert!(PriorityRange::from_str("-5").is_err());
655    }
656}