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