Skip to content

Commit 53553a5

Browse files
committed
refactor(proto): remove lifetime from read, 1.8 typed decoding
1 parent 1756fb7 commit 53553a5

File tree

10 files changed

+213
-95
lines changed

10 files changed

+213
-95
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crawlspace-macro/src/lib.rs

Lines changed: 28 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* <https://www.gnu.org/licenses/>.
1818
*/
1919

20-
use proc_macro::{Span, TokenStream};
20+
use proc_macro::TokenStream;
2121
use quote::quote;
2222
use syn::{parse_macro_input, parse_quote, DeriveInput, Fields, Ident, Index, Lit, Path};
2323

@@ -41,10 +41,9 @@ pub fn derive_packet(input: TokenStream) -> TokenStream {
4141
attr.parse_nested_meta(|meta| {
4242
if meta.path.is_ident("id") {
4343
let lit = meta.value()?.parse()?;
44-
match lit {
45-
Lit::Str(i) => {
46-
id = Some(i);
47-
}
44+
id = match lit {
45+
Lit::Str(i) => Some(quote!(crawlspace_proto::PacketId::String(#i))),
46+
Lit::Int(i) => Some(quote!(crawlspace_proto::PacketId::Numeric(#i))),
4847
_ => panic!("attribute value `id` must be a string"),
4948
}
5049
} else if meta.path.is_ident("state") {
@@ -88,28 +87,20 @@ pub fn derive_packet(input: TokenStream) -> TokenStream {
8887

8988
let id = id.expect("id must be provided for packet");
9089
let state = state.expect("state must be provided for packet");
91-
let direction = Ident::new(
92-
direction.expect("direction must be provided for packet"),
93-
Span::call_site().into(),
94-
);
9590

9691
let name = input.ident;
9792
let where_clause = input.generics.where_clause.clone();
9893
let generics = input.generics;
9994

10095
quote! {
101-
impl #generics Packet for #name #generics #where_clause {
102-
fn id() -> &'static str {
96+
impl #generics crawlspace_proto::Packet for #name #generics #where_clause {
97+
fn packet_id() -> crawlspace_proto::PacketId {
10398
#id
10499
}
105100

106-
fn state() -> PacketState {
101+
fn packet_state() -> crawlspace_proto::ConnectionState {
107102
#state
108103
}
109-
110-
fn direction() -> PacketDirection {
111-
PacketDirection::#direction
112-
}
113104
}
114105
}
115106
.into()
@@ -118,12 +109,12 @@ pub fn derive_packet(input: TokenStream) -> TokenStream {
118109
/// Automatically implements "straight-across" encoding for the given struct, i.e. fields are
119110
/// serialized in order as is. Supports #[varint] and #[varlong] attributes on integer types to
120111
/// serialize as those formats instead.
121-
#[proc_macro_derive(Encode, attributes(varint, varlong))]
122-
pub fn derive_encode(input: TokenStream) -> TokenStream {
112+
#[proc_macro_derive(Write, attributes(varint, varlong))]
113+
pub fn derive_write(input: TokenStream) -> TokenStream {
123114
let input = parse_macro_input!(input as DeriveInput);
124115

125116
let syn::Data::Struct(data) = input.data else {
126-
panic!("Can only derive Encode on a struct");
117+
panic!("Can only derive Write on a struct");
127118
};
128119

129120
let name = input.ident;
@@ -143,19 +134,19 @@ pub fn derive_encode(input: TokenStream) -> TokenStream {
143134
.any(|attr| attr.meta.path().is_ident("varint"))
144135
{
145136
fields_encoded.extend(quote! {
146-
VarInt(self.#field_name as i32).encode(&mut w)?;
137+
VarInt(self.#field_name as i32).write(w)?;
147138
});
148139
} else if field
149140
.attrs
150141
.iter()
151142
.any(|attr| attr.meta.path().is_ident("varlong"))
152143
{
153144
fields_encoded.extend(quote! {
154-
VarLong(self.#field_name as i64).encode(&mut w)?;
145+
VarLong(self.#field_name as i64).write(w)?;
155146
});
156147
} else {
157148
fields_encoded.extend(quote! {
158-
self.#field_name.encode(&mut w)?;
149+
self.#field_name.write(w)?;
159150
});
160151
}
161152
}
@@ -170,19 +161,19 @@ pub fn derive_encode(input: TokenStream) -> TokenStream {
170161
.any(|attr| attr.meta.path().is_ident("varint"))
171162
{
172163
fields_encoded.extend(quote! {
173-
VarInt(self.#i as i32).encode(&mut w)?;
164+
VarInt(self.#i as i32).write(w)?;
174165
});
175166
} else if field
176167
.attrs
177168
.iter()
178169
.any(|attr| attr.meta.path().is_ident("varlong"))
179170
{
180171
fields_encoded.extend(quote! {
181-
VarLong(self.#i as i64).encode(&mut w)?;
172+
VarLong(self.#i as i64).write(w)?;
182173
});
183174
} else {
184175
fields_encoded.extend(quote! {
185-
self.#i.encode(&mut w)?;
176+
self.#i.write(w)?;
186177
});
187178
}
188179
}
@@ -191,8 +182,8 @@ pub fn derive_encode(input: TokenStream) -> TokenStream {
191182
}
192183

193184
quote! {
194-
impl #generics Encode for #name #generics #where_clause {
195-
fn encode(&self, mut w: impl std::io::Write) -> color_eyre::Result<()> {
185+
impl #generics Write for #name #generics #where_clause {
186+
fn encode(&self, w: &mut impl std::io::Write) -> crawlspace_proto::Result<()> {
196187
#fields_encoded
197188

198189
Ok(())
@@ -205,12 +196,12 @@ pub fn derive_encode(input: TokenStream) -> TokenStream {
205196
/// Automatically implements "straight-across" decoding for the given struct, i.e. fields are
206197
/// deserialized in order as is. Supports #[decode_as(type)] to deserialize according to a different type.
207198
/// uses TryInto to convert to the expected type where necessary.
208-
#[proc_macro_derive(Decode, attributes(decode_as))]
209-
pub fn derive_decode(input: TokenStream) -> TokenStream {
199+
#[proc_macro_derive(Read, attributes(decode_as))]
200+
pub fn derive_read(input: TokenStream) -> TokenStream {
210201
let input = parse_macro_input!(input as DeriveInput);
211202

212203
let syn::Data::Struct(data) = input.data else {
213-
panic!("Can only derive Decode on a struct");
204+
panic!("Can only derive Read on a struct");
214205
};
215206

216207
let name = input.ident;
@@ -223,8 +214,6 @@ pub fn derive_decode(input: TokenStream) -> TokenStream {
223214
let field_name = field.ident.expect("couldn't get ident for named field");
224215
let ty = field.ty;
225216

226-
let wrapped = format!("for field {field_name} in {name}");
227-
228217
if let Some(attr) = field
229218
.attrs
230219
.iter()
@@ -235,14 +224,11 @@ pub fn derive_decode(input: TokenStream) -> TokenStream {
235224
.expect("decode_as value must be a Path");
236225

237226
field_tokens.extend(quote! {
238-
#field_name: <#ty as Decode>::decode(r)
239-
.wrap_err(#wrapped)?
240-
.try_into()?,
227+
#field_name: <#ty as Read>::read(r)?.try_into()?,
241228
});
242229
} else {
243230
field_tokens.extend(quote! {
244-
#field_name: <#ty as Decode>::decode(r)
245-
.wrap_err(#wrapped)?,
231+
#field_name: <#ty as Read>::read(r)?,
246232
});
247233
}
248234
}
@@ -254,11 +240,9 @@ pub fn derive_decode(input: TokenStream) -> TokenStream {
254240
}
255241
Fields::Unnamed(fields) => {
256242
let mut field_tokens = proc_macro2::TokenStream::new();
257-
for (i, field) in fields.unnamed.into_iter().enumerate() {
243+
for field in fields.unnamed.into_iter() {
258244
let ty = field.ty;
259245

260-
let wrapped = format!("for field {i} in {name}");
261-
262246
if let Some(attr) = field
263247
.attrs
264248
.iter()
@@ -269,13 +253,12 @@ pub fn derive_decode(input: TokenStream) -> TokenStream {
269253
.expect("decode_as value must be a Path");
270254

271255
field_tokens.extend(quote! {
272-
<#ty as Decode>::decode(r)
273-
.wrap_err(#wrapped)?
256+
<#ty as Read>::read(r)?
274257
.try_into()?,
275258
});
276259
} else {
277260
field_tokens.extend(quote! {
278-
<#ty as Decode>::decode(r).wrap_err(#wrapped)?,
261+
<#ty as Read>::read(r)?,
279262
});
280263
}
281264
}
@@ -289,18 +272,12 @@ pub fn derive_decode(input: TokenStream) -> TokenStream {
289272
let struct_generics = input.generics;
290273
let where_clause = struct_generics.where_clause.clone();
291274

292-
let mut impl_generics = struct_generics.clone();
293-
if impl_generics.lifetimes().count() == 0 {
294-
impl_generics.params.push(parse_quote!('a));
295-
}
296-
297275
quote! {
298-
impl #impl_generics Decode #impl_generics for #name #struct_generics #where_clause {
299-
fn decode(r: &mut &'a [u8]) -> color_eyre::Result<Self>
276+
impl #struct_generics crawlspace_proto::Read for #name #struct_generics #where_clause {
277+
fn read(r: &mut impl std::io::Read) -> crawlspace_proto::Result<Self>
300278
where
301279
Self: Sized,
302280
{
303-
use color_eyre::eyre::WrapErr;
304281
Ok(#struct_tokens)
305282
}
306283
}

crawlspace-proto-1_8/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,9 @@ version = "0.1.0"
44
edition = "2024"
55

66
[dependencies]
7-
bytes = "1.10.1"
7+
crawlspace-macro = { path = "../crawlspace-macro" }
88
crawlspace-proto = { path = "../crawlspace-proto" }
9+
10+
bytes = "1.10.1"
11+
tokio = { version = "1.47.1", features = ["io-util", "net", "time"] }
12+
tracing = "0.1.41"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
use crawlspace_macro::{Packet, Read};
2+
use crawlspace_proto::{ConnectionState, PacketId::Numeric};
3+
4+
#[derive(Packet, Read)]
5+
#[packet(
6+
id = "Numeric(0x00)",
7+
state = "ConnectionState::Handshake",
8+
serverbound
9+
)]
10+
pub struct HandshakeS {}

crawlspace-proto-1_8/src/lib.rs

Lines changed: 103 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,36 +17,122 @@
1717
* <https://www.gnu.org/licenses/>.
1818
*/
1919

20-
use bytes::BytesMut;
20+
use std::time::Duration;
21+
22+
use bytes::{Buf, BytesMut};
2123
use crawlspace_proto::{
22-
Packet, Read, ServerboundPacket,
24+
ConnectionState, ErrorKind, Packet, Read, ServerboundPacket,
2325
datatypes::{VarInt, VariableNumber},
2426
};
27+
use handshake::HandshakeS;
28+
use tokio::{
29+
io::AsyncReadExt,
30+
net::tcp::{OwnedReadHalf, OwnedWriteHalf},
31+
};
32+
use tracing::{debug, warn};
33+
34+
mod handshake;
35+
36+
const BUF_SIZE: usize = 4096;
37+
const MAX_PACKET_SIZE: i32 = 2097152;
38+
39+
type Frame = (i32, BytesMut);
2540

2641
/// Minecraft versions 1.8-1.8.9
2742
/// Protocol version 47
28-
pub struct Protocol47<R, W> {
29-
reader: R,
30-
writer: W,
31-
bytebuf: BytesMut,
43+
pub struct Protocol47 {
44+
connection_state: ConnectionState,
45+
read_half: OwnedReadHalf,
46+
write_half: OwnedWriteHalf,
47+
read_buf: BytesMut,
3248
}
3349

34-
impl<R: std::io::Read, W: std::io::Write> Protocol47<R, W> {
35-
pub fn new(reader: R, writer: W) -> Self {
50+
impl Protocol47 {
51+
pub fn new(read_half: OwnedReadHalf, write_half: OwnedWriteHalf) -> Self {
3652
Self {
37-
reader,
38-
writer,
39-
bytebuf: BytesMut::new(),
53+
connection_state: ConnectionState::Handshake,
54+
read_half,
55+
write_half,
56+
read_buf: BytesMut::new(),
4057
}
4158
}
4259

43-
fn read_packet(&mut self) -> Result<Box<dyn ServerboundPacket>, crawlspace_proto::ErrorKind> {
44-
let len = VarInt::read(&mut self.reader)?;
60+
pub async fn await_packet<T: ServerboundPacket>(&mut self) -> crawlspace_proto::Result<T> {
61+
// TODO: skip decoding for frames we're not looking for
62+
loop {
63+
let frame = self.read_frame().await?;
64+
65+
if T::packet_id() == frame.0 {
66+
return Ok(T::read(&mut &frame.1[..])?);
67+
}
4568

46-
todo!();
69+
debug!(
70+
"discarding packet with id {} while awaiting {:?}",
71+
frame.0,
72+
T::packet_id()
73+
);
74+
}
4775
}
48-
}
4976

50-
impl<R: std::io::Read, W: std::io::Write> crawlspace_proto::Protocol for Protocol47<R, W> {
51-
fn handshake_player(&mut self) {}
77+
pub async fn read_frame(&mut self) -> crawlspace_proto::Result<Frame> {
78+
// TODO: maybe move this somewhere else? i don't know if a global timeout of 5 seconds per
79+
// packet is realistic but for testing it's chill i suppose
80+
tokio::time::timeout(Duration::from_secs(5), async move {
81+
loop {
82+
if let Some(frame) = self.try_read_next()? {
83+
return Ok(frame);
84+
};
85+
86+
// otherwise, keep reading the rest of the packet
87+
// (commented for my own sanity - these method names are awful)
88+
89+
// reserve more space
90+
self.read_buf.reserve(BUF_SIZE);
91+
// split into two parts - self.read_buf containing already read data
92+
// and a new buf to fill with new data (.len returns number of bytes already held,
93+
// not capacity)
94+
let mut buf = self.read_buf.split_off(self.read_buf.len());
95+
96+
// fills the remainder of the buf (just newly allocated space)
97+
if self.read_half.read_buf(&mut buf).await? == 0 {
98+
return Err(std::io::Error::from(std::io::ErrorKind::UnexpectedEof).into());
99+
}
100+
101+
// joins "bufs" back together (just moves end pointer)
102+
self.read_buf.unsplit(buf);
103+
}
104+
})
105+
.await
106+
.map_err(|_| ErrorKind::Timeout)?
107+
}
108+
109+
/// Ok(None) represents an incomplete but correctly formed packet
110+
fn try_read_next(&mut self) -> crawlspace_proto::Result<Option<Frame>> {
111+
let mut buf = &self.read_buf[..];
112+
113+
let len = VarInt::read(&mut buf)?;
114+
115+
if len.0 < 0 || len.0 > MAX_PACKET_SIZE {
116+
return Err(ErrorKind::InvalidData(format!(
117+
"Packet length {len} is out of bounds (min 0, max {MAX_PACKET_SIZE})"
118+
)));
119+
};
120+
121+
if buf.len() < len.0 as usize {
122+
// packet is incomplete, keep waiting
123+
return Ok(None);
124+
}
125+
126+
// TODO: use compression here
127+
self.read_buf.advance(len.len());
128+
let mut data = self.read_buf.split_to(len.0 as usize);
129+
buf = &data[..];
130+
131+
let packet_id = VarInt::read(&mut buf)?.0;
132+
133+
// advance to end of packet id
134+
data.advance(data.len() - buf.len());
135+
136+
Ok(Some((packet_id, data)))
137+
}
52138
}

0 commit comments

Comments
 (0)