hopr_transport_probe/
types.rs

1use hopr_api::PeerId;
2use hopr_crypto_random::Randomizable;
3use hopr_internal_types::{NodeId, protocol::HoprPseudonym};
4use hopr_network_types::types::{DestinationRouting, RoutingOptions};
5use hopr_primitive_types::{bounded::BoundedVec, errors::GeneralError};
6
7pub struct TaggedDestinationRouting {
8    /// The destination node.
9    pub destination: Box<NodeId>,
10    /// Pseudonym shown to the destination.
11    pub pseudonym: HoprPseudonym,
12    /// The path to the destination.
13    pub forward_options: RoutingOptions,
14    /// Optional return path.
15    pub return_options: Option<RoutingOptions>,
16}
17
18impl TaggedDestinationRouting {
19    pub fn neighbor(destination: Box<NodeId>) -> Self {
20        Self {
21            destination,
22            pseudonym: HoprPseudonym::random(),
23            forward_options: RoutingOptions::Hops(0.try_into().expect("0 is a valid u8")),
24            return_options: Some(RoutingOptions::Hops(0.try_into().expect("0 is a valid u8"))),
25        }
26    }
27
28    pub fn loopback(me: Box<NodeId>, path: BoundedVec<NodeId, { RoutingOptions::MAX_INTERMEDIATE_HOPS }>) -> Self {
29        Self {
30            destination: me,
31            pseudonym: HoprPseudonym::random(),
32            forward_options: RoutingOptions::IntermediatePath(path),
33            return_options: None,
34        }
35    }
36}
37
38impl From<TaggedDestinationRouting> for DestinationRouting {
39    fn from(value: TaggedDestinationRouting) -> Self {
40        DestinationRouting::Forward {
41            destination: value.destination,
42            pseudonym: Some(value.pseudonym),
43            forward_options: value.forward_options,
44            return_options: value.return_options,
45        }
46    }
47}
48
49/// Serializable and deserializable enum for the probe message content.
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::EnumDiscriminants)]
51#[strum_discriminants(vis(pub(crate)), derive(strum::FromRepr, strum::EnumCount), repr(u8))]
52pub enum NeighborProbe {
53    /// Ping message with random nonce
54    Ping([u8; Self::NONCE_SIZE]),
55    /// Pong message replying to a specific nonce
56    Pong([u8; Self::NONCE_SIZE]),
57}
58
59impl NeighborProbe {
60    pub const NONCE_SIZE: usize = 32;
61    pub const SIZE: usize = 1 + Self::NONCE_SIZE;
62
63    /// Creates a new Ping probe with a random nonce
64    pub fn random_nonce() -> Self {
65        Self::Ping(hopr_crypto_random::random_bytes::<{ Self::NONCE_SIZE }>())
66    }
67
68    pub fn is_complement_to(&self, other: Self) -> bool {
69        match (self, &other) {
70            (Self::Ping(nonce1), Self::Pong(nonce2)) => nonce1 == nonce2,
71            (Self::Pong(nonce1), Self::Ping(nonce2)) => nonce1 == nonce2,
72            _ => false,
73        }
74    }
75
76    pub fn to_bytes(self) -> Box<[u8]> {
77        let mut out = Vec::with_capacity(1 + Self::NONCE_SIZE);
78        out.push(NeighborProbeDiscriminants::from(&self) as u8);
79        out.extend_from_slice(self.as_ref());
80        out.into_boxed_slice()
81    }
82}
83
84impl<'a> TryFrom<&'a [u8]> for NeighborProbe {
85    type Error = GeneralError;
86
87    fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
88        if value.len() == 1 + Self::NONCE_SIZE {
89            match NeighborProbeDiscriminants::from_repr(value[0])
90                .ok_or(GeneralError::ParseError("NeighborProbe.disc".into()))?
91            {
92                NeighborProbeDiscriminants::Ping => {
93                    Ok(Self::Ping((&value[1..]).try_into().map_err(|_| {
94                        GeneralError::ParseError("NeighborProbe.ping_nonce".into())
95                    })?))
96                }
97                NeighborProbeDiscriminants::Pong => {
98                    Ok(Self::Pong((&value[1..]).try_into().map_err(|_| {
99                        GeneralError::ParseError("NeighborProbe.pong_nonce".into())
100                    })?))
101                }
102            }
103        } else {
104            Err(GeneralError::ParseError("NeighborProbe.size".into()))
105        }
106    }
107}
108
109impl std::fmt::Display for NeighborProbe {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        match self {
112            NeighborProbe::Ping(nonce) => write!(f, "Ping({})", hex::encode(nonce)),
113            NeighborProbe::Pong(nonce) => write!(f, "Pong({})", hex::encode(nonce)),
114        }
115    }
116}
117
118impl AsRef<[u8]> for NeighborProbe {
119    fn as_ref(&self) -> &[u8] {
120        match self {
121            NeighborProbe::Ping(nonce) | NeighborProbe::Pong(nonce) => nonce,
122        }
123    }
124}
125
126/// Path telemetry data for multi-hop loopback probing.
127///
128/// Contains an identifier, path information, and timestamp for tracking
129/// telemetry through the network back to self.
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
131pub struct PathTelemetry {
132    /// Unique identifier for the telemetry data
133    pub id: [u8; Self::ID_SIZE],
134    /// Path information encoded as bytes
135    pub path: [u8; Self::PATH_SIZE],
136    /// Timestamp when the telemetry was created
137    pub timestamp: u128,
138}
139
140impl PathTelemetry {
141    pub const ID_SIZE: usize = 10;
142    pub const PATH_SIZE: usize = 10;
143    pub const SIZE: usize = Self::ID_SIZE + Self::PATH_SIZE + size_of::<u128>();
144
145    pub fn to_bytes(self) -> Box<[u8]> {
146        let mut out = Vec::with_capacity(Self::SIZE);
147        out.extend_from_slice(&self.id);
148        out.extend_from_slice(&self.path);
149        out.extend_from_slice(&self.timestamp.to_be_bytes());
150        out.into_boxed_slice()
151    }
152}
153
154impl hopr_api::ct::MeasurablePath for PathTelemetry {
155    fn id(&self) -> &[u8] {
156        &self.id
157    }
158
159    fn path(&self) -> &[u8] {
160        &self.path
161    }
162
163    fn timestamp(&self) -> u128 {
164        self.timestamp
165    }
166}
167
168const _: () = assert!(size_of::<u128>() > PathTelemetry::ID_SIZE);
169
170impl std::fmt::Display for PathTelemetry {
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        write!(
173            f,
174            "PathTelemetry(id: {}, path: {}, timestamp: {})",
175            hex::encode(self.id),
176            hex::encode(self.path),
177            self.timestamp
178        )
179    }
180}
181
182impl<'a> TryFrom<&'a [u8]> for PathTelemetry {
183    type Error = GeneralError;
184
185    fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
186        if value.len() == Self::SIZE {
187            Ok(Self {
188                id: (&value[0..10])
189                    .try_into()
190                    .map_err(|_| GeneralError::ParseError("PathTelemetry.id".into()))?,
191                path: (&value[10..20])
192                    .try_into()
193                    .map_err(|_| GeneralError::ParseError("PathTelemetry.path".into()))?,
194                timestamp: u128::from_be_bytes(
195                    (&value[20..36])
196                        .try_into()
197                        .map_err(|_| GeneralError::ParseError("PathTelemetry.timestamp".into()))?,
198                ),
199            })
200        } else {
201            Err(GeneralError::ParseError("PathTelemetry".into()))
202        }
203    }
204}
205
206/// Intermediate neighbor telemetry object.
207///
208/// Represents the finding of an intermediate peer probing operation.
209#[derive(Debug, Clone)]
210pub struct NeighborTelemetry {
211    pub peer: PeerId,
212    pub rtt: std::time::Duration,
213}
214
215impl hopr_api::ct::MeasurableNeighbor for NeighborTelemetry {
216    fn peer(&self) -> &PeerId {
217        &self.peer
218    }
219
220    fn rtt(&self) -> std::time::Duration {
221        self.rtt
222    }
223}