nym_sphinx_params/
packet_sizes.rs

1// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::PacketType;
5#[cfg(feature = "outfox")]
6use nym_sphinx_types::{MIN_PACKET_SIZE, MIX_PARAMS_LEN, OUTFOX_PACKET_OVERHEAD};
7#[cfg(feature = "sphinx")]
8use nym_sphinx_types::{PAYLOAD_OVERHEAD_SIZE, header::HEADER_SIZE};
9use serde::{Deserialize, Serialize};
10use std::cmp::Ordering;
11
12use std::fmt::{Debug, Display, Formatter};
13use std::str::FromStr;
14use thiserror::Error;
15
16// each sphinx packet contains mandatory header and payload padding + markers
17#[cfg(feature = "sphinx")]
18const SPHINX_PACKET_OVERHEAD: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE;
19
20// it's up to the smart people to figure those values out : )
21
22// TODO: even though we have 16B IV, is having just 5B (FRAG_ID_LEN) of the ID possibly insecure?
23
24// TODO: I'm not entirely sure if we can easily extract `<AckEncryptionAlgorithm as NewStreamCipher>::NonceSize`
25// into a const usize before relevant stuff is stabilised in rust...
26#[cfg(feature = "sphinx")]
27const ACK_IV_SIZE: usize = 16;
28
29#[cfg(feature = "sphinx")]
30const ACK_PACKET_SIZE: usize = ACK_IV_SIZE + crate::FRAG_ID_LEN + SPHINX_PACKET_OVERHEAD;
31#[cfg(feature = "sphinx")]
32const REGULAR_PACKET_SIZE: usize = 2 * 1024 + SPHINX_PACKET_OVERHEAD;
33#[cfg(feature = "sphinx")]
34const EXTENDED_PACKET_SIZE_8: usize = 8 * 1024 + SPHINX_PACKET_OVERHEAD;
35#[cfg(feature = "sphinx")]
36const EXTENDED_PACKET_SIZE_16: usize = 16 * 1024 + SPHINX_PACKET_OVERHEAD;
37#[cfg(feature = "sphinx")]
38const EXTENDED_PACKET_SIZE_32: usize = 32 * 1024 + SPHINX_PACKET_OVERHEAD;
39
40#[cfg(feature = "outfox")]
41const OUTFOX_ACK_PACKET_SIZE: usize = MIN_PACKET_SIZE + OUTFOX_PACKET_OVERHEAD;
42#[cfg(feature = "outfox")]
43const OUTFOX_REGULAR_PACKET_SIZE: usize = 2 * 1024 + OUTFOX_PACKET_OVERHEAD;
44
45#[derive(Debug, Error)]
46pub enum InvalidPacketSize {
47    #[error("{received} is not a valid packet size tag")]
48    UnknownPacketTag { received: u8 },
49
50    #[error("{received} is not a valid extended packet size variant")]
51    UnknownExtendedPacketVariant { received: String },
52
53    #[error("{received} does not correspond with any known packet size")]
54    UnknownPacketSize { received: usize },
55}
56
57#[repr(u8)]
58#[derive(Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
59pub enum PacketSize {
60    // for example instant messaging use case
61    #[default]
62    #[serde(rename = "regular")]
63    RegularPacket = 1,
64
65    // for sending SURB-ACKs
66    #[serde(rename = "ack")]
67    AckPacket = 2,
68
69    // for example for streaming fast and furious in uncompressed 10bit 4K HDR quality
70    #[serde(rename = "extended32")]
71    ExtendedPacket32 = 3,
72
73    // for example for streaming fast and furious in heavily compressed lossy RealPlayer quality
74    #[serde(rename = "extended8")]
75    ExtendedPacket8 = 4,
76
77    // for example for streaming fast and furious in compressed XviD quality
78    #[serde(rename = "extended16")]
79    ExtendedPacket16 = 5,
80
81    #[serde(rename = "outfox_regular")]
82    OutfoxRegularPacket = 6,
83
84    // for sending SURB-ACKs
85    #[serde(rename = "outfox_ack")]
86    OutfoxAckPacket = 7,
87}
88
89impl PartialOrd for PacketSize {
90    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
91        // order them by actual packet size
92        Some(self.cmp(other))
93    }
94}
95
96impl Ord for PacketSize {
97    fn cmp(&self, other: &Self) -> Ordering {
98        // order them by actual packet size
99        self.size().cmp(&other.size())
100    }
101}
102
103impl FromStr for PacketSize {
104    type Err = InvalidPacketSize;
105
106    fn from_str(s: &str) -> Result<Self, Self::Err> {
107        match s {
108            "regular" => Ok(Self::RegularPacket),
109            "ack" => Ok(Self::AckPacket),
110            "extended8" => Ok(Self::ExtendedPacket8),
111            "extended16" => Ok(Self::ExtendedPacket16),
112            "extended32" => Ok(Self::ExtendedPacket32),
113            "outfox_regular" => Ok(Self::OutfoxRegularPacket),
114            "outfox_ack" => Ok(Self::OutfoxAckPacket),
115            s => Err(InvalidPacketSize::UnknownExtendedPacketVariant {
116                received: s.to_string(),
117            }),
118        }
119    }
120}
121
122impl Display for PacketSize {
123    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
124        match self {
125            PacketSize::RegularPacket => write!(f, "regular"),
126            PacketSize::AckPacket => write!(f, "ack"),
127            PacketSize::ExtendedPacket32 => write!(f, "extended32"),
128            PacketSize::ExtendedPacket8 => write!(f, "extended8"),
129            PacketSize::ExtendedPacket16 => write!(f, "extended16"),
130            PacketSize::OutfoxRegularPacket => write!(f, "outfox_regular"),
131            PacketSize::OutfoxAckPacket => write!(f, "outfox_ack"),
132        }
133    }
134}
135
136impl Debug for PacketSize {
137    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
138        let name = self.to_string();
139        let size = self.size();
140        let plaintext = self.plaintext_size();
141
142        write!(f, "{name} ({size} bytes / {plaintext} plaintext)")
143    }
144}
145
146impl TryFrom<u8> for PacketSize {
147    type Error = InvalidPacketSize;
148
149    fn try_from(value: u8) -> Result<Self, Self::Error> {
150        match value {
151            _ if value == (PacketSize::RegularPacket as u8) => Ok(Self::RegularPacket),
152            _ if value == (PacketSize::AckPacket as u8) => Ok(Self::AckPacket),
153            _ if value == (PacketSize::ExtendedPacket8 as u8) => Ok(Self::ExtendedPacket8),
154            _ if value == (PacketSize::ExtendedPacket16 as u8) => Ok(Self::ExtendedPacket16),
155            _ if value == (PacketSize::ExtendedPacket32 as u8) => Ok(Self::ExtendedPacket32),
156            _ if value == (PacketSize::OutfoxRegularPacket as u8) => Ok(Self::OutfoxRegularPacket),
157            _ if value == (PacketSize::OutfoxAckPacket as u8) => Ok(Self::OutfoxAckPacket),
158            v => Err(InvalidPacketSize::UnknownPacketTag { received: v }),
159        }
160    }
161}
162
163impl PacketSize {
164    pub const fn size(self) -> usize {
165        #[allow(unreachable_patterns)]
166        match self {
167            #[cfg(feature = "sphinx")]
168            PacketSize::RegularPacket => REGULAR_PACKET_SIZE,
169            #[cfg(feature = "sphinx")]
170            PacketSize::AckPacket => ACK_PACKET_SIZE,
171            #[cfg(feature = "sphinx")]
172            PacketSize::ExtendedPacket8 => EXTENDED_PACKET_SIZE_8,
173            #[cfg(feature = "sphinx")]
174            PacketSize::ExtendedPacket16 => EXTENDED_PACKET_SIZE_16,
175            #[cfg(feature = "sphinx")]
176            PacketSize::ExtendedPacket32 => EXTENDED_PACKET_SIZE_32,
177            #[cfg(feature = "outfox")]
178            PacketSize::OutfoxRegularPacket => OUTFOX_REGULAR_PACKET_SIZE,
179            #[cfg(feature = "outfox")]
180            PacketSize::OutfoxAckPacket => OUTFOX_ACK_PACKET_SIZE,
181            _ => 0,
182        }
183    }
184
185    pub const fn header_size(&self) -> usize {
186        #[allow(unreachable_patterns)]
187        match self {
188            #[cfg(feature = "sphinx")]
189            PacketSize::RegularPacket
190            | PacketSize::AckPacket
191            | PacketSize::ExtendedPacket8
192            | PacketSize::ExtendedPacket16
193            | PacketSize::ExtendedPacket32 => HEADER_SIZE,
194            #[cfg(feature = "outfox")]
195            PacketSize::OutfoxRegularPacket | PacketSize::OutfoxAckPacket => MIX_PARAMS_LEN,
196            _ => 0,
197        }
198    }
199
200    pub const fn payload_overhead(&self) -> usize {
201        #[allow(unreachable_patterns)]
202        match self {
203            #[cfg(feature = "sphinx")]
204            PacketSize::RegularPacket
205            | PacketSize::AckPacket
206            | PacketSize::ExtendedPacket8
207            | PacketSize::ExtendedPacket16
208            | PacketSize::ExtendedPacket32 => PAYLOAD_OVERHEAD_SIZE,
209            #[cfg(feature = "outfox")]
210            PacketSize::OutfoxRegularPacket | PacketSize::OutfoxAckPacket => {
211                OUTFOX_PACKET_OVERHEAD - MIX_PARAMS_LEN // Mix params are calculated into the total overhead so we take them out here
212            }
213            _ => 0,
214        }
215    }
216
217    pub const fn plaintext_size(self) -> usize {
218        self.size() - self.header_size() - self.payload_overhead()
219    }
220
221    pub const fn payload_size(self) -> usize {
222        self.size() - self.header_size()
223    }
224
225    pub fn get_type(size: usize) -> Result<Self, InvalidPacketSize> {
226        if PacketSize::RegularPacket.size() == size {
227            Ok(PacketSize::RegularPacket)
228        } else if PacketSize::AckPacket.size() == size {
229            Ok(PacketSize::AckPacket)
230        } else if PacketSize::ExtendedPacket8.size() == size {
231            Ok(PacketSize::ExtendedPacket8)
232        } else if PacketSize::ExtendedPacket16.size() == size {
233            Ok(PacketSize::ExtendedPacket16)
234        } else if PacketSize::ExtendedPacket32.size() == size {
235            Ok(PacketSize::ExtendedPacket32)
236        } else if PacketSize::OutfoxRegularPacket.size() == size
237            || PacketSize::OutfoxRegularPacket.size() == size + 6
238        {
239            Ok(PacketSize::OutfoxRegularPacket)
240        } else if PacketSize::OutfoxAckPacket.size() == size {
241            Ok(PacketSize::OutfoxAckPacket)
242        } else {
243            Err(InvalidPacketSize::UnknownPacketSize { received: size })
244        }
245    }
246
247    pub fn is_extended_size(&self) -> bool {
248        match self {
249            PacketSize::RegularPacket
250            | PacketSize::AckPacket
251            | PacketSize::OutfoxAckPacket
252            | PacketSize::OutfoxRegularPacket => false,
253            PacketSize::ExtendedPacket8
254            | PacketSize::ExtendedPacket16
255            | PacketSize::ExtendedPacket32 => true,
256        }
257    }
258
259    pub fn as_extended_size(self) -> Option<Self> {
260        if self.is_extended_size() {
261            Some(self)
262        } else {
263            None
264        }
265    }
266
267    pub fn get_type_from_plaintext(
268        plaintext_size: usize,
269        packet_type: PacketType,
270    ) -> Result<Self, InvalidPacketSize> {
271        #[allow(unreachable_patterns)]
272        let overhead = match packet_type {
273            #[cfg(feature = "sphinx")]
274            PacketType::Mix => SPHINX_PACKET_OVERHEAD,
275            #[cfg(feature = "outfox")]
276            PacketType::Outfox => OUTFOX_PACKET_OVERHEAD,
277            _ => 0,
278        };
279        let packet_size = plaintext_size + overhead;
280        Self::get_type(packet_size)
281    }
282}
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287    use crate::AckEncryptionAlgorithm;
288    use nym_crypto::symmetric::stream_cipher::IvSizeUser;
289
290    #[test]
291    fn ack_iv_size_assertion() {
292        let iv_size = AckEncryptionAlgorithm::iv_size();
293        assert_eq!(iv_size, ACK_IV_SIZE);
294    }
295}