mc_protocol 2.1.1

Rust implementation of Minecraft Java Edition protocol primitives: serialization, packet framing, encryption, and compression
Documentation
//! Tests for packet framing and the derive macro.

use mc_protocol::packet::{PacketId, RawPacket, UncompressedPacket};
use mc_protocol::ser::{Deserialize, Serialize};
use mc_protocol::varint::VarInt;
use std::io::Cursor;

// ---------------------------------------------------------------------------
// A concrete test packet using the derive macro
// ---------------------------------------------------------------------------

#[derive(mc_protocol::Packet, Debug, PartialEq)]
#[packet(0x00)]
struct Handshake {
    protocol_version: VarInt,
    server_address: String,
    server_port: u16,
    next_state: VarInt,
}

// ---------------------------------------------------------------------------
// Derive macro generates PACKET_ID const
// ---------------------------------------------------------------------------

#[test]
fn derive_packet_id_const() {
    assert_eq!(Handshake::PACKET_ID, 0x00);
}

#[test]
fn derive_packet_id_trait() {
    let hs = Handshake {
        protocol_version: VarInt(765),
        server_address: "localhost".into(),
        server_port: 25565,
        next_state: VarInt(2),
    };
    assert_eq!(hs.packet_id(), 0x00);
}

// ---------------------------------------------------------------------------
// Serialize + Deserialize generated by derive macro
// ---------------------------------------------------------------------------

#[test]
fn derive_serialize_deserialize_round_trip() {
    let original = Handshake {
        protocol_version: VarInt(765),
        server_address: "play.example.com".into(),
        server_port: 25565,
        next_state: VarInt(2),
    };

    let mut buf = Vec::new();
    original.serialize(&mut buf).unwrap();

    let decoded = Handshake::deserialize(&mut Cursor::new(&buf)).unwrap();
    assert_eq!(decoded.protocol_version, original.protocol_version);
    assert_eq!(decoded.server_address, original.server_address);
    assert_eq!(decoded.server_port, original.server_port);
    assert_eq!(decoded.next_state, original.next_state);
}

// ---------------------------------------------------------------------------
// UncompressedPacket::from_packet
// ---------------------------------------------------------------------------

#[test]
fn uncompressed_packet_from_packet() {
    let hs = Handshake {
        protocol_version: VarInt(765),
        server_address: "localhost".into(),
        server_port: 25565,
        next_state: VarInt(2),
    };
    let packet = UncompressedPacket::from_packet(&hs).unwrap();
    assert_eq!(packet.packet_id, 0x00);
    // Payload should not be empty
    assert!(!packet.payload.is_empty());
}

// ---------------------------------------------------------------------------
// UncompressedPacket -> RawPacket -> UncompressedPacket
// ---------------------------------------------------------------------------

#[test]
fn uncompressed_to_raw_and_back() {
    let up = UncompressedPacket::new(0x00, vec![0x01, 0x02, 0x03]);
    let raw = up.to_raw_packet().unwrap();

    // raw.data = [packet_id_varint][payload]
    assert!(!raw.data.is_empty());

    let decoded = raw.as_uncompressed().unwrap();
    assert_eq!(decoded.packet_id, 0x00);
    assert_eq!(decoded.payload, &[0x01, 0x02, 0x03]);
}

// ---------------------------------------------------------------------------
// RawPacket length prefix
// ---------------------------------------------------------------------------

#[test]
fn raw_packet_write_and_read_sync() {
    let raw = RawPacket::new(vec![0xAA, 0xBB, 0xCC]);
    let mut buf = Vec::new();
    raw.write_sync(&mut buf).unwrap();

    // The length prefix is VarInt(3) = [0x03]
    assert_eq!(buf[0], 0x03);

    let decoded = RawPacket::read_sync(&mut Cursor::new(&buf)).unwrap();
    assert_eq!(decoded.data, raw.data);
}

#[test]
fn raw_packet_empty_payload() {
    let raw = RawPacket::new(vec![]);
    let mut buf = Vec::new();
    raw.write_sync(&mut buf).unwrap();
    let decoded = RawPacket::read_sync(&mut Cursor::new(&buf)).unwrap();
    assert_eq!(decoded.data, raw.data);
}

// ---------------------------------------------------------------------------
// Full packet round-trip: struct -> bytes -> struct
// ---------------------------------------------------------------------------

#[test]
fn full_packet_round_trip() {
    let original = Handshake {
        protocol_version: VarInt(765),
        server_address: "mc.example.com".into(),
        server_port: 25565,
        next_state: VarInt(1),
    };

    // Encode
    let up = UncompressedPacket::from_packet(&original).unwrap();
    let raw = up.to_raw_packet().unwrap();
    let mut wire = Vec::new();
    raw.write_sync(&mut wire).unwrap();

    // Decode
    let decoded_raw = RawPacket::read_sync(&mut Cursor::new(&wire)).unwrap();
    let decoded_up = decoded_raw.as_uncompressed().unwrap();
    assert_eq!(decoded_up.packet_id, Handshake::PACKET_ID);
    let decoded_hs = decoded_up.deserialize_payload::<Handshake>().unwrap();

    assert_eq!(decoded_hs.protocol_version, original.protocol_version);
    assert_eq!(decoded_hs.server_address, original.server_address);
    assert_eq!(decoded_hs.server_port, original.server_port);
    assert_eq!(decoded_hs.next_state, original.next_state);
}

// ---------------------------------------------------------------------------
// Packet derive without #[packet(ID)] — for nested field structs
// ---------------------------------------------------------------------------

#[derive(mc_protocol::Packet, Debug, PartialEq)]
struct Item {
    id: VarInt,
    count: u8,
}

#[derive(mc_protocol::Packet, Debug, PartialEq)]
#[packet(0x10)]
struct Inventory {
    items: Vec<Item>,
}

#[test]
fn nested_struct_without_packet_id_round_trips() {
    let inv = Inventory {
        items: vec![
            Item {
                id: VarInt(1),
                count: 64,
            },
            Item {
                id: VarInt(2),
                count: 1,
            },
        ],
    };

    let mut buf = Vec::new();
    inv.serialize(&mut buf).unwrap();

    let decoded = Inventory::deserialize(&mut Cursor::new(&buf)).unwrap();
    assert_eq!(decoded, inv);
}

#[test]
fn nested_struct_has_no_packet_id_const() {
    // Item does not have #[packet(...)], so PACKET_ID must not exist and
    // PacketId must not be implemented. If either were generated, this test
    // file would fail to compile due to the outer-packet assertions below
    // still needing to work. Sanity-check that Inventory still has an ID.
    assert_eq!(Inventory::PACKET_ID, 0x10);
}

// ---------------------------------------------------------------------------
// Async tests
// ---------------------------------------------------------------------------

#[cfg(feature = "async")]
mod async_tests {
    use super::*;

    #[tokio::test]
    async fn raw_packet_write_read_async() {
        let raw = RawPacket::new(vec![0x01, 0x02, 0x03, 0x04]);
        let mut buf = Vec::new();
        raw.write_async(&mut buf).await.unwrap();

        let decoded = RawPacket::read_async(&mut Cursor::new(&buf)).await.unwrap();
        assert_eq!(decoded.data, raw.data);
    }

    #[tokio::test]
    async fn uncompressed_packet_write_async() {
        let original = Handshake {
            protocol_version: VarInt(765),
            server_address: "localhost".into(),
            server_port: 25565,
            next_state: VarInt(2),
        };

        let up = UncompressedPacket::from_packet(&original).unwrap();
        let mut buf = Vec::new();
        up.write_async(&mut buf).await.unwrap();

        let raw = RawPacket::read_async(&mut Cursor::new(&buf)).await.unwrap();
        let decoded_up = raw.as_uncompressed().unwrap();
        let decoded_hs = decoded_up.deserialize_payload::<Handshake>().unwrap();

        assert_eq!(decoded_hs.server_address, original.server_address);
    }
}