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