// no clue why this produces a warning where `#[repr(u16)]` is below, // the thing is says to do also breaks the code, so we just // force the compiler to shut up here #![allow(unused_parens)] use crate::prudp::packet::PacketOption::{ ConnectionSignature, FragmentId, InitialSequenceId, MaximumSubstreamId, SupportedFunctions, }; use bytemuck::{Pod, Zeroable}; use hmac::{Hmac, Mac}; use log::{error, warn}; use md5::{Digest, Md5}; use rnex_core::prudp::socket_addr::PRUDPSockAddr; use rnex_core::prudp::types_flags::TypesFlags; use rnex_core::prudp::types_flags::flags::ACK; use rnex_core::prudp::virtual_port::VirtualPort; use std::fmt::Debug; use std::io; use std::io::{Cursor, Read, Seek, Write}; use std::net::SocketAddr; use std::net::SocketAddrV4; use thiserror::Error; use v_byte_helpers::SwapEndian; use v_byte_helpers::{IS_BIG_ENDIAN, ReadExtensions}; type Md5Hmac = Hmac; #[derive(Error, Debug)] pub enum Error { #[error("{0}")] IO(#[from] io::Error), #[error("invalid magic {0:#06x}")] InvalidMagic(u16), #[error("invalid version {0}")] InvalidVersion(u8), #[error("invalid option id {0}")] InvalidOptionId(u8), #[error("option size {size} doesnt match expected option for given option id {id}")] InvalidOptionSize { id: u8, size: u8 }, } pub type Result = std::result::Result; #[repr(C)] #[derive(Debug, Copy, Clone, Pod, Zeroable, SwapEndian, Eq, PartialEq)] pub struct PRUDPV1Header { pub magic: [u8; 2], pub version: u8, pub packet_specific_size: u8, pub payload_size: u16, pub source_port: VirtualPort, pub destination_port: VirtualPort, pub types_and_flags: TypesFlags, pub session_id: u8, pub substream_id: u8, pub sequence_id: u16, } impl Default for PRUDPV1Header { fn default() -> Self { Self { magic: [0xEA, 0xD0], version: 1, session_id: 0, source_port: VirtualPort(0), sequence_id: 0, payload_size: 0, destination_port: VirtualPort(0), types_and_flags: TypesFlags(0), packet_specific_size: 0, substream_id: 0, } } } #[derive(Debug, Clone, Eq, PartialEq)] pub enum PacketOption { SupportedFunctions(u32), ConnectionSignature([u8; 16]), FragmentId(u8), InitialSequenceId(u16), MaximumSubstreamId(u8), } impl PacketOption { fn from(option_id: OptionId, option_data: &[u8]) -> io::Result { let mut data_cursor = Cursor::new(option_data); let val = match option_id.into() { 0 => SupportedFunctions(data_cursor.read_struct(IS_BIG_ENDIAN)?), 1 => ConnectionSignature(data_cursor.read_struct(IS_BIG_ENDIAN)?), 2 => FragmentId(data_cursor.read_struct(IS_BIG_ENDIAN)?), 3 => InitialSequenceId(data_cursor.read_struct(IS_BIG_ENDIAN)?), 4 => MaximumSubstreamId(data_cursor.read_struct(IS_BIG_ENDIAN)?), _ => unreachable!(), }; Ok(val) } fn write_to_stream(&self, stream: &mut impl Write) -> io::Result<()> { match self { SupportedFunctions(v) => { stream.write_all(&[0, size_of_val(v) as u8])?; stream.write_all(&v.to_le_bytes())?; } ConnectionSignature(v) => { stream.write_all(&[1, size_of_val(v) as u8])?; stream.write_all(v)?; } FragmentId(v) => { stream.write_all(&[2, size_of_val(v) as u8])?; stream.write_all(&v.to_le_bytes())?; } InitialSequenceId(v) => { stream.write_all(&[3, size_of_val(v) as u8])?; stream.write_all(&v.to_le_bytes())?; } MaximumSubstreamId(v) => { stream.write_all(&[4, size_of_val(v) as u8])?; stream.write_all(&v.to_le_bytes())?; } } Ok(()) } fn write_size(&self) -> u8 { match self { SupportedFunctions(_) => 2 + 4, ConnectionSignature(_) => 2 + 16, FragmentId(_) => 2 + 1, InitialSequenceId(_) => 2 + 2, MaximumSubstreamId(_) => 2 + 1, } } } #[derive(Debug, Default, Clone, Eq, PartialEq)] pub struct PRUDPV1Packet { pub header: PRUDPV1Header, pub packet_signature: [u8; 16], pub payload: Vec, pub options: Vec, } #[derive(Copy, Clone, Debug)] // Invariant: can only contain 0, 1, 2, 3 or 4 struct OptionId(u8); impl OptionId { fn new(val: u8) -> Result { // Invariant is upheld because we only create the object if it doesn't violate the invariant match val { 0 | 1 | 2 | 3 | 4 => Ok(Self(val)), _ => Err(Error::InvalidOptionId(val)), } } fn option_type_size(self) -> u8 { match self.0 { 0 => 4, 1 => 16, 2 => 1, 3 => 2, 4 => 1, _ => unreachable!(), } } } impl Into for OptionId { fn into(self) -> u8 { self.0 } } impl PRUDPV1Packet { pub fn new(reader: &mut (impl Read + Seek)) -> Result { let header: PRUDPV1Header = reader.read_struct(IS_BIG_ENDIAN)?; if header.magic[0] != 0xEA || header.magic[1] != 0xD0 { return Err(Error::InvalidMagic(u16::from_be_bytes(header.magic))); } if header.version != 1 { return Err(Error::InvalidVersion(header.version)); } let packet_signature: [u8; 16] = reader.read_struct(IS_BIG_ENDIAN)?; //let packet_signature: [u8; 16] = [0; 16]; assert_eq!(reader.stream_position().ok(), Some(14 + 16)); let mut packet_specific_buffer = vec![0u8; header.packet_specific_size as usize]; reader.read_exact(&mut packet_specific_buffer)?; //no clue whats up with options but they are broken let mut packet_specific_data_cursor = Cursor::new(&packet_specific_buffer); let mut options = Vec::new(); loop { let Ok(option_id): io::Result = packet_specific_data_cursor.read_struct(IS_BIG_ENDIAN) else { break; }; let Ok(value_size): io::Result = packet_specific_data_cursor.read_struct(IS_BIG_ENDIAN) else { break; }; if value_size == 0 { // skip it if its 0 and dont check? warn!("reading packets options might be going wrong"); continue; } let option_id: OptionId = OptionId::new(option_id)?; if option_id.option_type_size() != value_size { error!("invalid packet options"); return Err(Error::InvalidOptionSize { size: value_size, id: option_id.0, }); } let mut option_data = vec![0u8; value_size as usize]; if packet_specific_data_cursor .read_exact(&mut option_data[..]) .is_err() { error!("unable to read options"); break; } options.push(PacketOption::from(option_id, &option_data)?); } let mut payload = vec![0u8; header.payload_size as usize]; reader.read_exact(&mut payload)?; Ok(Self { header, packet_signature, payload, options, }) } pub fn base_acknowledgement_packet(&self) -> Self { let base = self.base_response_packet(); let mut flags = self.header.types_and_flags.flags(0); flags.set_flag(ACK); let options = self .options .iter() .filter(|o| matches!(o, FragmentId(_))) .cloned() .collect(); Self { header: PRUDPV1Header { types_and_flags: flags, sequence_id: self.header.sequence_id, substream_id: self.header.substream_id, session_id: self.header.session_id, ..base.header }, options, ..base } } pub fn source_sockaddr(&self, socket_addr_v4: SocketAddrV4) -> PRUDPSockAddr { PRUDPSockAddr { regular_socket_addr: SocketAddr::V4(socket_addr_v4), virtual_port: self.header.source_port, } } fn generate_options_bytes(&self) -> Vec { let mut vec = Vec::new(); for option in &self.options { option .write_to_stream(&mut vec) .expect("vec should always automatically be able to extend"); } vec } pub fn calculate_signature_value( &self, access_key: &str, session_key: Option<[u8; 32]>, connection_signature: Option<[u8; 16]>, ) -> [u8; 16] { let access_key_bytes = access_key.as_bytes(); let access_key_sum: u32 = access_key_bytes.iter().map(|v| *v as u32).sum(); let access_key_sum_bytes: [u8; 4] = access_key_sum.to_le_bytes(); let header_data: [u8; 8] = bytemuck::bytes_of(&self.header)[0x6..].try_into().unwrap(); let option_bytes = self.generate_options_bytes(); let mut md5 = md5::Md5::default(); md5.update(access_key_bytes); let key = md5.finalize(); let mut hmac = Md5Hmac::new_from_slice(&key).expect("fuck"); hmac.write(&header_data) .expect("error during hmac calculation"); if let Some(session_key) = session_key { hmac.write(&session_key) .expect("error during hmac calculation"); } hmac.write(&access_key_sum_bytes) .expect("error during hmac calculation"); if let Some(connection_signature) = connection_signature { hmac.write(&connection_signature) .expect("error during hmac calculation"); } hmac.write(&option_bytes) .expect("error during hmac calculation"); hmac.write_all(&self.payload) .expect("error during hmac calculation"); hmac.finalize().into_bytes()[0..16] .try_into() .expect("invalid hmac size") } pub fn calculate_and_assign_signature( &mut self, access_key: &str, session_key: Option<[u8; 32]>, connection_signature: Option<[u8; 16]>, ) { self.packet_signature = self.calculate_signature_value(access_key, session_key, connection_signature); } pub fn set_sizes(&mut self) { self.header.packet_specific_size = self.options.iter().map(|o| o.write_size()).sum(); self.header.payload_size = self.payload.len() as u16; } pub fn base_response_packet(&self) -> Self { Self { header: PRUDPV1Header { magic: [0xEA, 0xD0], types_and_flags: TypesFlags(0), destination_port: self.header.source_port, source_port: self.header.destination_port, payload_size: 0, version: 1, packet_specific_size: 0, sequence_id: 0, session_id: 0, substream_id: 0, }, packet_signature: [0; 16], payload: Default::default(), options: Default::default(), } } pub fn write_to(&self, writer: &mut impl Write) -> io::Result<()> { writer.write_all(bytemuck::bytes_of(&self.header))?; writer.write_all(&self.packet_signature)?; for option in &self.options { option.write_to_stream(writer)?; } writer.write_all(&self.payload)?; Ok(()) } } #[cfg(test)] mod test { use super::{OptionId, PRUDPV1Header, PacketOption, TypesFlags}; use rnex_core::prudp::{ types_flags::{ flags::{NEED_ACK, RELIABLE}, types::DATA, }, virtual_port::VirtualPort, }; #[test] fn size_test() { assert_eq!(size_of::(), 14); } #[test] fn test_options() { let packet_types = [0, 1, 2, 3, 4]; for p_type in packet_types { let option_id = OptionId::new(p_type).unwrap(); let buf = vec![0; option_id.option_type_size() as usize]; let opt = PacketOption::from(option_id, &buf).unwrap(); { let mut write_buf = vec![]; opt.write_to_stream(&mut write_buf).unwrap(); assert_eq!(write_buf.len() as u8, opt.write_size()) } } } #[test] fn header_read() { let header = PRUDPV1Header { version: 0, destination_port: VirtualPort(0), substream_id: 0, types_and_flags: TypesFlags(0), session_id: 0, packet_specific_size: 0, payload_size: 0, sequence_id: 0, magic: [0xEA, 0xD0], source_port: VirtualPort(0), }; let bytes = bytemuck::bytes_of(&header); let bytes = &bytes[0x6..]; let header_data: [u8; 8] = bytes.try_into().unwrap(); } #[test] fn test_types_flags() { let types = TypesFlags::default().types(DATA).flags(NEED_ACK | RELIABLE); assert_ne!((types.0 >> 4) & NEED_ACK, 0); assert_ne!((types.0 >> 4) & RELIABLE, 0); assert_ne!((types.0 & 0xFF) as u8 & DATA, 0); } }