Skip to content

[1.21] (valence_protocol) LoginHelloC2s fails to decode valid packets #700

Description

@bcheidemann

Valence Version

2e4bc6e

What You Did

  1. Establish TCP listener on default MC port.
  2. Accept connection from 1.21.4 vanilla client
  3. Read data from TCP stream into buffer
  4. Queue slice into PacketDecoder
  5. Get next frame with try_next_packet
  6. Try decode LoginHelloC2s
Playground
use std::time::Instant;
use tokio::net::{TcpListener, TcpStream};
use tracing::{error, info, warn};
use tracing_subscriber;
use valence_protocol::PacketDecoder;
use valence_protocol::packets::login::LoginHelloC2s;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt::init();

    let listener = TcpListener::bind("0.0.0.0:25565").await?;
    info!("Listening on port 25565");

    loop {
        match listener.accept().await {
            Ok((socket, addr)) => {
                info!("New connection from {}", addr);
                tokio::spawn(handle_connection(socket, addr.to_string()));
            }
            Err(e) => {
                error!("Failed to accept connection: {}", e);
            }
        }
    }
}

async fn handle_connection(socket: TcpStream, addr: String) {
    let start_time = Instant::now();
    info!("Connection established with {} at {:?}", addr, start_time);

    loop {
        socket.readable().await.unwrap_or_else(|e| {
            warn!("Error waiting for socket readability: {}", e);
        });

        let mut decoder = PacketDecoder::new();
        let mut buf = [0; 1024];
        match socket.try_read(&mut buf) {
            Ok(0) => {
                let duration = start_time.elapsed();
                info!(
                    "Connection from {} closed after {:?} ({:.3} seconds)",
                    addr,
                    duration,
                    duration.as_secs_f64()
                );
                break;
            }
            Ok(n) => {
                info!("Received {} bytes from {}", n, addr);

                decoder.queue_slice(&buf[0..n]);

                while let Ok(Some(frame)) = decoder.try_next_packet() {
                    match frame.decode::<LoginHelloC2s>() {
                        Ok(login_packet) => {
                            info!("Bingo! Received login from: {}", login_packet.username)
                        }
                        Err(err) => info!("Not LoginHelloC2s: {err}"),
                    }
                }
            }
            Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
                continue;
            }
            Err(e) => {
                let duration = start_time.elapsed();
                warn!(
                    "Connection from {} error after {:?} ({:.3} seconds): {}",
                    addr,
                    duration,
                    duration.as_secs_f64(),
                    e
                );
                break;
            }
        }
    }
}

What Went Wrong

Expected Result

2025-08-28T14:08:46.987550Z  INFO mcprox: Listening on port 25565
2025-08-28T14:08:49.420192Z  INFO mcprox: New connection from 127.0.0.1:51014
2025-08-28T14:08:49.420360Z  INFO mcprox: Connection established with 127.0.0.1:51014 at Instant { tv_sec: 6612, tv_nsec: 740116261 }
2025-08-28T14:08:49.420896Z  INFO mcprox: Received 77 bytes from 127.0.0.1:51014
2025-08-28T14:08:49.420933Z  INFO mcprox: Not LoginHelloC2s: failed to decode field `username` in `LoginHelloC2s`
2025-08-28T14:08:49.421136Z  INFO mcprox: Received 27 bytes from 127.0.0.1:51014
2025-08-28T14:08:49.421155Z  INFO mcprox: Bingo! Received login from: rohedin4
2025-08-28T14:08:52.680779Z  INFO mcprox: Connection from 127.0.0.1:51014 closed after 3.260432602s (3.260 seconds)

Actual Result

2025-08-28T14:08:23.785379Z  INFO mcprox: Listening on port 25565
2025-08-28T14:08:26.259271Z  INFO mcprox: New connection from 127.0.0.1:60720
2025-08-28T14:08:26.259399Z  INFO mcprox: Connection established with 127.0.0.1:60720 at Instant { tv_sec: 6589, tv_nsec: 579175778 }
2025-08-28T14:08:26.259655Z  INFO mcprox: Received 77 bytes from 127.0.0.1:60720
2025-08-28T14:08:26.259709Z  INFO mcprox: Not LoginHelloC2s: failed to decode field `username` in `LoginHelloC2s`
2025-08-28T14:08:26.259732Z  INFO mcprox: Received 27 bytes from 127.0.0.1:60720
2025-08-28T14:08:26.259747Z  INFO mcprox: Not LoginHelloC2s: failed to decode field `profile_id` in `LoginHelloC2s`
2025-08-28T14:08:31.326701Z  INFO mcprox: Connection from 127.0.0.1:60720 closed after 5.067285718s (5.067 seconds)

Additional Information

The Minecraft Wiki indicates that the profile ID is not optional. However, the LoginHelloC2s struct is implemented with an Option<Uuid>:

#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(state = PacketState::Login)]
pub struct LoginHelloC2s<'a> {
    pub username: Bounded<&'a str, 16>,
    pub profile_id: Option<Uuid>,
}

Replacing Option<Uuid> with Uuid results in successfully decoding packets from the 1.20.4 client.

-    pub profile_id: Option<Uuid>,
+    pub profile_id: Uuid,

The issue seems to be in the Decode implementation for Option<T>:

impl<'a, T: Decode<'a>> Decode<'a> for Option<T> {
    fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> {
        Ok(if bool::decode(r)? {
            Some(T::decode(r)?)
        } else {
            None
        })
    }
}

This essentially expects either [username]1[profile_id] or [username]0. However, the actual packet looks like this: [username][profile_id].

I'm not sure if there is a version of MC which doesn't send the profile_id, so I'm not sure how this should be fixed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtriageBug has not been confirmed as real issue yet

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions