827c13b69e
dont build netstack in CI additional rust 2024 fixes fixes removed temp.rs first round of cleanup removed duplicated NS types moved gateway probe to the monorepo
306 lines
9.9 KiB
Rust
306 lines
9.9 KiB
Rust
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
use crate::codec::NymCodecError;
|
|
use bytes::{BufMut, BytesMut};
|
|
use nym_sphinx_forwarding::packet::MixPacket;
|
|
use nym_sphinx_params::PacketType;
|
|
use nym_sphinx_params::key_rotation::SphinxKeyRotation;
|
|
use nym_sphinx_params::packet_sizes::PacketSize;
|
|
use nym_sphinx_params::packet_version::{
|
|
CURRENT_PACKET_VERSION, LEGACY_PACKET_VERSION, PacketVersion,
|
|
};
|
|
use nym_sphinx_types::NymPacket;
|
|
|
|
#[derive(Debug)]
|
|
pub struct FramedNymPacket {
|
|
/// Contains any metadata helping receiver to handle the underlying packet.
|
|
pub(crate) header: Header,
|
|
|
|
/// The actual SphinxPacket being sent.
|
|
pub(crate) packet: NymPacket,
|
|
}
|
|
|
|
impl FramedNymPacket {
|
|
pub fn new(
|
|
packet: NymPacket,
|
|
packet_type: PacketType,
|
|
key_rotation: SphinxKeyRotation,
|
|
use_legacy_packet_encoding: bool,
|
|
) -> Self {
|
|
// If this fails somebody is using the library in a super incorrect way, because they
|
|
// already managed to somehow create a sphinx packet
|
|
let packet_size = PacketSize::get_type(packet.len()).unwrap();
|
|
|
|
let packet_version = if use_legacy_packet_encoding {
|
|
LEGACY_PACKET_VERSION
|
|
} else {
|
|
PacketVersion::new()
|
|
};
|
|
|
|
let header = Header {
|
|
packet_version,
|
|
packet_size,
|
|
key_rotation,
|
|
packet_type,
|
|
};
|
|
|
|
FramedNymPacket { header, packet }
|
|
}
|
|
|
|
pub fn from_mix_packet(packet: MixPacket, use_legacy_packet_encoding: bool) -> Self {
|
|
let typ = packet.packet_type();
|
|
let rot = packet.key_rotation();
|
|
FramedNymPacket::new(packet.into_packet(), typ, rot, use_legacy_packet_encoding)
|
|
}
|
|
|
|
pub fn header(&self) -> Header {
|
|
self.header
|
|
}
|
|
|
|
pub fn packet_size(&self) -> PacketSize {
|
|
self.header.packet_size
|
|
}
|
|
|
|
pub fn packet_type(&self) -> PacketType {
|
|
self.header.packet_type
|
|
}
|
|
|
|
pub fn into_inner(self) -> NymPacket {
|
|
self.packet
|
|
}
|
|
|
|
pub fn packet(&self) -> &NymPacket {
|
|
&self.packet
|
|
}
|
|
|
|
pub fn key_rotation(&self) -> SphinxKeyRotation {
|
|
self.header.key_rotation
|
|
}
|
|
|
|
pub fn is_sphinx(&self) -> bool {
|
|
self.packet.is_sphinx()
|
|
}
|
|
}
|
|
|
|
// Contains any metadata that might be useful for sending between mix nodes.
|
|
// TODO: in theory all those data could be put in a single `u8` by setting appropriate bits,
|
|
// but would that really be worth it?
|
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
|
pub struct Header {
|
|
/// Represents the wire format version used to construct this packet.
|
|
pub packet_version: PacketVersion,
|
|
|
|
/// Represents type and consequently size of the included SphinxPacket.
|
|
pub packet_size: PacketSize,
|
|
|
|
/// Represents information regarding which key rotation has been used for constructing this packet.
|
|
pub key_rotation: SphinxKeyRotation,
|
|
|
|
/// Represents whether this packet is sent in a `vpn_mode` meaning it should not get delayed
|
|
/// and shared keys might get reused. Mixnodes are capable of inferring this mode from the
|
|
/// delay values inside the packet header (i.e. being set to 0), however, gateway, being final
|
|
/// hop, would be unable to do so.
|
|
///
|
|
/// TODO: ask @AP whether this can be sent like this - could it introduce some anonymity issues?
|
|
/// (note: this will be behind some encryption, either something implemented by us or some SSL action)
|
|
// Note: currently packet_type is deprecated but is still left as a concept behind to not break
|
|
// compatibility with existing network
|
|
pub packet_type: PacketType,
|
|
}
|
|
|
|
impl Header {
|
|
pub(crate) const INITIAL_SIZE: usize = 3;
|
|
pub(crate) const V8_SIZE: usize = 4;
|
|
|
|
pub(crate) fn encode(&self, dst: &mut BytesMut) {
|
|
let len = self.encoded_size();
|
|
|
|
if dst.len() < len {
|
|
dst.reserve(len);
|
|
}
|
|
|
|
dst.put_u8(self.packet_version.as_u8());
|
|
dst.put_u8(self.packet_size as u8);
|
|
dst.put_u8(self.packet_type as u8);
|
|
|
|
if !self.packet_version.is_initial() {
|
|
dst.put_u8(self.key_rotation as u8)
|
|
}
|
|
|
|
// reserve bytes for the actual packet
|
|
dst.reserve(self.packet_size.size());
|
|
}
|
|
|
|
pub(crate) fn frame_size(&self) -> usize {
|
|
self.encoded_size() + self.packet_size.size()
|
|
}
|
|
|
|
pub(crate) fn encoded_size(&self) -> usize {
|
|
if self.packet_version.is_initial() {
|
|
Self::INITIAL_SIZE
|
|
} else {
|
|
Self::V8_SIZE
|
|
}
|
|
}
|
|
|
|
pub(crate) fn decode(src: &mut BytesMut) -> Result<Option<Self>, NymCodecError> {
|
|
if src.len() < Self::INITIAL_SIZE {
|
|
// can't do anything if we don't have enough bytes - but reserve enough for the next call
|
|
src.reserve(Self::INITIAL_SIZE);
|
|
return Ok(None);
|
|
}
|
|
|
|
let packet_version = PacketVersion::try_from(src[0])?;
|
|
if packet_version > CURRENT_PACKET_VERSION {
|
|
// received an unsupported packet version - we don't know how it's meant to look like!
|
|
// (this is in preparation for the dual support of breaking sphinx changes)
|
|
return Err(NymCodecError::UnsupportedPacketVersion {
|
|
received: packet_version,
|
|
max_supported: CURRENT_PACKET_VERSION,
|
|
});
|
|
}
|
|
|
|
// we need to be able to decode the full header
|
|
if !packet_version.is_initial() && src.len() < Self::V8_SIZE {
|
|
src.reserve(1);
|
|
return Ok(None);
|
|
}
|
|
|
|
let key_rotation = if packet_version.is_initial() {
|
|
SphinxKeyRotation::Unknown
|
|
} else {
|
|
SphinxKeyRotation::try_from(src[3])?
|
|
};
|
|
|
|
Ok(Some(Header {
|
|
packet_version,
|
|
packet_size: PacketSize::try_from(src[1])?,
|
|
packet_type: PacketType::try_from(src[2])?,
|
|
key_rotation,
|
|
}))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod header_encoding {
|
|
use super::*;
|
|
use nym_sphinx_params::packet_version::INITIAL_PACKET_VERSION_NUMBER;
|
|
|
|
fn dummy_header() -> Header {
|
|
Header {
|
|
packet_version: CURRENT_PACKET_VERSION,
|
|
packet_size: Default::default(),
|
|
key_rotation: Default::default(),
|
|
packet_type: Default::default(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn header_can_be_decoded_from_a_valid_encoded_instance() {
|
|
let header = dummy_header();
|
|
let mut bytes = BytesMut::new();
|
|
header.encode(&mut bytes);
|
|
let decoded = Header::decode(&mut bytes).unwrap().unwrap();
|
|
assert_eq!(decoded, header);
|
|
}
|
|
|
|
#[test]
|
|
fn decoding_will_fail_for_unknown_packet_size() {
|
|
let unknown_packet_size: u8 = 255;
|
|
// make sure this is still 'unknown' for if we make changes in the future
|
|
assert!(PacketSize::try_from(unknown_packet_size).is_err());
|
|
|
|
// unfortunately this will only work for the 'versioned' variant
|
|
// due to the hack used to get legacy mode compatibility
|
|
let mut bytes = BytesMut::from(
|
|
[
|
|
PacketVersion::new().as_u8(),
|
|
unknown_packet_size,
|
|
PacketType::default() as u8,
|
|
SphinxKeyRotation::EvenRotation as u8,
|
|
]
|
|
.as_ref(),
|
|
);
|
|
assert!(Header::decode(&mut bytes).is_err())
|
|
}
|
|
|
|
#[test]
|
|
fn decoding_will_fail_for_unknown_packet_type() {
|
|
let unknown_packet_type: u8 = 255;
|
|
// make sure this is still 'unknown' for if we make changes in the future
|
|
assert!(PacketType::try_from(unknown_packet_type).is_err());
|
|
|
|
let mut bytes = BytesMut::from(
|
|
[
|
|
PacketVersion::try_from(INITIAL_PACKET_VERSION_NUMBER)
|
|
.unwrap()
|
|
.as_u8(),
|
|
PacketSize::default() as u8,
|
|
unknown_packet_type,
|
|
]
|
|
.as_ref(),
|
|
);
|
|
assert!(Header::decode(&mut bytes).is_err())
|
|
}
|
|
|
|
#[test]
|
|
fn decode_will_allocate_enough_bytes_for_next_call() {
|
|
let mut empty_bytes = BytesMut::new();
|
|
let decode_attempt_1 = Header::decode(&mut empty_bytes).unwrap();
|
|
assert!(decode_attempt_1.is_none());
|
|
assert!(empty_bytes.capacity() > Header::V8_SIZE);
|
|
|
|
let mut empty_bytes = BytesMut::with_capacity(1);
|
|
let decode_attempt_2 = Header::decode(&mut empty_bytes).unwrap();
|
|
assert!(decode_attempt_2.is_none());
|
|
assert!(empty_bytes.capacity() > Header::V8_SIZE);
|
|
}
|
|
|
|
#[test]
|
|
fn header_encoding_reserves_enough_bytes_for_full_sphinx_packet_() {
|
|
let packet_sizes = vec![
|
|
PacketSize::AckPacket,
|
|
PacketSize::RegularPacket,
|
|
PacketSize::ExtendedPacket8,
|
|
PacketSize::ExtendedPacket16,
|
|
PacketSize::ExtendedPacket32,
|
|
];
|
|
for packet_size in packet_sizes {
|
|
let header = Header {
|
|
packet_version: PacketVersion::new(),
|
|
packet_size,
|
|
..dummy_header()
|
|
};
|
|
let mut bytes = BytesMut::new();
|
|
header.encode(&mut bytes);
|
|
assert_eq!(bytes.capacity(), bytes.len() + packet_size.size())
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn header_decoding_will_reject_future_versions() {
|
|
let future_version = PacketVersion::try_from(123).unwrap();
|
|
|
|
let unchecked_header = Header {
|
|
packet_version: future_version,
|
|
packet_size: PacketSize::RegularPacket,
|
|
key_rotation: SphinxKeyRotation::EvenRotation,
|
|
packet_type: PacketType::Mix,
|
|
};
|
|
let mut bytes = BytesMut::new();
|
|
unchecked_header.encode(&mut bytes);
|
|
|
|
match Header::decode(&mut bytes).unwrap_err() {
|
|
NymCodecError::UnsupportedPacketVersion {
|
|
received,
|
|
max_supported,
|
|
} => {
|
|
assert_eq!(received, future_version);
|
|
assert_eq!(max_supported, CURRENT_PACKET_VERSION);
|
|
}
|
|
_ => panic!("unexpected error variant"),
|
|
}
|
|
}
|
|
}
|