Skip to main content

yog_network/
lib.rs

1//! Networking — custom packets as raw bytes over named channels.
2//!
3//! Yog never puts NBT on the wire: a packet payload is just `Vec<u8>`, so mod
4//! authors serialize with whatever is fastest for them (bincode, protobuf,
5//! FlatBuffers, plain bytes). NBT is only ever built when something must be
6//! handed to the game itself.
7//!
8//! For ergonomic typed packets, use the [`packet!`] macro:
9//!
10//! ```
11//! use yog_network::{packet, Packet};
12//!
13//! packet! {
14//!     pub struct TeleportPacket {
15//!         x: f64,
16//!         y: f64,
17//!         z: f64,
18//!         player: String,
19//!     }
20//! }
21//!
22//! let pkt = TeleportPacket { x: 0.0, y: 64.0, z: 0.0, player: "Steve".into() };
23//! let bytes = pkt.encode();
24//! let decoded = TeleportPacket::decode(&bytes).unwrap();
25//! assert_eq!(decoded.player, "Steve");
26//! ```
27
28/// A packet received on a channel.
29#[derive(Debug, Clone)]
30pub struct PacketEvent {
31    /// Channel id, e.g. `mymod:sync`.
32    pub channel: String,
33    /// Sender's player name on the server side; empty for packets the client
34    /// received from the server.
35    pub player: String,
36    /// Raw payload bytes.
37    pub payload: Vec<u8>,
38}
39
40// ── Typed packet support ──────────────────────────────────────────────────────
41
42/// Encode/decode a single field to/from a byte buffer.
43///
44/// Implemented for the types supported by [`packet!`]:
45/// `bool`, `i32`, `i64`, `u32`, `u64`, `f32`, `f64`, `String`, `Vec<u8>`.
46pub trait PacketField: Sized {
47    fn write_to(&self, buf: &mut Vec<u8>);
48    fn read_from(buf: &[u8], pos: &mut usize) -> Option<Self>;
49}
50
51macro_rules! impl_fixed {
52    ($T:ty, $N:literal) => {
53        impl PacketField for $T {
54            fn write_to(&self, buf: &mut Vec<u8>) {
55                buf.extend_from_slice(&self.to_le_bytes());
56            }
57            fn read_from(buf: &[u8], pos: &mut usize) -> Option<Self> {
58                let end = pos.checked_add($N)?;
59                if end > buf.len() { return None; }
60                let v = Self::from_le_bytes(buf[*pos..end].try_into().ok()?);
61                *pos = end;
62                Some(v)
63            }
64        }
65    };
66}
67
68impl_fixed!(i32, 4);
69impl_fixed!(i64, 8);
70impl_fixed!(u32, 4);
71impl_fixed!(u64, 8);
72impl_fixed!(f32, 4);
73impl_fixed!(f64, 8);
74
75impl PacketField for bool {
76    fn write_to(&self, buf: &mut Vec<u8>) { buf.push(*self as u8); }
77    fn read_from(buf: &[u8], pos: &mut usize) -> Option<Self> {
78        let b = *buf.get(*pos)?;
79        *pos += 1;
80        Some(b != 0)
81    }
82}
83
84impl PacketField for String {
85    fn write_to(&self, buf: &mut Vec<u8>) {
86        let bytes = self.as_bytes();
87        (bytes.len() as u32).write_to(buf);
88        buf.extend_from_slice(bytes);
89    }
90    fn read_from(buf: &[u8], pos: &mut usize) -> Option<Self> {
91        let len = u32::read_from(buf, pos)? as usize;
92        let end = pos.checked_add(len)?;
93        if end > buf.len() { return None; }
94        let s = std::str::from_utf8(&buf[*pos..end]).ok()?.to_owned();
95        *pos = end;
96        Some(s)
97    }
98}
99
100impl PacketField for Vec<u8> {
101    fn write_to(&self, buf: &mut Vec<u8>) {
102        (self.len() as u32).write_to(buf);
103        buf.extend_from_slice(self);
104    }
105    fn read_from(buf: &[u8], pos: &mut usize) -> Option<Self> {
106        let len = u32::read_from(buf, pos)? as usize;
107        let end = pos.checked_add(len)?;
108        if end > buf.len() { return None; }
109        let v = buf[*pos..end].to_vec();
110        *pos = end;
111        Some(v)
112    }
113}
114
115/// A typed packet that can be encoded to / decoded from a raw byte buffer.
116///
117/// Implement this trait via the [`packet!`] macro — do not implement manually.
118pub trait Packet: Sized {
119    fn encode(&self) -> Vec<u8>;
120    fn decode(bytes: &[u8]) -> Option<Self>;
121}
122
123/// Declare a typed packet struct whose fields are automatically encoded/decoded.
124///
125/// All field types must implement [`PacketField`]: `bool`, `i32`, `i64`,
126/// `u32`, `u64`, `f32`, `f64`, `String`, `Vec<u8>`.
127///
128/// ```
129/// use yog_network::{packet, Packet};
130///
131/// packet! {
132///     pub struct PingPacket {
133///         seq: u32,
134///         message: String,
135///     }
136/// }
137///
138/// let p = PingPacket { seq: 1, message: "hello".into() };
139/// let decoded = PingPacket::decode(&p.encode()).unwrap();
140/// assert_eq!(decoded.seq, 1);
141/// ```
142#[macro_export]
143macro_rules! packet {
144    (
145        $(#[$meta:meta])*
146        $vis:vis struct $Name:ident {
147            $( $fvis:vis $field:ident : $ty:ty ),* $(,)?
148        }
149    ) => {
150        $(#[$meta])*
151        $vis struct $Name {
152            $( $fvis $field : $ty, )*
153        }
154
155        impl $crate::Packet for $Name {
156            fn encode(&self) -> ::std::vec::Vec<u8> {
157                let mut buf = ::std::vec::Vec::new();
158                $( $crate::PacketField::write_to(&self.$field, &mut buf); )*
159                buf
160            }
161            fn decode(bytes: &[u8]) -> ::std::option::Option<Self> {
162                let mut pos = 0usize;
163                $(
164                    let $field = <$ty as $crate::PacketField>::read_from(bytes, &mut pos)?;
165                )*
166                ::std::option::Option::Some(Self { $( $field, )* })
167            }
168        }
169    };
170}