1#[derive(Debug, Clone)]
30pub struct PacketEvent {
31 pub channel: String,
33 pub player: String,
36 pub payload: Vec<u8>,
38}
39
40pub trait PacketField: Sized {
47 fn write_to(&self, buf: &mut Vec<u8>);
48 fn read_from(buf: &[u8], pos: &mut usize) -> Option<Self>;
49 fn encoded_len(&self) -> usize {
53 0
54 }
55}
56
57const LEN_PREFIX: usize = std::mem::size_of::<u32>();
59
60#[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#[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
142pub trait Packet: Sized {
146 fn encode(&self) -> Vec<u8>;
147 fn decode(bytes: &[u8]) -> Option<Self>;
148}
149
150#[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}