Skip to main content

hopr_crypto_packet/
types.rs

1use std::{borrow::Cow, fmt::Formatter, marker::PhantomData, ops::Not};
2
3use hopr_crypto_sphinx::{
4    errors::SphinxError,
5    prelude::{PaddedPayload, SURB, SphinxHeaderSpec, SphinxSuite},
6};
7use hopr_types::primitive::prelude::GeneralError;
8
9use crate::{HoprSphinxHeaderSpec, HoprSphinxSuite, PAYLOAD_SIZE_INT};
10
11flagset::flags! {
12   /// Individual packet signals passed up between the packet sender and destination.
13   #[repr(u8)]
14   #[derive(PartialOrd, Ord, strum::EnumString, strum::Display)]
15   pub enum PacketSignal: u8 {
16        /// The other party is in a "SURB distress" state, potentially running out of SURBs soon.
17        ///
18        /// Has no effect on packets that take the "forward path".
19        SurbDistress = 0b0000_0001,
20        /// The other party has run out of SURBs, and this was potentially the last message they could
21        /// send.
22        ///
23        /// Has no effect on packets that take the "forward path".
24        ///
25        /// Implies [`SurbDistress`].
26        OutOfSurbs = 0b0000_0011,
27   }
28}
29
30/// Packet signal states that can be passed between the packet sender and destination.
31///
32/// These signals can be typically propagated up to the application layer to take an appropriate
33/// action to the signaled states.
34pub type PacketSignals = flagset::FlagSet<PacketSignal>;
35
36/// Additional encoding of a packet message that can be preceded by a number of [`SURBs`](SURB).
37pub struct PacketMessage<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize>(PaddedPayload<P>, PhantomData<(S, H)>);
38
39/// Convenience alias for HOPR specific [`PacketMessage`].
40pub type HoprPacketMessage = PacketMessage<HoprSphinxSuite, HoprSphinxHeaderSpec, PAYLOAD_SIZE_INT>;
41
42/// Individual parts of a [`PacketMessage`]: SURBs, the actual message (payload) and additional signals for the
43/// recipient.
44pub struct PacketParts<'a, S: SphinxSuite, H: SphinxHeaderSpec> {
45    /// Contains (a potentially empty) list of SURBs.
46    pub surbs: Vec<SURB<S, H>>,
47    /// Contains the actual packet payload.
48    pub payload: Cow<'a, [u8]>,
49    /// Additional packet signals from the sender to the recipient.
50    pub signals: PacketSignals,
51}
52
53impl<S: SphinxSuite, H: SphinxHeaderSpec> Clone for PacketParts<'_, S, H>
54where
55    H::KeyId: Clone,
56    H::SurbReceiverData: Clone,
57{
58    fn clone(&self) -> Self {
59        Self {
60            surbs: self.surbs.clone(),
61            payload: self.payload.clone(),
62            signals: self.signals,
63        }
64    }
65}
66
67impl<S: SphinxSuite, H: SphinxHeaderSpec> std::fmt::Debug for PacketParts<'_, S, H>
68where
69    H::KeyId: std::fmt::Debug,
70    H::SurbReceiverData: std::fmt::Debug,
71{
72    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
73        f.debug_struct("PacketParts")
74            .field("surbs", &self.surbs)
75            .field("payload", &self.payload)
76            .field("signals", &self.signals)
77            .finish()
78    }
79}
80
81impl<S: SphinxSuite, H: SphinxHeaderSpec> PartialEq for PacketParts<'_, S, H>
82where
83    H::KeyId: PartialEq,
84    H::SurbReceiverData: PartialEq,
85{
86    fn eq(&self, other: &Self) -> bool {
87        self.surbs == other.surbs && self.payload == other.payload && self.signals == other.signals
88    }
89}
90
91impl<S: SphinxSuite, H: SphinxHeaderSpec> Eq for PacketParts<'_, S, H>
92where
93    H::KeyId: Eq,
94    H::SurbReceiverData: Eq,
95{
96}
97
98/// Convenience alias for HOPR specific [`PacketParts`].
99pub type HoprPacketParts<'a> = PacketParts<'a, HoprSphinxSuite, HoprSphinxHeaderSpec>;
100
101// Coerces PacketSignals to only lower 4 bits.
102pub(crate) const S_MASK: u8 = 0b0000_1111;
103
104impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> PacketMessage<S, H, P> {
105    /// Size of the message header.
106    ///
107    /// This is currently 1 byte to indicate the number of SURBs that precede the message.
108    pub const HEADER_LEN: usize = 1;
109    /// The maximum number of SURBs a packet message can hold, according to RFC-0003.
110    ///
111    /// The number of SURBs in a `PacketMessage` is intentionally limited to 15, so that
112    /// the upper 4 bits remain reserved for additional flags.
113    pub const MAX_SURBS_PER_MESSAGE: usize = S_MASK as usize;
114}
115
116impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> TryFrom<PacketParts<'_, S, H>> for PacketMessage<S, H, P> {
117    type Error = SphinxError;
118
119    fn try_from(value: PacketParts<S, H>) -> Result<Self, Self::Error> {
120        if value.surbs.len() > Self::MAX_SURBS_PER_MESSAGE {
121            return Err(GeneralError::ParseError("HoprPacketMessage.num_surbs not valid".into()).into());
122        }
123
124        if value.signals.bits() > S_MASK {
125            return Err(GeneralError::ParseError("HoprPacketMessage.flags not valid".into()).into());
126        }
127
128        // The total size of the packet message must not exceed the maximum packet size.
129        if Self::HEADER_LEN + value.surbs.len() * SURB::<S, H>::SIZE + value.payload.len() > P {
130            return Err(GeneralError::ParseError("HoprPacketMessage.size not valid".into()).into());
131        }
132
133        let mut ret = Vec::with_capacity(PaddedPayload::<P>::SIZE);
134        let flags_and_len = (value.signals.bits() << S_MASK.trailing_ones()) | (value.surbs.len() as u8 & S_MASK);
135        ret.push(flags_and_len);
136        for surb in value.surbs.into_iter().map(|s| s.into_boxed()) {
137            ret.extend(surb);
138        }
139        ret.extend_from_slice(value.payload.as_ref());
140
141        // Save one reallocation by using the vector that we just created
142        Ok(Self(PaddedPayload::new_from_vec(ret)?, PhantomData))
143    }
144}
145
146impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> TryFrom<PacketMessage<S, H, P>> for PacketParts<'_, S, H> {
147    type Error = SphinxError;
148
149    fn try_from(value: PacketMessage<S, H, P>) -> Result<Self, Self::Error> {
150        let data = value.0.into_unpadded()?;
151        if data.is_empty() {
152            return Err(GeneralError::ParseError("HoprPacketMessage.size".into()).into());
153        }
154
155        let num_surbs = (data[0] & S_MASK) as usize;
156        let signals = PacketSignals::new((data[0] & S_MASK.not()) >> S_MASK.trailing_ones())
157            .map_err(|_| GeneralError::ParseError("HoprPacketMessage.signals".into()))?;
158
159        if num_surbs > 0 {
160            let surb_end = num_surbs * SURB::<S, H>::SIZE;
161            if surb_end >= data.len() {
162                return Err(GeneralError::ParseError("HoprPacketMessage.num_surbs not valid".into()).into());
163            }
164
165            let mut data = data.into_vec();
166
167            let surbs = data[1..=surb_end]
168                .chunks_exact(SURB::<S, H>::SIZE)
169                .map(SURB::<S, H>::try_from)
170                .collect::<Result<Vec<_>, _>>()?;
171
172            // Skip buffer all the way to the end of the SURBs.
173            data.drain(0..=surb_end).for_each(drop);
174
175            Ok(PacketParts {
176                surbs,
177                payload: Cow::Owned(data),
178                signals,
179            })
180        } else {
181            let mut data = data.into_vec();
182            data.remove(0);
183            Ok(PacketParts {
184                surbs: Vec::with_capacity(0),
185                payload: Cow::Owned(data),
186                signals,
187            })
188        }
189    }
190}
191
192impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> From<PaddedPayload<P>> for PacketMessage<S, H, P> {
193    fn from(value: PaddedPayload<P>) -> Self {
194        Self(value, PhantomData)
195    }
196}
197
198impl<S: SphinxSuite, H: SphinxHeaderSpec, const P: usize> From<PacketMessage<S, H, P>> for PaddedPayload<P> {
199    fn from(value: PacketMessage<S, H, P>) -> Self {
200        value.0
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use anyhow::anyhow;
207    use bimap::BiHashMap;
208    use hex_literal::hex;
209    use hopr_crypto_sphinx::prelude::*;
210    use hopr_types::{
211        crypto::prelude::*, crypto_random::Randomizable, internal::routing::HoprSenderId, primitive::prelude::*,
212    };
213
214    use super::*;
215    use crate::{
216        HoprSphinxHeaderSpec, HoprSphinxSuite, HoprSurb,
217        packet::HoprPacket,
218        por::{SurbReceiverInfo, generate_proof_of_relay},
219    };
220
221    lazy_static::lazy_static! {
222        static ref PEERS: [(ChainKeypair, OffchainKeypair); 4] = [
223            (hex!("a7c486ceccf5ab53bd428888ab1543dc2667abd2d5e80aae918da8d4b503a426"), hex!("5eb212d4d6aa5948c4f71574d45dad43afef6d330edb873fca69d0e1b197e906")),
224            (hex!("9a82976f7182c05126313bead5617c623b93d11f9f9691c87b1a26f869d569ed"), hex!("e995db483ada5174666c46bafbf3628005aca449c94ebdc0c9239c3f65d61ae0")),
225            (hex!("ca4bdfd54a8467b5283a0216288fdca7091122479ccf3cfb147dfa59d13f3486"), hex!("9dec751c00f49e50fceff7114823f726a0425a68a8dc6af0e4287badfea8f4a4")),
226            (hex!("e306ebfb0d01d0da0952c9a567d758093a80622c6cb55052bf5f1a6ebd8d7b5c"), hex!("9a82976f7182c05126313bead5617c623b93d11f9f9691c87b1a26f869d569ed"))
227        ].map(|(p1,p2)| (ChainKeypair::from_secret(&p1).expect("lazy static keypair should be valid"), OffchainKeypair::from_secret(&p2).expect("lazy static keypair should be valid")));
228
229        static ref MAPPER: SimpleBiMapper<HoprSphinxSuite, HoprSphinxHeaderSpec> = PEERS
230            .iter()
231            .enumerate()
232            .map(|(i, (_, k))| (KeyIdent::from(i as u32), *k.public()))
233            .collect::<BiHashMap<_, _>>()
234            .into();
235    }
236
237    fn generate_surbs(count: usize) -> anyhow::Result<Vec<SURB<HoprSphinxSuite, HoprSphinxHeaderSpec>>> {
238        let path = PEERS.iter().map(|(_, k)| *k.public()).collect::<Vec<_>>();
239        let path_ids = MAPPER
240            .map_keys_to_ids(&path)
241            .into_iter()
242            .map(|v| v.ok_or(anyhow!("missing id")))
243            .collect::<Result<Vec<_>, _>>()?;
244        let pseudonym = SimplePseudonym::random();
245        let recv_data = HoprSenderId::new(&pseudonym);
246
247        Ok((0..count)
248            .map(|_| {
249                let shared_keys = HoprSphinxSuite::new_shared_keys(&path)?;
250                let (por_strings, por_values) = generate_proof_of_relay(&shared_keys.secrets)
251                    .map_err(|e| CryptoError::Other(GeneralError::NonSpecificError(e.to_string())))?;
252
253                create_surb::<HoprSphinxSuite, HoprSphinxHeaderSpec>(
254                    shared_keys,
255                    &path_ids,
256                    &por_strings,
257                    recv_data,
258                    SurbReceiverInfo::new(por_values, [0u8; 32]),
259                )
260                .map(|(s, _)| s)
261            })
262            .collect::<Result<Vec<_>, _>>()?)
263    }
264
265    #[test]
266    fn hopr_packet_message_message_only() -> anyhow::Result<()> {
267        let parts_1 = HoprPacketParts {
268            surbs: vec![],
269            payload: b"test message".into(),
270            signals: PacketSignal::OutOfSurbs.into(),
271        };
272
273        let parts_2: HoprPacketParts = HoprPacketMessage::try_from(parts_1.clone())?.try_into()?;
274        assert_eq!(parts_1, parts_2);
275
276        Ok(())
277    }
278
279    #[test]
280    fn hopr_packet_message_surbs_only() -> anyhow::Result<()> {
281        let parts_1 = HoprPacketParts {
282            surbs: generate_surbs(2)?,
283            payload: Cow::default(),
284            signals: PacketSignal::OutOfSurbs.into(),
285        };
286
287        let parts_2: HoprPacketParts = HoprPacketMessage::try_from(parts_1.clone())?.try_into()?;
288        assert_eq!(parts_1, parts_2);
289
290        Ok(())
291    }
292
293    #[test]
294    fn hopr_packet_message_surbs_and_msg() -> anyhow::Result<()> {
295        let parts_1 = HoprPacketParts {
296            surbs: generate_surbs(2)?,
297            payload: b"test msg".into(),
298            signals: PacketSignal::OutOfSurbs.into(),
299        };
300
301        let parts_2: HoprPacketParts = HoprPacketMessage::try_from(parts_1.clone())?.try_into()?;
302        assert_eq!(parts_1, parts_2);
303
304        Ok(())
305    }
306
307    #[test]
308    fn hopr_packet_size_msg_size_limit() {
309        let res = HoprPacketMessage::try_from(HoprPacketParts {
310            surbs: vec![],
311            payload: (&[1u8; HoprPacket::PAYLOAD_SIZE + 1]).into(),
312            signals: None.into(),
313        });
314        assert!(res.is_err());
315    }
316
317    #[test]
318    fn hopr_packet_message_surbs_size_limit() -> anyhow::Result<()> {
319        let res = HoprPacketMessage::try_from(PacketParts {
320            surbs: generate_surbs(HoprPacketMessage::MAX_SURBS_PER_MESSAGE + 1)?,
321            payload: Cow::default(),
322            signals: None.into(),
323        });
324        assert!(res.is_err());
325
326        let res = HoprPacketMessage::try_from(HoprPacketParts {
327            surbs: generate_surbs(3)?,
328            payload: Cow::default(),
329            signals: None.into(),
330        });
331        assert!(res.is_err());
332
333        Ok(())
334    }
335
336    #[test]
337    fn hopr_packet_message_surbs_flag_limit() -> anyhow::Result<()> {
338        let res = HoprPacketMessage::try_from(PacketParts {
339            surbs: generate_surbs(3)?,
340            payload: Cow::default(),
341            signals: unsafe { PacketSignals::new_unchecked(16) },
342        });
343        assert!(res.is_err());
344
345        Ok(())
346    }
347
348    #[test]
349    fn hopr_packet_size_msg_and_surb_size_limit() -> anyhow::Result<()> {
350        let res = HoprPacketMessage::try_from(PacketParts {
351            surbs: generate_surbs(2)?,
352            payload: (&[1u8; HoprPacket::PAYLOAD_SIZE - 2 * HoprSurb::SIZE + 1]).into(),
353            signals: None.into(),
354        });
355        assert!(res.is_err());
356
357        Ok(())
358    }
359}