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