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    /// Exact number of bytes [`write_to`](Self::write_to) appends; lets
50    /// [`packet!`] allocate the buffer once. A low estimate is still correct,
51    /// just slower.
52    fn encoded_len(&self) -> usize {
53        0
54    }
55}
56
57/// Byte width of the length prefix in front of strings and byte blobs.
58const LEN_PREFIX: usize = std::mem::size_of::<u32>();
59
60/// The next `len` bytes at `*pos`, advancing past them.
61#[inline]
62fn take<'a>(buf: &'a [u8], pos: &mut usize, len: usize) -> Option<&'a [u8]> {
63    let end = pos.checked_add(len)?;
64    let bytes = buf.get(*pos..end)?;
65    *pos = end;
66    Some(bytes)
67}
68
69/// A `u32`-length-prefixed byte run at `*pos`.
70#[inline]
71fn take_prefixed<'a>(buf: &'a [u8], pos: &mut usize) -> Option<&'a [u8]> {
72    let len = u32::read_from(buf, pos)? as usize;
73    take(buf, pos, len)
74}
75
76fn write_prefixed(bytes: &[u8], buf: &mut Vec<u8>) {
77    (bytes.len() as u32).write_to(buf);
78    buf.extend_from_slice(bytes);
79}
80
81macro_rules! impl_fixed {
82    ($($T:ty),*) => {$(
83        impl PacketField for $T {
84            fn write_to(&self, buf: &mut Vec<u8>) {
85                buf.extend_from_slice(&self.to_le_bytes());
86            }
87            fn read_from(buf: &[u8], pos: &mut usize) -> Option<Self> {
88                const N: usize = std::mem::size_of::<$T>();
89                let bytes: [u8; N] = buf.get(*pos..pos.checked_add(N)?)?.try_into().ok()?;
90                *pos += N;
91                Some(Self::from_le_bytes(bytes))
92            }
93            fn encoded_len(&self) -> usize {
94                std::mem::size_of::<$T>()
95            }
96        }
97    )*};
98}
99
100impl_fixed!(i32, i64, u32, u64, f32, f64);
101
102impl PacketField for bool {
103    fn write_to(&self, buf: &mut Vec<u8>) {
104        buf.push(*self as u8);
105    }
106    fn read_from(buf: &[u8], pos: &mut usize) -> Option<Self> {
107        let b = *buf.get(*pos)?;
108        *pos += 1;
109        Some(b != 0)
110    }
111    fn encoded_len(&self) -> usize {
112        1
113    }
114}
115
116impl PacketField for String {
117    fn write_to(&self, buf: &mut Vec<u8>) {
118        write_prefixed(self.as_bytes(), buf);
119    }
120    fn read_from(buf: &[u8], pos: &mut usize) -> Option<Self> {
121        std::str::from_utf8(take_prefixed(buf, pos)?)
122            .ok()
123            .map(str::to_owned)
124    }
125    fn encoded_len(&self) -> usize {
126        LEN_PREFIX + self.len()
127    }
128}
129
130impl PacketField for Vec<u8> {
131    fn write_to(&self, buf: &mut Vec<u8>) {
132        write_prefixed(self, buf);
133    }
134    fn read_from(buf: &[u8], pos: &mut usize) -> Option<Self> {
135        take_prefixed(buf, pos).map(<[u8]>::to_vec)
136    }
137    fn encoded_len(&self) -> usize {
138        LEN_PREFIX + self.len()
139    }
140}
141
142/// A typed packet that can be encoded to / decoded from a raw byte buffer.
143///
144/// Implement this trait via the [`packet!`] macro — do not implement manually.
145pub trait Packet: Sized {
146    fn encode(&self) -> Vec<u8>;
147    fn decode(bytes: &[u8]) -> Option<Self>;
148}
149
150/// Declare a typed packet struct whose fields are automatically encoded/decoded.
151///
152/// All field types must implement [`PacketField`]: `bool`, `i32`, `i64`,
153/// `u32`, `u64`, `f32`, `f64`, `String`, `Vec<u8>`.
154///
155/// ```
156/// use yog_network::{packet, Packet};
157///
158/// packet! {
159///     pub struct PingPacket {
160///         seq: u32,
161///         message: String,
162///     }
163/// }
164///
165/// let p = PingPacket { seq: 1, message: "hello".into() };
166/// let decoded = PingPacket::decode(&p.encode()).unwrap();
167/// assert_eq!(decoded.seq, 1);
168/// ```
169#[macro_export]
170macro_rules! packet {
171    (
172        $(#[$meta:meta])*
173        $vis:vis struct $Name:ident {
174            $( $fvis:vis $field:ident : $ty:ty ),* $(,)?
175        }
176    ) => {
177        $(#[$meta])*
178        $vis struct $Name {
179            $( $fvis $field : $ty, )*
180        }
181
182        impl $crate::Packet for $Name {
183            fn encode(&self) -> ::std::vec::Vec<u8> {
184                let mut buf = ::std::vec::Vec::with_capacity(
185                    0 $( + $crate::PacketField::encoded_len(&self.$field) )*
186                );
187                $( $crate::PacketField::write_to(&self.$field, &mut buf); )*
188                buf
189            }
190            fn decode(bytes: &[u8]) -> ::std::option::Option<Self> {
191                let mut pos = 0usize;
192                $(
193                    let $field = <$ty as $crate::PacketField>::read_from(bytes, &mut pos)?;
194                )*
195                ::std::option::Option::Some(Self { $( $field, )* })
196            }
197        }
198    };
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    packet! {
206        struct Mixed {
207            a: bool,
208            b: i32,
209            c: f64,
210            s: String,
211            v: Vec<u8>,
212        }
213    }
214
215    #[test]
216    fn round_trip_and_exact_capacity() {
217        let p = Mixed {
218            a: true,
219            b: -7,
220            c: 1.5,
221            s: "hé".into(),
222            v: vec![1, 2, 3],
223        };
224        let bytes = p.encode();
225        assert_eq!(bytes.len(), bytes.capacity());
226        let d = Mixed::decode(&bytes).unwrap();
227        assert!(d.a && d.b == -7 && d.c == 1.5 && d.s == "hé" && d.v == [1, 2, 3]);
228    }
229
230    #[test]
231    fn truncated_input_is_rejected() {
232        let bytes = Mixed {
233            a: false,
234            b: 1,
235            c: 2.0,
236            s: "x".into(),
237            v: vec![9; 8],
238        }
239        .encode();
240        for cut in 0..bytes.len() {
241            assert!(Mixed::decode(&bytes[..cut]).is_none(), "cut at {cut}");
242        }
243    }
244}