hopr_transport_probe/
content.rs1use anyhow::Context;
2use hopr_transport_packet::prelude::{ApplicationData, ReservedTag, Tag};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
6pub enum NeighborProbe {
7 Ping([u8; Self::SIZE]),
9 Pong([u8; Self::SIZE]),
11}
12
13impl NeighborProbe {
14 pub const SIZE: usize = 32;
15
16 pub fn random_nonce() -> Self {
18 const SIZE: usize = NeighborProbe::SIZE;
19 Self::Ping(hopr_crypto_random::random_bytes::<SIZE>())
20 }
21
22 pub fn is_complement_to(&self, other: Self) -> bool {
23 match (self, &other) {
24 (Self::Ping(nonce1), Self::Pong(nonce2)) => nonce1 == nonce2,
25 (Self::Pong(nonce1), Self::Ping(nonce2)) => nonce1 == nonce2,
26 _ => false,
27 }
28 }
29}
30
31impl std::fmt::Display for NeighborProbe {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 match self {
34 NeighborProbe::Ping(nonce) => write!(f, "Ping({})", hex::encode(nonce)),
35 NeighborProbe::Pong(nonce) => write!(f, "Pong({})", hex::encode(nonce)),
36 }
37 }
38}
39
40impl From<NeighborProbe> for [u8; NeighborProbe::SIZE] {
41 fn from(probe: NeighborProbe) -> Self {
42 match probe {
43 NeighborProbe::Ping(nonce) | NeighborProbe::Pong(nonce) => nonce,
44 }
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
50pub struct PathTelemetry {
51 id: [u8; 10],
52 path: [u8; 10],
53 timestamp: u128,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
57pub enum Message {
58 Telemetry(PathTelemetry),
59 Probe(NeighborProbe),
60}
61
62impl std::fmt::Display for Message {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 match self {
65 Message::Telemetry(telemetry) => write!(f, "Telemetry({:?})", telemetry),
66 Message::Probe(probe) => write!(f, "Probe({})", probe),
67 }
68 }
69}
70
71impl TryFrom<Message> for ApplicationData {
72 type Error = anyhow::Error;
73
74 fn try_from(message: Message) -> Result<Self, Self::Error> {
75 let tag: Tag = ReservedTag::Ping.into();
76 Ok(ApplicationData {
77 application_tag: tag,
78 plain_text: bitcode::serialize(&message)
79 .context("Failed to serialize message")?
80 .into_boxed_slice(),
81 })
82 }
83}
84
85impl TryFrom<ApplicationData> for Message {
86 type Error = anyhow::Error;
87
88 fn try_from(value: ApplicationData) -> Result<Self, Self::Error> {
89 let reserved_probe_tag: Tag = ReservedTag::Ping.into();
90
91 if value.application_tag == reserved_probe_tag {
92 let message: Message = bitcode::deserialize(&value.plain_text).context("Failed to deserialize message")?;
93 Ok(message)
94 } else {
95 Err(anyhow::anyhow!("Non probing application tag found"))
96 }
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use hopr_platform::time::native::current_time;
103 use hopr_primitive_types::traits::AsUnixTimestamp;
104 use more_asserts::assert_lt;
105
106 use super::*;
107
108 #[test]
109 fn random_generation_of_a_neighbor_probe_produces_a_ping() {
110 let ping = NeighborProbe::random_nonce();
111 assert!(matches!(ping, NeighborProbe::Ping(_)));
112 }
113
114 #[test]
115 fn check_for_complement_works_for_ping_and_pong_with_the_same_nonce() {
116 let ping = NeighborProbe::random_nonce();
117 let pong = { NeighborProbe::Pong(ping.into()) };
118
119 assert!(ping.is_complement_to(pong));
120 }
121
122 #[test]
123 fn check_for_complement_fails_for_ping_and_pong_with_different_nonce() {
124 let ping = NeighborProbe::random_nonce();
125 let pong = {
126 let mut other: [u8; NeighborProbe::SIZE] = ping.into();
127 other[0] = other[0].wrapping_add(1); NeighborProbe::Pong(other)
129 };
130
131 assert!(!ping.is_complement_to(pong));
132 }
133
134 #[test]
135 fn check_that_at_least_one_surb_can_fit_into_the_payload_for_direct_probing() -> anyhow::Result<()> {
136 let ping = NeighborProbe::random_nonce();
137 let as_data: ApplicationData = Message::Probe(ping).try_into()?;
138
139 assert_lt!(
140 as_data.plain_text.len(),
141 ApplicationData::PAYLOAD_SIZE - hopr_db_api::protocol::HoprSurb::SIZE
142 );
143
144 Ok(())
145 }
146
147 #[test]
148 fn check_that_at_least_one_surb_can_fit_into_the_payload_for_path_telemetry() -> anyhow::Result<()> {
149 let telemetry = PathTelemetry {
150 id: [1; 10],
151 path: [1; 10],
152 timestamp: current_time().as_unix_timestamp().as_millis(),
153 };
154 let as_data: ApplicationData = Message::Telemetry(telemetry).try_into()?;
155
156 assert_lt!(
157 as_data.plain_text.len(),
158 ApplicationData::PAYLOAD_SIZE - hopr_db_api::protocol::HoprSurb::SIZE
159 );
160
161 Ok(())
162 }
163}