hopr_transport_packet/
v1.rs

1use std::{fmt::Formatter, ops::Range};
2
3use strum::IntoEnumIterator;
4
5/// List of all reserved application tags for the protocol.
6#[repr(u64)]
7#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, strum::EnumIter)]
8pub enum ReservedTag {
9    /// Ping traffic for 0-hop detection.
10    Ping = 0,
11
12    /// Commands associated with session start protocol regarding session initiation.
13    SessionStart = 1,
14
15    /// Undefined catch all.
16    Undefined = 15,
17}
18
19impl ReservedTag {
20    /// The range of reserved tags
21    pub fn range() -> Range<u64> {
22        0..(Self::iter().max().unwrap_or(Self::Undefined) as u64 + 1)
23    }
24}
25
26impl From<ReservedTag> for Tag {
27    fn from(tag: ReservedTag) -> Self {
28        (tag as u64).into()
29    }
30}
31
32/// Tags are represented by 8 bytes ([`u64`]`).
33///
34/// [`u64`] should offer enough space to avoid collisions and tag attacks.
35// #[repr(u64)]
36#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
37pub enum Tag {
38    Reserved(u64),
39    Application(u64),
40}
41
42impl Tag {
43    /// Application tag range for external usage
44    pub const APPLICATION_TAG_RANGE: Range<Self> =
45        (Self::Application(ReservedTag::Undefined as u64 + 1))..Self::Application(Self::MAX);
46    pub const MAX: u64 = u64::MAX;
47    pub const SIZE: usize = size_of::<u64>();
48
49    pub fn from_be_bytes(bytes: [u8; Self::SIZE]) -> Self {
50        let tag = u64::from_be_bytes(bytes);
51        tag.into()
52    }
53
54    pub fn to_be_bytes(&self) -> [u8; Self::SIZE] {
55        match self {
56            Tag::Reserved(tag) | Tag::Application(tag) => tag.to_be_bytes(),
57        }
58    }
59
60    pub fn as_u64(&self) -> u64 {
61        match self {
62            Tag::Reserved(tag) | Tag::Application(tag) => *tag,
63        }
64    }
65}
66
67impl<T: Into<u64>> From<T> for Tag {
68    fn from(tag: T) -> Self {
69        let tag: u64 = tag.into();
70
71        if ReservedTag::range().contains(&tag) {
72            Tag::Reserved(
73                ReservedTag::iter()
74                    .find(|&t| t as u64 == tag)
75                    .unwrap_or(ReservedTag::Undefined) as u64,
76            )
77        } else {
78            Tag::Application(tag)
79        }
80    }
81}
82
83#[cfg(feature = "serde")]
84impl serde::Serialize for Tag {
85    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
86    where
87        S: serde::Serializer,
88    {
89        match self {
90            Tag::Reserved(tag) | Tag::Application(tag) => serializer.serialize_u64(*tag),
91        }
92    }
93}
94
95#[cfg(feature = "serde")]
96impl<'a> serde::Deserialize<'a> for Tag {
97    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
98    where
99        D: serde::Deserializer<'a>,
100    {
101        let value = u64::deserialize(deserializer)?;
102
103        Ok(value.into())
104    }
105}
106
107impl std::fmt::Display for Tag {
108    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
109        write!(f, "Tag({})", self.as_u64())
110    }
111}
112
113/// Represents the received decrypted packet carrying the application-layer data.
114#[derive(Clone, PartialEq, Eq)]
115#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
116pub struct ApplicationData {
117    pub application_tag: Tag,
118    #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
119    pub plain_text: Box<[u8]>,
120}
121
122impl ApplicationData {
123    pub const PAYLOAD_SIZE: usize = hopr_crypto_packet::prelude::HoprPacket::PAYLOAD_SIZE - Tag::SIZE;
124
125    pub fn new<T: Into<Tag>>(application_tag: T, plain_text: &[u8]) -> Self {
126        Self {
127            application_tag: application_tag.into(),
128            plain_text: plain_text.into(),
129        }
130    }
131
132    pub fn new_from_owned<T: Into<Tag>>(tag: T, plain_text: Box<[u8]>) -> Self {
133        Self {
134            application_tag: tag.into(),
135            plain_text,
136        }
137    }
138
139    #[allow(clippy::len_without_is_empty)]
140    pub fn len(&self) -> usize {
141        Self::TAG_SIZE + self.plain_text.len()
142    }
143}
144
145impl std::fmt::Debug for ApplicationData {
146    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
147        f.debug_struct("ApplicationData")
148            .field("application_tag", &self.application_tag)
149            .finish()
150    }
151}
152
153impl std::fmt::Display for ApplicationData {
154    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
155        write!(f, "({}): {}", self.application_tag, hex::encode(&self.plain_text))
156    }
157}
158
159impl ApplicationData {
160    const TAG_SIZE: usize = Tag::SIZE;
161
162    pub fn from_bytes(data: &[u8]) -> crate::errors::Result<Self> {
163        if data.len() >= Self::TAG_SIZE {
164            Ok(Self {
165                application_tag: Tag::from_be_bytes(
166                    data[0..Self::TAG_SIZE]
167                        .try_into()
168                        .map_err(|_e| crate::errors::PacketError::DecodingError("ApplicationData.tag".into()))?,
169                ),
170                plain_text: Box::from(&data[Self::TAG_SIZE..]),
171            })
172        } else {
173            Err(crate::errors::PacketError::DecodingError("ApplicationData".into()))
174        }
175    }
176
177    pub fn to_bytes(&self) -> Box<[u8]> {
178        let mut buf = Vec::with_capacity(Self::TAG_SIZE + self.plain_text.len());
179        buf.extend_from_slice(&self.application_tag.to_be_bytes());
180        buf.extend_from_slice(&self.plain_text);
181        buf.into_boxed_slice()
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[test]
190    fn reserved_tag_v1_range_is_stable() {
191        let range = ReservedTag::range();
192        assert_eq!(range.start, 0);
193        assert_eq!(range.count(), 16); // 0 to 15 inclusive
194    }
195
196    #[test]
197    fn tag_should_be_obtainable_as_reserved_when_created_from_a_reserved_range() {
198        let reserved_tag = ReservedTag::Ping as u64;
199
200        assert_eq!(Tag::from(reserved_tag), Tag::Reserved(reserved_tag));
201    }
202
203    #[test]
204    fn tag_should_be_obtainable_as_undefined_reserved_when_created_from_an_undefined_value_in_reserved_range() {
205        let reserved_tag_without_assignment = 7u64;
206
207        assert_eq!(
208            Tag::from(reserved_tag_without_assignment),
209            Tag::Reserved(ReservedTag::Undefined as u64)
210        );
211    }
212
213    #[test]
214    fn v1_format_is_binary_stable() -> anyhow::Result<()> {
215        let original = ApplicationData::new(10u64, &[0_u8, 1_u8]);
216        let reserialized = ApplicationData::from_bytes(&original.to_bytes())?;
217        let reserialized = ApplicationData::from_bytes(&reserialized.to_bytes())?;
218
219        assert_eq!(original, reserialized);
220
221        Ok(())
222    }
223
224    #[test]
225    fn test_application_data() -> anyhow::Result<()> {
226        let ad_1 = ApplicationData::new(10u64, &[0_u8, 1_u8]);
227        let ad_2 = ApplicationData::from_bytes(&ad_1.to_bytes())?;
228        assert_eq!(ad_1, ad_2);
229
230        let ad_1 = ApplicationData::new(0u64, &[]);
231        let ad_2 = ApplicationData::from_bytes(&ad_1.to_bytes())?;
232        assert_eq!(ad_1, ad_2);
233
234        let ad_1 = ApplicationData::new(10u64, &[0_u8, 1_u8]);
235        let ad_2 = ApplicationData::from_bytes(&ad_1.to_bytes())?;
236        assert_eq!(ad_1, ad_2);
237
238        Ok(())
239    }
240}