Skip to main content

hopr_protocol_app/
v1.rs

1use std::{borrow::Cow, fmt::Formatter, ops::Range, str::FromStr};
2
3use hopr_crypto_packet::prelude::{HoprPacket, PacketSignals};
4use hopr_types::primitive::to_hex_shortened;
5use strum::IntoEnumIterator;
6
7use crate::errors::ApplicationLayerError;
8
9/// List of all reserved application tags for the protocol.
10#[repr(u64)]
11#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, strum::EnumIter)]
12pub enum ReservedTag {
13    /// Ping traffic for 0-hop detection.
14    Ping = 0,
15
16    /// Commands associated with session start protocol regarding session initiation.
17    SessionStart = 1,
18
19    /// Reserved tag for the Session protocol.
20    Session = 2,
21
22    /// Undefined catch all.
23    Undefined = 15,
24}
25
26impl ReservedTag {
27    /// The exclusive upper bound of the reserved tag range.
28    ///
29    /// Must be kept in sync with the highest variant discriminant.
30    pub const UPPER_BOUND: u64 = Self::Undefined as u64 + 1;
31
32    /// The range of reserved tags
33    pub fn range() -> Range<u64> {
34        0..Self::UPPER_BOUND
35    }
36}
37
38impl From<ReservedTag> for Tag {
39    fn from(tag: ReservedTag) -> Self {
40        (tag as u64).into()
41    }
42}
43
44/// Tags distinguishing different application-layer protocols.
45///
46/// Currently, 8 bytes represent tags (`u64`).
47///
48/// `u64` should offer enough space to avoid collisions and tag attacks.
49#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
50pub enum Tag {
51    Reserved(u64),
52    Application(u64),
53}
54
55impl Tag {
56    /// Application tag range for external usage
57    pub const APPLICATION_TAG_RANGE: Range<Self> =
58        (Self::Application(ReservedTag::Undefined as u64 + 1))..Self::Application(Self::MAX);
59    /// The maximum value of a tag.
60    ///
61    /// The maximum value is determined by the fact that the 3 most significant bits
62    /// must be set to 0 in version 1.
63    pub const MAX: u64 = 0x1fffffffffffffff_u64;
64    /// Size of a tag in bytes.
65    pub const SIZE: usize = size_of::<u64>();
66
67    pub fn from_be_bytes(bytes: [u8; Self::SIZE]) -> Self {
68        let tag = u64::from_be_bytes(bytes);
69        tag.into()
70    }
71
72    pub fn to_be_bytes(&self) -> [u8; Self::SIZE] {
73        match self {
74            Tag::Reserved(tag) | Tag::Application(tag) => tag.to_be_bytes(),
75        }
76    }
77
78    pub fn as_u64(&self) -> u64 {
79        match self {
80            Tag::Reserved(tag) | Tag::Application(tag) => (*tag) & Self::MAX,
81        }
82    }
83}
84
85impl<T: Into<u64>> From<T> for Tag {
86    fn from(tag: T) -> Self {
87        // In version 1, the 3 most significant bits are always 0.
88        let tag: u64 = tag.into() & Self::MAX;
89
90        if ReservedTag::range().contains(&tag) {
91            Tag::Reserved(
92                ReservedTag::iter()
93                    .find(|&t| t as u64 == tag)
94                    .unwrap_or(ReservedTag::Undefined) as u64,
95            )
96        } else {
97            Tag::Application(tag)
98        }
99    }
100}
101
102#[cfg(feature = "serde")]
103impl serde::Serialize for Tag {
104    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
105    where
106        S: serde::Serializer,
107    {
108        serializer.serialize_u64(self.as_u64())
109    }
110}
111
112#[cfg(feature = "serde")]
113impl<'a> serde::Deserialize<'a> for Tag {
114    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
115    where
116        D: serde::Deserializer<'a>,
117    {
118        Ok(u64::deserialize(deserializer)?.into())
119    }
120}
121
122impl std::fmt::Display for Tag {
123    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
124        write!(f, "{}", self.as_u64())
125    }
126}
127
128impl FromStr for Tag {
129    type Err = std::num::ParseIntError;
130
131    fn from_str(s: &str) -> Result<Self, Self::Err> {
132        u64::from_str(s).map(Tag::from)
133    }
134}
135
136/// Holds packet transient information when [`ApplicationData`] is passed from the HOPR protocol layer to the
137/// Application layer.
138///
139/// The HOPR protocol layer typically takes care of properly populating this structure
140/// as the packet arrives.
141#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
142pub struct IncomingPacketInfo {
143    /// Packet signals that were passed by the sender.
144    pub signals_from_sender: PacketSignals,
145    /// The number of SURBs the HOPR packet was carrying along with the [`ApplicationData`] instance.
146    pub num_saved_surbs: usize,
147    /// How many already-held SURBs were evicted while inserting this packet's because this sender's
148    /// SURB buffer was full.
149    ///
150    /// The arriving SURBs are retained and the oldest queued ones are dropped. Exposed for
151    /// observability only; sessions deliberately do not feed it back into the SURB flow estimate.
152    pub num_evicted_surbs: usize,
153}
154
155/// Holds packet transient information when [`ApplicationData`] is passed to the HOPR protocol layer from the
156/// Application layer.
157///
158/// The information passed to the HOPR protocol only serves as a suggestion, and the HOPR protocol
159/// may choose to ignore it, based on its configuration.
160#[derive(Copy, Clone, Debug, PartialEq, Eq)]
161pub struct OutgoingPacketInfo {
162    /// Packet signals that should be passed to the recipient.
163    pub signals_to_destination: PacketSignals,
164    /// The maximum number of SURBs the HOPR packet should be carrying when sent.
165    pub max_surbs_in_packet: usize,
166}
167
168impl Default for OutgoingPacketInfo {
169    fn default() -> Self {
170        Self {
171            signals_to_destination: PacketSignals::empty(),
172            max_surbs_in_packet: usize::MAX,
173        }
174    }
175}
176
177/// Wrapper for incoming [`ApplicationData`] with optional [`IncomingPacketInfo`].
178#[derive(Clone, Debug, PartialEq, Eq)]
179#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
180pub struct ApplicationDataIn {
181    /// The actual application-layer data.
182    pub data: ApplicationData,
183    /// Additional transient information about the incoming packet.
184    ///
185    /// This information is always populated by the HOPR packet layer.
186    #[cfg_attr(feature = "serde", serde(skip))]
187    pub packet_info: IncomingPacketInfo,
188}
189
190impl ApplicationDataIn {
191    /// Returns how many SURBs were carried with this packet.
192    pub fn num_surbs_with_msg(&self) -> usize {
193        self.packet_info
194            .num_saved_surbs
195            .min(HoprPacket::max_surbs_with_message(self.data.total_len()))
196    }
197}
198
199/// Wrapper for outgoing [`ApplicationData`] with optional [`IncomingPacketInfo`].
200#[derive(Clone, Debug, PartialEq, Eq)]
201#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
202pub struct ApplicationDataOut {
203    /// The actual application-layer data.
204    pub data: ApplicationData,
205    /// Additional transient information about the outgoing packet.
206    ///
207    /// This field is optional and acts mainly as a suggestion to the HOPR packet layer,
208    /// as it may choose to completely ignore it, based on its configuration.
209    #[cfg_attr(feature = "serde", serde(skip))]
210    pub packet_info: Option<OutgoingPacketInfo>,
211}
212
213impl ApplicationDataOut {
214    /// Creates a new instance with `packet_info` set to `None`.
215    pub fn with_no_packet_info(data: ApplicationData) -> Self {
216        Self {
217            data,
218            packet_info: None,
219        }
220    }
221
222    /// Returns the upper bound of how many SURBs that will be carried with this packet.
223    pub fn estimate_surbs_with_msg(&self) -> usize {
224        let max_possible = HoprPacket::max_surbs_with_message(self.data.total_len());
225        self.packet_info
226            .map(|info| info.max_surbs_in_packet.min(max_possible))
227            .unwrap_or(max_possible)
228    }
229}
230
231/// Represents the to-be-sent or received decrypted packet carrying the application-layer data.
232///
233/// This type is already HOPR specific, as it enforces the maximum payload size to be at most
234/// [`HoprPacket::PAYLOAD_SIZE`] bytes-long. This structure always owns the data.
235#[derive(Clone, PartialEq, Eq)]
236#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
237pub struct ApplicationData {
238    /// Tag identifying the application-layer protocol.
239    pub application_tag: Tag,
240    /// The actual application-layer data.
241    #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
242    pub plain_text: Box<[u8]>,
243}
244
245impl ApplicationData {
246    /// The payload size is the [`HoprPacket::PAYLOAD_SIZE`] minus the [`Tag::SIZE`].
247    pub const PAYLOAD_SIZE: usize = HoprPacket::PAYLOAD_SIZE - Tag::SIZE;
248
249    /// Creates a new instance with the given tag and application layer data.
250    ///
251    /// Fails if the `plain_text` is larger than [`ApplicationData::PAYLOAD_SIZE`].
252    pub fn new<'a, T: Into<Tag>, D: Into<Cow<'a, [u8]>>>(
253        application_tag: T,
254        plain_text: D,
255    ) -> crate::errors::Result<Self> {
256        let data = plain_text.into();
257        if data.len() <= Self::PAYLOAD_SIZE {
258            Ok(Self {
259                application_tag: application_tag.into(),
260                plain_text: data.into(),
261            })
262        } else {
263            Err(ApplicationLayerError::PayloadTooLarge)
264        }
265    }
266
267    /// Length of the payload plus the [`Tag`].
268    ///
269    /// Can never be zero due to the `Tag`.
270    #[inline]
271    pub fn total_len(&self) -> usize {
272        Tag::SIZE + self.plain_text.len()
273    }
274
275    /// Indicates if the payload is empty.
276    #[inline]
277    pub fn is_payload_empty(&self) -> bool {
278        self.plain_text.is_empty()
279    }
280
281    /// Serializes the structure into binary representation.
282    pub fn to_bytes(&self) -> Box<[u8]> {
283        let mut buf = Vec::with_capacity(Tag::SIZE + self.plain_text.len());
284        buf.extend_from_slice(&self.application_tag.to_be_bytes());
285        buf.extend_from_slice(&self.plain_text);
286        buf.into_boxed_slice()
287    }
288}
289
290impl std::fmt::Debug for ApplicationData {
291    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
292        f.debug_struct("ApplicationData")
293            .field("application_tag", &self.application_tag)
294            .field("plain_text", &to_hex_shortened::<32>(&self.plain_text))
295            .finish()
296    }
297}
298
299impl std::fmt::Display for ApplicationData {
300    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
301        write!(
302            f,
303            "({}): {}",
304            self.application_tag,
305            to_hex_shortened::<16>(&self.plain_text)
306        )
307    }
308}
309
310impl TryFrom<&[u8]> for ApplicationData {
311    type Error = ApplicationLayerError;
312
313    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
314        if value.len() >= Tag::SIZE && value.len() <= HoprPacket::PAYLOAD_SIZE {
315            Ok(Self {
316                application_tag: Tag::from_be_bytes(
317                    value[0..Tag::SIZE]
318                        .try_into()
319                        .map_err(|_e| ApplicationLayerError::DecodingError("ApplicationData.tag".into()))?,
320                ),
321                plain_text: Box::from(&value[Tag::SIZE..]),
322            })
323        } else {
324            Err(ApplicationLayerError::DecodingError("ApplicationData.size".into()))
325        }
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    use super::*;
332
333    #[test]
334    fn reserved_tag_v1_range_is_stable() {
335        let range = ReservedTag::range();
336        assert_eq!(range.start, 0);
337        assert_eq!(range.count(), 16); // 0 to 15 inclusive
338    }
339
340    #[test]
341    fn tag_should_be_obtainable_as_reserved_when_created_from_a_reserved_range() {
342        let reserved_tag = ReservedTag::Ping as u64;
343
344        assert_eq!(Tag::from(reserved_tag), Tag::Reserved(reserved_tag));
345    }
346
347    #[test]
348    fn v1_tags_should_have_3_most_significant_bits_unset() {
349        let tag: Tag = u64::MAX.into();
350        assert_eq!(tag.as_u64(), Tag::MAX);
351    }
352
353    #[test]
354    fn tag_should_be_obtainable_as_undefined_reserved_when_created_from_an_undefined_value_in_reserved_range() {
355        let reserved_tag_without_assignment = 7u64;
356
357        assert_eq!(
358            Tag::from(reserved_tag_without_assignment),
359            Tag::Reserved(ReservedTag::Undefined as u64)
360        );
361    }
362
363    #[test]
364    fn v1_format_is_binary_stable() -> anyhow::Result<()> {
365        let original = ApplicationData::new(10u64, &[0_u8, 1_u8])?;
366        let reserialized = ApplicationData::try_from(original.to_bytes().as_ref())?;
367        let reserialized = ApplicationData::try_from(reserialized.to_bytes().as_ref())?;
368
369        assert_eq!(original, reserialized);
370
371        Ok(())
372    }
373
374    #[test]
375    fn test_application_data() -> anyhow::Result<()> {
376        let ad_1 = ApplicationData::new(10u64, &[0_u8, 1_u8])?;
377        let ad_2 = ApplicationData::try_from(ad_1.to_bytes().as_ref())?;
378        assert_eq!(ad_1, ad_2);
379
380        let ad_1 = ApplicationData::new(0u64, &[])?;
381        let ad_2 = ApplicationData::try_from(ad_1.to_bytes().as_ref())?;
382        assert_eq!(ad_1, ad_2);
383
384        let ad_1 = ApplicationData::new(10u64, &[0_u8, 1_u8])?;
385        let ad_2 = ApplicationData::try_from(ad_1.to_bytes().as_ref())?;
386        assert_eq!(ad_1, ad_2);
387
388        let ad_1 = ApplicationData::new(10u64, &[0_u8; ApplicationData::PAYLOAD_SIZE])?;
389        let ad_2 = ApplicationData::try_from(ad_1.to_bytes().as_ref())?;
390        assert_eq!(ad_1, ad_2);
391
392        assert!(ApplicationData::try_from([0_u8; Tag::SIZE - 1].as_ref()).is_err());
393        assert!(ApplicationData::try_from([0_u8; ApplicationData::PAYLOAD_SIZE + Tag::SIZE + 1].as_ref()).is_err());
394
395        Ok(())
396    }
397
398    #[test]
399    fn application_data_should_not_allow_payload_larger_than_hopr_packet_payload_size() {
400        assert!(ApplicationData::new(10u64, [0_u8; HoprPacket::PAYLOAD_SIZE + 1].as_ref()).is_err());
401    }
402}