fix port binding
This commit is contained in:
parent
dad8ac498c
commit
785341e883
43 changed files with 1543 additions and 431 deletions
18
prudplite/Cargo.toml
Normal file
18
prudplite/Cargo.toml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "prudplite"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[features]
|
||||
nx = []
|
||||
v4-3-11 = []
|
||||
|
||||
[dependencies]
|
||||
rnex-core = { path = "../rnex-core", version = "0.1.1" }
|
||||
tokio = { version = "1.47.0", features = ["full"] }
|
||||
bytemuck = { version = "1.23.1", features = ["derive"] }
|
||||
proxy-common = {path = "../proxy-common"}
|
||||
tokio-tungstenite = {version = "0.28.0", features = ["rustls", "rustls-tls-native-roots"]}
|
||||
log = "0.4.25"
|
||||
futures-util = "0.3.31"
|
||||
v-byte-helpers = { git = "https://github.com/RusticMaple/VByteMacros", version = "0.1.1" }
|
||||
14
prudplite/src/crypto/insecure.rs
Normal file
14
prudplite/src/crypto/insecure.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
use rnex_core::PID;
|
||||
|
||||
use crate::crypto::Crypto;
|
||||
|
||||
pub struct Insecure;
|
||||
|
||||
impl Crypto for Insecure {
|
||||
fn new_connection(&self, data: &[u8]) -> Option<(PID, Vec<u8>)> {
|
||||
Some((100, vec![]))
|
||||
}
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
9
prudplite/src/crypto/mod.rs
Normal file
9
prudplite/src/crypto/mod.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
use rnex_core::PID;
|
||||
|
||||
pub mod insecure;
|
||||
pub mod secure;
|
||||
|
||||
pub trait Crypto: 'static + Send + Sync {
|
||||
fn new_connection(&self, data: &[u8]) -> Option<(PID, Vec<u8>)>;
|
||||
fn new() -> Self;
|
||||
}
|
||||
27
prudplite/src/crypto/secure.rs
Normal file
27
prudplite/src/crypto/secure.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use rnex_core::{
|
||||
PID, executables::common::SECURE_SERVER_ACCOUNT, nex::account::Account,
|
||||
prudp::ticket::read_secure_connection_data, rmc::structures::RmcSerialize,
|
||||
};
|
||||
|
||||
use crate::crypto::Crypto;
|
||||
|
||||
pub struct Secure(&'static Account);
|
||||
|
||||
impl Crypto for Secure {
|
||||
fn new_connection(&self, data: &[u8]) -> Option<(PID, Vec<u8>)> {
|
||||
let (_, pid, check_value) = read_secure_connection_data(data, &self.0)?;
|
||||
|
||||
let check_value_response = check_value + 1;
|
||||
|
||||
let data = bytemuck::bytes_of(&check_value_response);
|
||||
|
||||
let mut response = Vec::new();
|
||||
|
||||
data.serialize(&mut response).ok()?;
|
||||
|
||||
Some((pid, response))
|
||||
}
|
||||
fn new() -> Self {
|
||||
Self(&SECURE_SERVER_ACCOUNT)
|
||||
}
|
||||
}
|
||||
309
prudplite/src/lib.rs
Normal file
309
prudplite/src/lib.rs
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
pub mod crypto;
|
||||
mod packet;
|
||||
|
||||
use std::{collections::HashMap, net::SocketAddr, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
crypto::{Crypto, insecure::Insecure, secure::Secure},
|
||||
packet::{LiteHeader, LitePacket, PacketSpecificData, StreamTypes, create_packet_from},
|
||||
};
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use log::{error, info, warn};
|
||||
use proxy_common::{ProxyStartupParam, new_backend_connection};
|
||||
use rnex_core::{
|
||||
PID,
|
||||
prudp::{
|
||||
socket_addr::PRUDPSockAddr,
|
||||
types_flags::{
|
||||
TypesFlags,
|
||||
flags::{ACK, NEED_ACK, RELIABLE},
|
||||
types::{CONNECT, DATA, DISCONNECT, SYN},
|
||||
},
|
||||
virtual_port::VirtualPort,
|
||||
},
|
||||
util::SplittableBufferConnection,
|
||||
};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio_tungstenite::{
|
||||
WebSocketStream,
|
||||
tungstenite::{
|
||||
Bytes, Message, client::IntoClientRequest, http::header::ACCESS_CONTROL_REQUEST_METHOD,
|
||||
},
|
||||
};
|
||||
|
||||
struct ConnectionState {
|
||||
param: Arc<ProxyStartupParam>,
|
||||
active: bool,
|
||||
websocket: WebSocketStream<TcpStream>,
|
||||
pid: PID,
|
||||
backend_conn: SplittableBufferConnection,
|
||||
addr: PRUDPSockAddr,
|
||||
incoming_reliable: HashMap<u16, LitePacket<Bytes>>,
|
||||
client_reliable_counter: u16,
|
||||
server_reliable_counter: u16,
|
||||
}
|
||||
|
||||
impl ConnectionState {
|
||||
pub async fn handle_incoming_prudp(&mut self, packet: LitePacket<Bytes>, sorted: bool) {
|
||||
let Some(header) = packet.header() else {
|
||||
warn!("invalid data on connection");
|
||||
return;
|
||||
};
|
||||
|
||||
if (header.types_flags.get_flags() & NEED_ACK) != 0 {
|
||||
let data = create_packet_from(
|
||||
LiteHeader {
|
||||
stream_types: StreamTypes::new(
|
||||
self.param.virtual_port.get_stream_type(),
|
||||
self.addr.virtual_port.get_stream_type(),
|
||||
),
|
||||
source_port: self.param.virtual_port.get_port_number(),
|
||||
destination_port: self.addr.virtual_port.get_port_number(),
|
||||
fragment_id: header.fragment_id,
|
||||
types_flags: TypesFlags::default()
|
||||
.types(header.types_flags.get_types())
|
||||
.flags(ACK),
|
||||
sequence_id: header.sequence_id,
|
||||
..Default::default()
|
||||
},
|
||||
&[],
|
||||
&[],
|
||||
);
|
||||
let data: Bytes = data.into();
|
||||
if header.types_flags.get_types() == DISCONNECT {
|
||||
self.websocket.send(Message::Binary(data.clone())).await;
|
||||
self.websocket.send(Message::Binary(data.clone())).await;
|
||||
}
|
||||
self.websocket.send(Message::Binary(data)).await;
|
||||
}
|
||||
|
||||
if (header.types_flags.get_flags() & ACK) != 0 {
|
||||
// we can just safely ignore acks, we ARE sending over tcp after all already guarantees that our packets will arrive
|
||||
// we can however not guarantee the order of incoming client packets so we should still take care of that
|
||||
// (the client might be doing some funny things which we dont know of)
|
||||
return;
|
||||
}
|
||||
|
||||
if (header.types_flags.get_flags() & RELIABLE != 0) & !sorted {
|
||||
self.incoming_reliable.insert(header.sequence_id, packet);
|
||||
if self.incoming_reliable.len() > 5 {
|
||||
self.active = false;
|
||||
warn!("client is spamming out of order reliable packets, throwing out");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
match header.types_flags.get_types() {
|
||||
DATA => {
|
||||
if header.fragment_id != 0 {
|
||||
warn!("fragmented packets arent yet supported");
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(payload) = packet.payload() else {
|
||||
return;
|
||||
};
|
||||
self.backend_conn.send(payload.into()).await;
|
||||
}
|
||||
PING => {}
|
||||
v => {
|
||||
info!("unimplemented packet type: {}", v);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub async fn process_reliable(&mut self) {
|
||||
while let Some(v) = self.incoming_reliable.remove(&self.client_reliable_counter) {
|
||||
self.handle_incoming_prudp(v, true).await;
|
||||
self.client_reliable_counter += 1;
|
||||
}
|
||||
}
|
||||
pub async fn handle_connection(&mut self) {
|
||||
while self.active {
|
||||
tokio::select! {
|
||||
v = self.websocket.next() => {
|
||||
match v {
|
||||
Some(Ok(Message::Binary(v))) => {
|
||||
self.handle_incoming_prudp(LitePacket::new(v), false).await;
|
||||
}
|
||||
_ => {
|
||||
info!("client disconnected or errored out");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
v = self.backend_conn.recv() => {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn websocket_thread_unconnected<C: Crypto>(
|
||||
param: Arc<ProxyStartupParam>,
|
||||
crypto: Arc<C>,
|
||||
conn: TcpStream,
|
||||
addr: SocketAddr,
|
||||
) {
|
||||
let mut websocket = match tokio_tungstenite::accept_async(conn).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("error accepting websocket connection: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
while let Some(Ok(v)) = websocket.next().await {
|
||||
match v {
|
||||
Message::Binary(b) => {
|
||||
let packet = LitePacket::new(b);
|
||||
|
||||
let Some(header) = packet.header() else {
|
||||
error!("got malformed message, disconnecting");
|
||||
return;
|
||||
};
|
||||
|
||||
match header.types_flags.get_types() {
|
||||
SYN => {
|
||||
let Some(supported) = packet.packet_specific_iter() else {
|
||||
error!("got malformed message, disconnecting");
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(PacketSpecificData::SupportedFunctions(s)) = supported
|
||||
.into_iter()
|
||||
.find(|v| matches!(v, PacketSpecificData::SupportedFunctions(_)))
|
||||
else {
|
||||
error!("got malformed message, disconnecting");
|
||||
return;
|
||||
};
|
||||
|
||||
let data = create_packet_from(
|
||||
LiteHeader {
|
||||
destination_port: header.source_port,
|
||||
source_port: param.virtual_port.get_port_number(),
|
||||
stream_types: StreamTypes::new(
|
||||
param.virtual_port.get_stream_type(),
|
||||
header.stream_types.source(),
|
||||
),
|
||||
fragment_id: 0,
|
||||
sequence_id: 0,
|
||||
types_flags: TypesFlags::default().types(SYN).flags(ACK),
|
||||
..Default::default()
|
||||
},
|
||||
&[
|
||||
PacketSpecificData::SupportedFunctions(s & 0xFF),
|
||||
PacketSpecificData::ConnectionSignature([0; 16]),
|
||||
],
|
||||
&[],
|
||||
);
|
||||
websocket.send(Message::Binary(data.into())).await;
|
||||
}
|
||||
CONNECT => {
|
||||
let Some(supported) = packet.packet_specific_iter() else {
|
||||
error!("got malformed message, disconnecting");
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(PacketSpecificData::SupportedFunctions(s)) = supported
|
||||
.into_iter()
|
||||
.find(|v| matches!(v, PacketSpecificData::SupportedFunctions(_)))
|
||||
else {
|
||||
error!("got malformed message, disconnecting");
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(data) = packet.payload() else {
|
||||
error!("got malformed message, disconnecting");
|
||||
return;
|
||||
};
|
||||
|
||||
let Some((pid, data)) = crypto.new_connection(data) else {
|
||||
error!("invalid login data");
|
||||
return;
|
||||
};
|
||||
|
||||
let data = create_packet_from(
|
||||
LiteHeader {
|
||||
destination_port: header.source_port,
|
||||
source_port: param.virtual_port.get_port_number(),
|
||||
stream_types: StreamTypes::new(
|
||||
param.virtual_port.get_stream_type(),
|
||||
header.stream_types.source(),
|
||||
),
|
||||
fragment_id: 0,
|
||||
sequence_id: 0,
|
||||
types_flags: TypesFlags::default().types(CONNECT).flags(ACK),
|
||||
..Default::default()
|
||||
},
|
||||
&[
|
||||
PacketSpecificData::SupportedFunctions(s & 0xFF),
|
||||
PacketSpecificData::ConnectionSignature([0; 16]),
|
||||
],
|
||||
&data,
|
||||
);
|
||||
websocket.send(Message::Binary(data.into())).await;
|
||||
|
||||
let addr = PRUDPSockAddr::new(
|
||||
addr,
|
||||
VirtualPort::new(header.source_port, header.stream_types.source()),
|
||||
);
|
||||
let Some(backend_conn) = new_backend_connection(¶m, addr, pid).await
|
||||
else {
|
||||
error!("unable to connect to backend");
|
||||
return;
|
||||
};
|
||||
let mut connection = ConnectionState {
|
||||
active: true,
|
||||
addr,
|
||||
pid,
|
||||
backend_conn,
|
||||
client_reliable_counter: 2,
|
||||
server_reliable_counter: 1,
|
||||
param,
|
||||
incoming_reliable: HashMap::new(),
|
||||
websocket,
|
||||
};
|
||||
|
||||
connection.handle_connection().await;
|
||||
break;
|
||||
}
|
||||
v => {
|
||||
error!(
|
||||
"invalid packet type for unconnected client {}, disconnecting",
|
||||
v,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
v => {
|
||||
error!("non binary message({:?}) , disconnecting", v);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start_proxy<C: Crypto>(param: ProxyStartupParam) {
|
||||
let param = Arc::new(param);
|
||||
let crypto = Arc::new(C::new());
|
||||
let listener = TcpListener::bind(param.self_private)
|
||||
.await
|
||||
.expect("unable to bind to port");
|
||||
|
||||
while let Ok((connection, addr)) = listener.accept().await {
|
||||
let param = param.clone();
|
||||
let crypto = crypto.clone();
|
||||
tokio::spawn(websocket_thread_unconnected(
|
||||
param, crypto, connection, addr,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start_secure(param: ProxyStartupParam) {
|
||||
start_proxy::<Secure>(param).await;
|
||||
}
|
||||
|
||||
pub async fn start_insecure(param: ProxyStartupParam) {
|
||||
start_proxy::<Insecure>(param).await;
|
||||
}
|
||||
45
prudplite/src/main.rs
Normal file
45
prudplite/src/main.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use futures_util::{SinkExt, StreamExt};
|
||||
use rnex_core::prudp::types_flags::{TypesFlags, flags::NEED_ACK, types::SYN};
|
||||
use tokio_tungstenite::tungstenite::{Message, client::IntoClientRequest, http::header};
|
||||
|
||||
use crate::packet::{LiteHeader, LitePacket, PacketSpecificData, StreamTypes, create_packet_from};
|
||||
|
||||
mod packet;
|
||||
|
||||
const KEY: &str = "4eb18d39";
|
||||
|
||||
const URL: &str = "wss://g2DF33D01-lp1.s.n.srv.nintendo.net";
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let login = URL.into_client_request().unwrap();
|
||||
let (mut stream, response) = tokio_tungstenite::connect_async(login).await.unwrap();
|
||||
|
||||
println!("response: {:?}", response);
|
||||
|
||||
let packet = create_packet_from(
|
||||
LiteHeader {
|
||||
stream_types: StreamTypes::new(10, 10),
|
||||
source_port: 1,
|
||||
destination_port: 1,
|
||||
fragment_id: 0,
|
||||
types_flags: TypesFlags::default().types(SYN).flags(NEED_ACK),
|
||||
sequence_id: 0,
|
||||
..Default::default()
|
||||
},
|
||||
&[PacketSpecificData::SupportedFunctions(0x8)],
|
||||
&[],
|
||||
);
|
||||
|
||||
println!("sending ack");
|
||||
stream.send(Message::Binary(packet.into())).await.unwrap();
|
||||
println!("waiting for response");
|
||||
let packet = stream.next().await.unwrap();
|
||||
let Message::Binary(packet) = packet.unwrap() else {
|
||||
panic!()
|
||||
};
|
||||
let packet = LitePacket::new(packet);
|
||||
|
||||
let header = packet.header().unwrap();
|
||||
|
||||
println!("{:?}", header);
|
||||
}
|
||||
223
prudplite/src/packet.rs
Normal file
223
prudplite/src/packet.rs
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
use std::{
|
||||
fmt::Debug,
|
||||
io::{self, Cursor, Read, Write},
|
||||
};
|
||||
|
||||
use bytemuck::{Pod, Zeroable, bytes_of};
|
||||
use futures_util::Stream;
|
||||
use rnex_core::prudp::types_flags::TypesFlags;
|
||||
use v_byte_helpers::{IS_BIG_ENDIAN, ReadExtensions};
|
||||
|
||||
#[derive(Pod, Zeroable, Copy, Clone, Default, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct LiteHeader {
|
||||
pub magic: u8,
|
||||
pub packet_specific_length: u8,
|
||||
pub payload_size: u16,
|
||||
pub stream_types: StreamTypes,
|
||||
pub source_port: u8,
|
||||
pub destination_port: u8,
|
||||
pub fragment_id: u8,
|
||||
pub types_flags: TypesFlags,
|
||||
pub sequence_id: u16,
|
||||
}
|
||||
|
||||
pub enum PacketSpecificData {
|
||||
SupportedFunctions(u32),
|
||||
ConnectionSignature([u8; 16]),
|
||||
LiteSignature([u8; 16]),
|
||||
}
|
||||
|
||||
impl PacketSpecificData {
|
||||
fn consume(reader: &mut impl Read) -> io::Result<Self> {
|
||||
let mut option_id = 0;
|
||||
reader.read_exact(&mut [option_id])?;
|
||||
let mut size = 0;
|
||||
reader.read_exact(&mut [size])?;
|
||||
|
||||
match option_id {
|
||||
0 => {
|
||||
if size != 4 {
|
||||
Err(io::Error::other(
|
||||
"invalid option size for supported functions",
|
||||
))
|
||||
} else {
|
||||
Ok(Self::SupportedFunctions(reader.read_le_u32()?))
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
if size != 16 {
|
||||
Err(io::Error::other(
|
||||
"invalid option size for connection signature",
|
||||
))
|
||||
} else {
|
||||
Ok(Self::ConnectionSignature(
|
||||
reader.read_struct(IS_BIG_ENDIAN)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
0x80 => {
|
||||
if size != 16 {
|
||||
Err(io::Error::other("invalid option size for lite signature"))
|
||||
} else {
|
||||
Ok(Self::LiteSignature(reader.read_struct(IS_BIG_ENDIAN)?))
|
||||
}
|
||||
}
|
||||
_ => Err(io::Error::other("invalid option id")),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_size(&self) -> usize {
|
||||
2 + match self {
|
||||
PacketSpecificData::SupportedFunctions(_) => 4,
|
||||
Self::ConnectionSignature(_) => 16,
|
||||
Self::LiteSignature(_) => 16,
|
||||
}
|
||||
}
|
||||
|
||||
fn write_self(&self, writer: &mut impl Write) -> io::Result<()> {
|
||||
match self {
|
||||
PacketSpecificData::SupportedFunctions(v) => {
|
||||
writer.write_all(&[0, 4])?;
|
||||
writer.write_all(&v.to_le_bytes())?;
|
||||
}
|
||||
Self::ConnectionSignature(v) => {
|
||||
writer.write_all(&[1, 16])?;
|
||||
writer.write_all(&v[..])?;
|
||||
}
|
||||
Self::LiteSignature(v) => {
|
||||
writer.write_all(&[0x80, 16])?;
|
||||
writer.write_all(&v[..])?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LitePacket<T: AsRef<[u8]>>(T);
|
||||
|
||||
pub struct PacketSpecificIter<'a>(Cursor<&'a [u8]>);
|
||||
|
||||
impl<'a> Iterator for PacketSpecificIter<'a> {
|
||||
type Item = PacketSpecificData;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
PacketSpecificData::consume(&mut self.0).ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]>> LitePacket<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
|
||||
pub fn header(&self) -> Option<&LiteHeader> {
|
||||
bytemuck::try_from_bytes(self.0.as_ref().get(..size_of::<LiteHeader>())?).ok()
|
||||
}
|
||||
pub fn header_mut(&mut self) -> Option<&mut LiteHeader>
|
||||
where
|
||||
T: AsMut<[u8]>,
|
||||
{
|
||||
bytemuck::try_from_bytes_mut(self.0.as_mut().get_mut(..size_of::<LiteHeader>())?).ok()
|
||||
}
|
||||
|
||||
pub fn payload(&self) -> Option<&[u8]> {
|
||||
let header = self.header()?;
|
||||
self.0
|
||||
.as_ref()
|
||||
.get(size_of::<LiteHeader>() + header.packet_specific_length as usize..)
|
||||
}
|
||||
|
||||
pub fn payload_mut(&mut self) -> Option<&mut [u8]>
|
||||
where
|
||||
T: AsMut<[u8]>,
|
||||
{
|
||||
let len = self.header()?.packet_specific_length;
|
||||
self.0
|
||||
.as_mut()
|
||||
.get_mut(size_of::<LiteHeader>() + len as usize..)
|
||||
}
|
||||
|
||||
pub fn packet_specific_raw(&self) -> Option<&[u8]> {
|
||||
let header = self.header()?;
|
||||
self.0.as_ref().get(
|
||||
size_of::<LiteHeader>()
|
||||
..size_of::<LiteHeader>() + header.packet_specific_length as usize,
|
||||
)
|
||||
}
|
||||
pub fn packet_specific_raw_mut(&mut self) -> Option<&mut [u8]>
|
||||
where
|
||||
T: AsMut<[u8]>,
|
||||
{
|
||||
let len = self.header()?.packet_specific_length;
|
||||
self.0
|
||||
.as_mut()
|
||||
.get_mut(size_of::<LiteHeader>()..size_of::<LiteHeader>() + len as usize)
|
||||
}
|
||||
|
||||
pub fn packet_specific_iter(&self) -> Option<PacketSpecificIter> {
|
||||
self.packet_specific_raw()
|
||||
.map(Cursor::new)
|
||||
.map(PacketSpecificIter)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_packet_from(
|
||||
header: LiteHeader,
|
||||
specific_data: &[PacketSpecificData],
|
||||
data: &[u8],
|
||||
) -> Vec<u8> {
|
||||
let specific_size: usize = specific_data.iter().map(|v| v.write_size()).sum();
|
||||
let mut packet = LitePacket::new(vec![
|
||||
0u8;
|
||||
size_of::<LiteHeader>() + specific_size + data.len()
|
||||
]);
|
||||
|
||||
*packet.header_mut().expect("packet malformed in creation") = LiteHeader {
|
||||
magic: 0x80,
|
||||
packet_specific_length: specific_size as u8,
|
||||
payload_size: data.len() as u16,
|
||||
..header
|
||||
};
|
||||
|
||||
let mut cursor = Cursor::new(
|
||||
packet
|
||||
.packet_specific_raw_mut()
|
||||
.expect("packet malformed in creation"),
|
||||
);
|
||||
|
||||
for specific in specific_data {
|
||||
specific.write_self(&mut cursor).unwrap();
|
||||
}
|
||||
|
||||
packet
|
||||
.payload_mut()
|
||||
.expect("packet malformed in creation")
|
||||
.copy_from_slice(data);
|
||||
|
||||
packet.0
|
||||
}
|
||||
|
||||
#[derive(Pod, Zeroable, Copy, Clone, Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct StreamTypes(u8);
|
||||
|
||||
impl Debug for StreamTypes {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "({},{})", self.source(), self.destination())
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamTypes {
|
||||
pub fn new(source_stream: u8, dest_stream: u8) -> Self {
|
||||
Self((source_stream & 0xF << 4) & dest_stream & 0xF)
|
||||
}
|
||||
|
||||
pub fn source(&self) -> u8 {
|
||||
self.0 >> 4
|
||||
}
|
||||
pub fn destination(&self) -> u8 {
|
||||
self.0 & 0xF
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue