use borsh::{BorshDeserialize, BorshSerialize};
use std::fmt;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct SessionId(pub [u8; 32]);
impl SessionId {
pub fn random() -> Self {
let mut bytes = [0u8; 32];
if getrandom::getrandom(&mut bytes).is_err() {
rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut bytes);
}
Self(bytes)
}
pub fn from_bytes(bytes: [u8; 32]) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
}
impl fmt::Debug for SessionId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SessionId({}...)", hex::encode(&self.0[..8]))
}
}
impl fmt::Display for SessionId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}...", hex::encode(&self.0[..8]))
}
}
pub type StreamId = u16;
pub type SequenceNumber = u32;
pub type PacketNumber = u64;
pub const WIRE_VERSION: u8 = 6;
pub const HP_PROTECTED_OFFSET: usize = 0;
pub const HP_PROTECTED_LEN: usize = PacketHeader::SIZE - HP_PROTECTED_OFFSET;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WireError {
Truncated,
}
impl fmt::Display for WireError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WireError::Truncated => write!(f, "truncated packet"),
}
}
}
impl std::error::Error for WireError {}
#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub struct PacketFlags(pub u16);
impl PacketFlags {
pub const RELIABLE: u16 = 0x0001;
pub const ACK: u16 = 0x0002;
pub const FIN: u16 = 0x0004;
pub const UNRELIABLE: u16 = 0x0008;
pub const PRIORITY: u16 = 0x0010;
pub const ENCRYPTED: u16 = 0x0020;
pub const COMPRESSED: u16 = 0x0040;
pub const CONTROL: u16 = 0x0080;
pub const REKEY: u16 = 0x0100;
pub const PATH_VALIDATION: u16 = 0x0200;
pub const COALESCED: u16 = 0x0400;
pub const WINDOW_UPDATE: u16 = 0x0800;
pub const KEEPALIVE: u16 = 0x1000;
pub const PADDED: u16 = 0x2000;
pub const COVER: u16 = 0x4000;
pub const fn empty() -> Self {
Self(0)
}
pub const fn new(bits: u16) -> Self {
Self(bits)
}
#[inline]
pub const fn contains(&self, flag: u16) -> bool {
(self.0 & flag) == flag
}
#[inline]
pub fn set(&mut self, flag: u16) {
self.0 |= flag;
}
#[inline]
pub fn clear(&mut self, flag: u16) {
self.0 &= !flag;
}
#[inline]
pub const fn is_reliable(&self) -> bool {
self.contains(Self::RELIABLE)
}
#[inline]
pub const fn is_ack(&self) -> bool {
self.contains(Self::ACK)
}
#[inline]
pub const fn is_fin(&self) -> bool {
self.contains(Self::FIN)
}
#[inline]
pub const fn is_control(&self) -> bool {
self.contains(Self::CONTROL)
}
#[inline]
pub const fn is_rekey(&self) -> bool {
self.contains(Self::REKEY)
}
}
impl fmt::Debug for PacketFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut flags = Vec::new();
if self.contains(Self::RELIABLE) {
flags.push("RELIABLE");
}
if self.contains(Self::ACK) {
flags.push("ACK");
}
if self.contains(Self::FIN) {
flags.push("FIN");
}
if self.contains(Self::UNRELIABLE) {
flags.push("UNRELIABLE");
}
if self.contains(Self::PRIORITY) {
flags.push("PRIORITY");
}
if self.contains(Self::ENCRYPTED) {
flags.push("ENCRYPTED");
}
if self.contains(Self::COMPRESSED) {
flags.push("COMPRESSED");
}
if self.contains(Self::CONTROL) {
flags.push("CONTROL");
}
if self.contains(Self::REKEY) {
flags.push("REKEY");
}
if self.contains(Self::PATH_VALIDATION) {
flags.push("PATH_VALIDATION");
}
if self.contains(Self::COALESCED) {
flags.push("COALESCED");
}
if self.contains(Self::WINDOW_UPDATE) {
flags.push("WINDOW_UPDATE");
}
write!(f, "PacketFlags({})", flags.join("|"))
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct PacketHeader {
pub version: u8,
pub session_id: SessionId,
pub stream_id: StreamId,
pub packet_number: PacketNumber,
pub flags: PacketFlags,
pub epoch: u8,
pub path_id: u8,
}
impl PacketHeader {
pub const SIZE: usize = 15;
pub const AAD_SIZE: usize = 47;
pub fn new(
session_id: SessionId,
stream_id: StreamId,
packet_number: PacketNumber,
flags: PacketFlags,
) -> Self {
Self {
version: WIRE_VERSION,
session_id,
stream_id,
packet_number,
flags,
epoch: 0,
path_id: 0,
}
}
pub fn with_epoch(mut self, epoch: u8) -> Self {
self.epoch = epoch;
self
}
pub fn with_path_id(mut self, path_id: u8) -> Self {
self.path_id = path_id;
self
}
pub fn to_wire(&self) -> [u8; Self::SIZE] {
let mut b = [0u8; Self::SIZE];
b[0] = self.version;
b[1..9].copy_from_slice(&self.packet_number.to_be_bytes());
b[9..11].copy_from_slice(&self.flags.0.to_be_bytes());
b[11..13].copy_from_slice(&self.stream_id.to_be_bytes());
b[13] = self.epoch;
b[14] = self.path_id;
b
}
pub fn to_aad_image(&self) -> [u8; Self::AAD_SIZE] {
let mut b = [0u8; Self::AAD_SIZE];
b[0] = self.version;
b[1..33].copy_from_slice(&self.session_id.0);
b[33..41].copy_from_slice(&self.packet_number.to_be_bytes());
b[41..43].copy_from_slice(&self.flags.0.to_be_bytes());
b[43..45].copy_from_slice(&self.stream_id.to_be_bytes());
b[45] = self.epoch;
b[46] = self.path_id;
b
}
pub fn from_wire(bytes: &[u8]) -> Result<Self, WireError> {
if bytes.len() < Self::SIZE {
return Err(WireError::Truncated);
}
Ok(Self {
version: bytes[0],
session_id: SessionId([0u8; 32]),
packet_number: u64::from_be_bytes([
bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8],
]),
flags: PacketFlags(u16::from_be_bytes([bytes[9], bytes[10]])),
stream_id: u16::from_be_bytes([bytes[11], bytes[12]]),
epoch: bytes[13],
path_id: bytes[14],
})
}
}
impl fmt::Debug for PacketHeader {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PacketHeader")
.field("version", &self.version)
.field("session", &self.session_id)
.field("stream", &self.stream_id)
.field("pn", &self.packet_number)
.field("flags", &self.flags)
.field("epoch", &self.epoch)
.field("path_id", &self.path_id)
.finish()
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct PhantomPacket {
pub header: PacketHeader,
pub payload: Vec<u8>,
pub extensions: Vec<u8>,
}
impl PhantomPacket {
pub fn new(header: PacketHeader, payload: Vec<u8>) -> Self {
Self {
header,
payload,
extensions: Vec::new(),
}
}
pub fn ack(session_id: SessionId, stream_id: StreamId, ack_packet_number: u64) -> Self {
Self {
header: PacketHeader::new(
session_id,
stream_id,
ack_packet_number,
PacketFlags::new(PacketFlags::ACK),
),
payload: Vec::new(),
extensions: Vec::new(),
}
}
pub fn wire_size(&self) -> usize {
PacketHeader::SIZE + self.payload.len()
}
pub fn to_wire(&self) -> Vec<u8> {
let mut b = Vec::with_capacity(self.wire_size());
b.extend_from_slice(&self.header.to_wire());
b.extend_from_slice(&self.payload);
b
}
pub fn to_wire_masked(&self, mask: &[u8]) -> Vec<u8> {
let mut buf = self.to_wire();
for (b, m) in buf[HP_PROTECTED_OFFSET..PacketHeader::SIZE]
.iter_mut()
.zip(mask)
{
*b ^= *m;
}
buf
}
pub fn from_wire(bytes: &[u8]) -> Result<Self, WireError> {
let header = PacketHeader::from_wire(bytes)?;
let payload = bytes[PacketHeader::SIZE..].to_vec();
Ok(Self {
header,
payload,
extensions: Vec::new(),
})
}
}
#[derive(Clone, Debug)]
pub struct RawPacket {
masked_header: [u8; HP_PROTECTED_LEN],
pub payload: Vec<u8>,
pub extensions: Vec<u8>,
}
impl RawPacket {
pub fn from_wire(bytes: &[u8]) -> Result<Self, WireError> {
if bytes.len() < PacketHeader::SIZE {
return Err(WireError::Truncated);
}
let mut masked_header = [0u8; HP_PROTECTED_LEN];
masked_header.copy_from_slice(&bytes[HP_PROTECTED_OFFSET..PacketHeader::SIZE]);
let payload = bytes[PacketHeader::SIZE..].to_vec();
Ok(Self {
masked_header,
payload,
extensions: Vec::new(),
})
}
pub fn unmask_header(&self, mask: &[u8]) -> Result<PacketHeader, WireError> {
if mask.len() < HP_PROTECTED_LEN {
return Err(WireError::Truncated);
}
let mut hdr = [0u8; PacketHeader::SIZE];
hdr[HP_PROTECTED_OFFSET..].copy_from_slice(&self.masked_header);
for (h, m) in hdr[HP_PROTECTED_OFFSET..].iter_mut().zip(mask) {
*h ^= *m;
}
PacketHeader::from_wire(&hdr)
}
pub fn into_packet(self, header: PacketHeader) -> PhantomPacket {
PhantomPacket {
header,
payload: self.payload,
extensions: self.extensions,
}
}
}
impl fmt::Debug for PhantomPacket {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PhantomPacket")
.field("header", &self.header)
.field("payload_len", &self.payload.len())
.field("extensions_len", &self.extensions.len())
.finish()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
#[borsh(use_discriminant = true)]
#[repr(u8)]
pub enum ControlMessage {
Hello = 0,
HelloAck = 1,
Resume = 2,
ResumeAck = 3,
Migrate = 4,
MigrateAck = 5,
Close = 6,
CloseAck = 7,
Ping = 8,
Pong = 9,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize)]
pub enum LegType {
Kcp,
Tcp,
FakeTls,
Udp,
}
impl LegType {
pub fn is_reliable(&self) -> bool {
matches!(
self,
LegType::Kcp | LegType::Tcp | LegType::FakeTls | LegType::Udp
)
}
pub fn is_obfuscated(&self) -> bool {
matches!(self, LegType::FakeTls)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize)]
pub enum SchedulerMode {
LowLatency,
HighThroughput,
Reliability,
Stealth,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_session_id_random() {
let id1 = SessionId::random();
let id2 = SessionId::random();
assert_ne!(id1, id2);
}
#[test]
fn test_packet_flags() {
let mut flags = PacketFlags::empty();
assert!(!flags.is_reliable());
flags.set(PacketFlags::RELIABLE);
assert!(flags.is_reliable());
flags.set(PacketFlags::ENCRYPTED);
assert!(flags.contains(PacketFlags::RELIABLE));
assert!(flags.contains(PacketFlags::ENCRYPTED));
flags.clear(PacketFlags::RELIABLE);
assert!(!flags.is_reliable());
assert!(flags.contains(PacketFlags::ENCRYPTED));
}
#[test]
fn flags_bit_assignments() {
assert_eq!(PacketFlags::RELIABLE, 0x0001);
assert_eq!(PacketFlags::ENCRYPTED, 0x0020);
assert_eq!(PacketFlags::CONTROL, 0x0080);
assert_eq!(PacketFlags::REKEY, 0x0100);
assert_eq!(PacketFlags::PATH_VALIDATION, 0x0200);
assert_eq!(PacketFlags::COALESCED, 0x0400);
assert_eq!(PacketFlags::WINDOW_UPDATE, 0x0800);
assert_eq!(PacketFlags::KEEPALIVE, 0x1000);
}
#[test]
fn flags_contains_set_clear() {
let mut f = PacketFlags::empty();
assert!(!f.is_reliable());
assert!(!f.is_rekey());
f.set(PacketFlags::RELIABLE | PacketFlags::REKEY);
assert!(f.is_reliable());
assert!(f.is_rekey());
f.clear(PacketFlags::REKEY);
assert!(f.is_reliable());
assert!(!f.is_rekey());
}
#[test]
fn packet_header_serializes_to_15_bytes() {
assert_eq!(PacketHeader::SIZE, 15);
let header = PacketHeader::new(
SessionId::from_bytes([0u8; 32]),
1,
1,
PacketFlags::new(PacketFlags::ENCRYPTED),
);
let bytes = header.to_wire();
assert_eq!(
bytes.len(),
PacketHeader::SIZE,
"the v5 on-wire header is exactly 15 bytes (session_id is off-wire)"
);
assert_eq!(bytes[0], WIRE_VERSION);
assert_eq!(PacketHeader::from_wire(&bytes).expect("roundtrip"), header);
}
#[test]
fn to_aad_image_is_the_47_byte_v4_layout() {
let sid = SessionId::from_bytes([0x5Au8; 32]);
let header = PacketHeader::new(sid, 0x1122, 0x33445566778899AA, PacketFlags::new(0xBCCD))
.with_epoch(0xEE)
.with_path_id(0xFF);
let aad = header.to_aad_image();
assert_eq!(
aad.len(),
47,
"AAD image stays the 47-byte v4 logical header"
);
assert_eq!(aad[0], WIRE_VERSION, "version @ 0");
assert_eq!(
&aad[1..33],
&[0x5Au8; 32],
"session_id @ [1..33] (off-wire, in AAD)"
);
assert_eq!(
&aad[33..41],
&0x33445566778899AAu64.to_be_bytes(),
"packet_number @ [33..41]"
);
assert_eq!(&aad[41..43], &0xBCCDu16.to_be_bytes(), "flags @ [41..43]");
assert_eq!(
&aad[43..45],
&0x1122u16.to_be_bytes(),
"stream_id @ [43..45]"
);
assert_eq!(aad[45], 0xEE, "epoch @ 45");
assert_eq!(aad[46], 0xFF, "path_id @ 46");
}
#[test]
fn to_wire_omits_session_id() {
let sid = SessionId::from_bytes([0xABu8; 32]);
let header = PacketHeader::new(sid, 7, 42, PacketFlags::new(PacketFlags::ENCRYPTED));
let wire = header.to_wire();
assert_eq!(wire.len(), 15);
assert!(
!wire.windows(4).any(|w| w == [0xAB, 0xAB, 0xAB, 0xAB]),
"session_id must not be serialised onto the v5 wire"
);
}
#[test]
fn test_phantom_packet_ack() {
let session_id = SessionId::random();
let ack = PhantomPacket::ack(session_id, 5, 100);
assert!(ack.header.flags.is_ack());
assert_eq!(ack.header.stream_id, 5);
assert_eq!(ack.header.packet_number, 100);
assert!(ack.payload.is_empty());
assert!(ack.extensions.is_empty());
}
#[test]
fn packet_roundtrip_preserves_fields() {
let session_id = SessionId::random();
let header = PacketHeader::new(
session_id,
7,
42,
PacketFlags::new(PacketFlags::ENCRYPTED | PacketFlags::RELIABLE),
)
.with_epoch(3)
.with_path_id(1);
let packet = PhantomPacket::new(header, vec![0xCA, 0xFE, 0xBA, 0xBE]);
let bytes = packet.to_wire();
let decoded = PhantomPacket::from_wire(&bytes).expect("roundtrip");
assert_eq!(
decoded.header.session_id,
SessionId::from_bytes([0u8; 32]),
"session_id is off-wire (reconstructed by Session::parse_protected)"
);
assert_eq!(decoded.header.version, WIRE_VERSION);
assert_eq!(decoded.header.stream_id, 7);
assert_eq!(decoded.header.packet_number, 42);
assert_eq!(decoded.header.epoch, 3);
assert_eq!(decoded.header.path_id, 1);
assert!(decoded.header.flags.is_reliable());
assert!(decoded.header.flags.contains(PacketFlags::ENCRYPTED));
assert_eq!(decoded.payload, vec![0xCA, 0xFE, 0xBA, 0xBE]);
}
#[test]
fn v6_packet_drops_length_prefixes_and_masks_version() {
assert_eq!(HP_PROTECTED_OFFSET, 0, "v6 masks from the version byte");
assert_eq!(
HP_PROTECTED_LEN,
PacketHeader::SIZE,
"v6 masks the whole 15-byte header"
);
assert_eq!(
WIRE_VERSION, 6,
"anti-fingerprint diet bumps the wire version"
);
let header = PacketHeader::new(
SessionId::from_bytes([0u8; 32]),
7,
42,
PacketFlags::new(PacketFlags::ENCRYPTED),
);
let payload = vec![0xCAu8; 40];
let packet = PhantomPacket::new(header, payload.clone());
let wire = packet.to_wire();
assert_eq!(
wire.len(),
PacketHeader::SIZE + payload.len(),
"v6: header ‖ payload, no length prefixes"
);
assert_eq!(
&wire[PacketHeader::SIZE..],
&payload[..],
"payload is the message remainder"
);
assert_eq!(
packet.wire_size(),
PacketHeader::SIZE + payload.len(),
"wire_size() drops the 8 prefix bytes"
);
let decoded = PhantomPacket::from_wire(&wire).expect("v6 roundtrip");
assert_eq!(decoded.payload, payload);
assert!(
decoded.extensions.is_empty(),
"extensions are off the v6 data-plane wire"
);
}
#[test]
fn v6_header_layout_offsets() {
let header = PacketHeader::new(
SessionId::from_bytes([0x5Au8; 32]),
0x1122, 0x33445566778899AA, PacketFlags::new(0xBCCD),
)
.with_epoch(0xEE)
.with_path_id(0xFF);
let b = header.to_wire();
assert_eq!(b.len(), 15, "v5 wire header is 15 bytes");
assert_eq!(b[0], WIRE_VERSION, "version @ 0 (cleartext)");
assert_eq!(
&b[1..9],
&0x33445566778899AAu64.to_be_bytes(),
"packet_number @ [1..9]"
);
assert_eq!(&b[9..11], &0xBCCDu16.to_be_bytes(), "flags @ [9..11]");
assert_eq!(&b[11..13], &0x1122u16.to_be_bytes(), "stream_id @ [11..13]");
assert_eq!(b[13], 0xEE, "epoch @ 13");
assert_eq!(b[14], 0xFF, "path_id @ 14");
let rt = PacketHeader::from_wire(&b).expect("roundtrip");
assert_eq!(rt.packet_number, header.packet_number);
assert_eq!(rt.flags, header.flags);
assert_eq!(rt.stream_id, header.stream_id);
assert_eq!(rt.epoch, header.epoch);
assert_eq!(rt.path_id, header.path_id);
assert_eq!(
rt.session_id,
SessionId::from_bytes([0u8; 32]),
"session_id off-wire (reconstructed from session context)"
);
}
#[test]
fn raw_packet_mask_unmask_round_trip() {
let sid = SessionId::from_bytes([0x5Au8; 32]);
let header = PacketHeader::new(
sid,
0x0203, 0x1111222233334444, PacketFlags::new(PacketFlags::ENCRYPTED | PacketFlags::PRIORITY),
)
.with_epoch(7)
.with_path_id(9);
let packet = PhantomPacket::new(header, vec![0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x11]);
let mask: [u8; 16] = [
0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0,
0xF0, 0x01,
];
let wire = packet.to_wire_masked(&mask);
let cleartext = packet.to_wire();
assert_ne!(
&wire[HP_PROTECTED_OFFSET..PacketHeader::SIZE],
&cleartext[HP_PROTECTED_OFFSET..PacketHeader::SIZE],
"version/pn/flags/stream_id/epoch/path_id must all be masked on the wire"
);
assert_eq!(
HP_PROTECTED_OFFSET, 0,
"v6 masks from byte 0 (version included)"
);
assert_ne!(
wire[0], cleartext[0],
"the version byte is masked in v6 (no constant cleartext byte)"
);
let raw = RawPacket::from_wire(&wire).expect("raw parse");
assert_eq!(raw.payload, packet.payload);
let recovered = raw.unmask_header(&mask).expect("unmask");
assert_eq!(
recovered.version, WIRE_VERSION,
"the masked version byte round-trips through unmask"
);
assert_eq!(recovered.packet_number, header.packet_number);
assert_eq!(recovered.flags, header.flags);
assert_eq!(recovered.stream_id, header.stream_id);
assert_eq!(recovered.epoch, header.epoch);
assert_eq!(recovered.path_id, header.path_id);
assert_eq!(
recovered.session_id,
SessionId::from_bytes([0u8; 32]),
"session_id off-wire after unmask"
);
}
#[test]
fn unmask_header_rejects_short_mask() {
let wire = PhantomPacket::new(
PacketHeader::new(
SessionId::from_bytes([1u8; 32]),
1,
1,
PacketFlags::new(PacketFlags::ENCRYPTED),
),
vec![0u8; 16],
)
.to_wire_masked(&[0u8; 16]);
let raw = RawPacket::from_wire(&wire).expect("raw parse");
assert!(
raw.unmask_header(&[0u8; 8]).is_err(),
"a mask shorter than HP_PROTECTED_LEN must error, not panic"
);
}
#[test]
fn extensions_are_dropped_from_the_v6_wire() {
let session_id = SessionId::random();
let mut packet = PhantomPacket::new(
PacketHeader::new(
session_id,
1,
1,
PacketFlags::new(PacketFlags::CONTROL | PacketFlags::RELIABLE),
),
vec![1, 2, 3],
);
packet.extensions = vec![0xFF, 0x01, 0x00, 0x04, b't', b'e', b's', b't'];
let bytes = packet.to_wire();
assert_eq!(bytes.len(), PacketHeader::SIZE + 3, "header ‖ payload only");
let deser = PhantomPacket::from_wire(&bytes).expect("deserialize failed");
assert_eq!(deser.payload, vec![1, 2, 3]);
assert!(
deser.extensions.is_empty(),
"extensions are off the v6 data-plane wire"
);
}
}