Skip to main content

hopr_crypto_packet/
types.rs

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