From 0ed05f6116a08fc73c13a36e9364f1e539f3743a Mon Sep 17 00:00:00 2001 From: DJMrTV Date: Sun, 19 Jan 2025 13:02:15 +0100 Subject: [PATCH] initial commit --- .gitignore | 1 + .idea/.gitignore | 5 + .idea/discord.xml | 14 ++ .idea/modules.xml | 8 ++ .idea/splatoon-server-rust.iml | 11 ++ .idea/vcs.xml | 6 + Cargo.lock | 86 +++++++++++++ Cargo.toml | 10 ++ src/endianness.rs | 221 ++++++++++++++++++++++++++++++++ src/main.rs | 6 + src/prudp/auth_module.rs | 0 src/prudp/endpoint.rs | 3 + src/prudp/mod.rs | 4 + src/prudp/packet.rs | 228 +++++++++++++++++++++++++++++++++ src/prudp/server.rs | 42 ++++++ 15 files changed, 645 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/discord.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/splatoon-server-rust.iml create mode 100644 .idea/vcs.xml create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/endianness.rs create mode 100644 src/main.rs create mode 100644 src/prudp/auth_module.rs create mode 100644 src/prudp/endpoint.rs create mode 100644 src/prudp/mod.rs create mode 100644 src/prudp/packet.rs create mode 100644 src/prudp/server.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..104c42f --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..dba6738 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/splatoon-server-rust.iml b/.idea/splatoon-server-rust.iml new file mode 100644 index 0000000..cf84ae4 --- /dev/null +++ b/.idea/splatoon-server-rust.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..795dbf0 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,86 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bytemuck" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "splatoon-server-rust" +version = "0.1.0" +dependencies = [ + "bytemuck", + "dotenv", + "once_cell", + "thiserror", +] + +[[package]] +name = "syn" +version = "2.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9f3e9d6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "splatoon-server-rust" +version = "0.1.0" +edition = "2021" + +[dependencies] +bytemuck = "1.21.0" +dotenv = "0.15.0" +once_cell = "1.20.2" +thiserror = "2.0.11" diff --git a/src/endianness.rs b/src/endianness.rs new file mode 100644 index 0000000..a7b9aff --- /dev/null +++ b/src/endianness.rs @@ -0,0 +1,221 @@ +use std::io; +use std::io::Read; +use std::marker::PhantomData; +use std::pin::Pin; +use bytemuck::Pod; + +#[cfg(target_endian = "little")] +pub const IS_LITTLE_ENDIAN: bool = true; + +#[cfg(target_endian = "big")] +pub const IS_LITTLE_ENDIAN: bool = false; + +pub const IS_BIG_ENDIAN: bool = !IS_LITTLE_ENDIAN; + +pub mod little_endian{ + use std::io; + use std::io::Read; + + #[inline] + pub fn read_u16(reader: &mut (impl Read + ?Sized)) -> io::Result{ + let mut data = [0u8; 2]; + + reader.read_exact(&mut data)?; + + Ok(((data[0] as u16) << 8) | (data[1] as u16)) + } + + #[inline] + pub fn read_u32(reader: &mut (impl Read + ?Sized)) -> io::Result{ + let mut data = [0u8; 4]; + + reader.read_exact(&mut data)?; + + Ok( + ((data[0] as u32) << 24) | + ((data[1] as u32) << 16) | + ((data[2] as u32) << 8) | + (data[3] as u32) + ) + } +} + +pub struct StructMultiReadIter<'a, T: Pod + SwapEndian>{ + reader: &'a mut dyn Read, + left_to_read: usize, + swap_endian: bool, + _phantom_data: PhantomData<&'static T> +} + +impl<'a, T: Pod + SwapEndian> Iterator for StructMultiReadIter<'a, T>{ + type Item = io::Result; + + #[inline] + fn next(&mut self) -> Option { + if self.left_to_read == 0{ + None + } else { + Some(self.reader.read_struct(self.swap_endian)) + } + } +} + +impl<'a, T: Pod + SwapEndian> Drop for StructMultiReadIter<'a, T>{ + #[inline] + fn drop(&mut self) { + + // read all the structs we would be reading and discard them to make the result after using + // this always be the same + while let Some(_) = self.next() { } + } +} + + + + +pub trait ReadExtensions: Read{ + #[inline] + fn read_le_u16(&mut self) -> io::Result{ + little_endian::read_u16(self) + } + + #[inline] + fn read_le_u32(&mut self) -> io::Result{ + little_endian::read_u32(self) + } + + #[inline] + fn read_le_struct(&mut self) -> io::Result{ + let mut data = T::zeroed(); + let bytes = bytemuck::bytes_of_mut(&mut data); + + self.read_exact(bytes)?; + + if cfg!(not(target_endian = "little")){ + data = data.swap_endian(); + } + + Ok(data) + } + + #[inline] + fn read_struct(&mut self, swap_endian: bool) -> io::Result{ + let mut data = T::zeroed(); + let bytes = bytemuck::bytes_of_mut(&mut data); + + self.read_exact(bytes)?; + + if swap_endian{ + data = data.swap_endian(); + } + + Ok(data) + } + + + fn read_struct_multi(&mut self, swap_endian: bool, count: usize) -> io::Result>; +} + +impl ReadExtensions for T{ + // i was forced to put this here because it requires info about self + #[inline] + fn read_struct_multi(&mut self, swap_endian: bool, count: usize) -> io::Result>{ + Ok(StructMultiReadIter{ + reader: self, + swap_endian, + left_to_read: count, + _phantom_data: Default::default() + }) + } +} + + +pub trait SwapEndian: Clone + Copy{ + fn swap_endian(self) -> Self; +} + +impl SwapEndian for u8{ + #[inline] + fn swap_endian(self) -> Self { + self + } +} + +impl SwapEndian for u16{ + #[inline] + fn swap_endian(self) -> Self { + self.swap_bytes() + } +} +impl SwapEndian for u32{ + #[inline] + fn swap_endian(self) -> Self { + self.swap_bytes() + } +} + +impl SwapEndian for u64{ + #[inline] + fn swap_endian(self) -> Self { + self.swap_bytes() + } +} + +impl SwapEndian for i8{ + #[inline] + fn swap_endian(self) -> Self { + self + } +} + +impl SwapEndian for i16{ + #[inline] + fn swap_endian(self) -> Self { + self.swap_bytes() + } +} +impl SwapEndian for i32{ + #[inline] + fn swap_endian(self) -> Self { + self.swap_bytes() + } +} + +impl SwapEndian for i64{ + #[inline] + fn swap_endian(self) -> Self { + self.swap_bytes() + } +} + +impl SwapEndian for (T, U){ + #[inline] + fn swap_endian(self) -> Self { + (self.0.swap_endian(), self.1.swap_endian()) + } +} + +impl SwapEndian for (T, U, V){ + #[inline] + fn swap_endian(self) -> Self { + (self.0.swap_endian(), self.1.swap_endian(), self.2.swap_endian()) + } +} + +impl SwapEndian for (T, U, V, W){ + #[inline] + fn swap_endian(self) -> Self { + (self.0.swap_endian(), self.1.swap_endian(), self.2.swap_endian(), self.3.swap_endian()) + } +} + +impl SwapEndian for [T; size]{ + #[inline] + fn swap_endian(mut self) -> Self { + for elem in &mut self{ + *elem = elem.swap_endian(); + } + + self + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..8357261 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,6 @@ +mod endianness; +mod prudp; + +fn main() { + println!("Hello, world!"); +} diff --git a/src/prudp/auth_module.rs b/src/prudp/auth_module.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/prudp/endpoint.rs b/src/prudp/endpoint.rs new file mode 100644 index 0000000..32c1593 --- /dev/null +++ b/src/prudp/endpoint.rs @@ -0,0 +1,3 @@ +pub struct Endpoint{ + +} \ No newline at end of file diff --git a/src/prudp/mod.rs b/src/prudp/mod.rs new file mode 100644 index 0000000..10f89b9 --- /dev/null +++ b/src/prudp/mod.rs @@ -0,0 +1,4 @@ +pub mod packet; +mod server; +mod endpoint; +mod auth_module; \ No newline at end of file diff --git a/src/prudp/packet.rs b/src/prudp/packet.rs new file mode 100644 index 0000000..66d38f4 --- /dev/null +++ b/src/prudp/packet.rs @@ -0,0 +1,228 @@ +use std::fmt::{Debug, Formatter}; +use std::hint::unreachable_unchecked; +use std::io; +use std::io::{Cursor, ErrorKind, Read, Seek}; +use bytemuck::{Pod, Zeroable}; +use thiserror::Error; +use v_byte_macros::{EnumTryInto, SwapEndian}; +use crate::endianness::{IS_BIG_ENDIAN, IS_LITTLE_ENDIAN, ReadExtensions}; + +#[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(transparent)] +#[derive(Copy, Clone, Pod, Zeroable, SwapEndian)] +pub struct TypesFlags(u16); + +impl TypesFlags{ + pub fn get_types(self) -> u8 { + (self.0 & 0x000F) as u8 + } + + pub fn get_flags(self) -> u16 { + (self.0 & 0xFFF0) >> 4 + } + + pub fn types(self, val: u8) -> Self { + Self((self.0 & 0xFFF0) | (val as u16 & 0x000F)) + } + + pub fn flags(self, val: u16) -> Self { + Self((self.0 & 0x000F) | ((val << 4) & 0xFFF0) ) + } +} + +impl Debug for TypesFlags{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let stream_type = self.get_types(); + let port_number = self.get_flags(); + write!(f, "TypesFlags{{ types: {}, flags: {} }}", stream_type, port_number) + } +} + +#[repr(transparent)] +#[derive(Copy, Clone, Pod, Zeroable, SwapEndian)] +pub struct VirtualPort(u8); + +impl VirtualPort{ + pub fn get_stream_type(self) -> u8 { + (self.0 & 0x0F) as u8 + } + + pub fn get_port_number(self) -> u8 { + (self.0 & 0xF0) >> 4 + } + + pub fn stream_type(self, val: u8) -> Self { + let masked_val = val & 0x0F; + assert_eq!(masked_val, val); + + Self((self.0 & 0xF0) | masked_val) + } + + pub fn port_number(self, val: u8) -> Self { + let masked_val = val & 0x0F; + assert_eq!(masked_val, val); + + Self((self.0 & 0x0F) | (masked_val << 4)) + } +} + +impl Debug for VirtualPort{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let stream_type = self.get_stream_type(); + let port_number = self.get_port_number(); + write!(f, "VirtualPort{{ stream_type: {}, port_number: {} }}", stream_type, port_number) + } +} + +#[repr(C, packed)] +#[derive(Debug, Copy, Clone, Pod, Zeroable, SwapEndian)] +pub struct PRUDPHeader{ + magic: [u8; 2], + 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, +} +#[repr(u16)] +#[derive(EnumTryInto)] +enum PacketSpecificData{ + E = 0x10 +} + +#[derive(Debug)] +pub struct PRUDPPacket{ + pub header: PRUDPHeader +} + +#[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, + // Getting here would mean that the invariant has been violated, thus this isnt my + // problem lmao + _ => unsafe { unreachable_unchecked() } + } + } +} + +impl Into for OptionId{ + fn into(self) -> u8 { + self.0 + } +} + +impl PRUDPPacket{ + pub fn new(reader: &mut (impl Read + Seek)) -> Result{ + let header: PRUDPHeader = 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)) + } + + //discard it for now + let _: [u8; 16] = reader.read_struct(IS_BIG_ENDIAN)?; + + 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); + + + 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? + continue; + } + + let option_id: OptionId = OptionId::new(option_id)?; + + if option_id.option_type_size() != value_size{ + return Err(Error::InvalidOptionSize { + size: value_size, + id: option_id.0 + }) + } + + let mut option_data = vec![0u8,value_size]; + if packet_specific_data_cursor.read_exact(&mut option_data).is_err(){ + break; + } + }*/ + + + let mut packet_payload = vec![0u8; header.payload_size as usize]; + + reader.read_exact(&mut packet_payload)?; + + Ok(Self{ + header + }) + } +} + +#[cfg(test)] +mod test{ + use super::{PRUDPHeader}; + #[test] + fn size_test(){ + assert_eq!(size_of::(), 14); + } +} \ No newline at end of file diff --git a/src/prudp/server.rs b/src/prudp/server.rs new file mode 100644 index 0000000..cd7f904 --- /dev/null +++ b/src/prudp/server.rs @@ -0,0 +1,42 @@ +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; +use std::sync::{Arc, Mutex}; +use crate::prudp::endpoint::Endpoint; + +pub struct NexServer{ + pub endpoints: Mutex>, + _no_outside_construction: PhantomData<()> +} + +impl NexServer{ + fn server_thread_entry(){ + + } + + pub fn new() -> Arc{ + let own_impl = NexServer{ + endpoints: Default::default(), + _no_outside_construction: Default::default() + }; + + let arc = Arc::new(own_impl); + } +} + +#[cfg(test)] +mod test{ + use std::ops::Deref; + use std::sync::Arc; + use crate::prudp::server::{NexServer}; + + #[test] + fn test(){ + let server = NexServer::new(); + + let a = (server.deref()).clone(); + + } +} + + +