java_protocol

package
v0.0.0-...-075a84e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 17, 2026 License: MIT Imports: 12 Imported by: 17

README

Java Protocol

This package implements the low-level Minecraft: Java Edition protocol, providing primitives for reading and writing packets over TCP connections.

Overview

The Minecraft server accepts connections from TCP clients and communicates using packets. A packet is a sequence of bytes where the meaning depends on both its packet ID and the current connection state.

┌─────────────────────────────────────────────────────────────────────────────┐
│                           Packet Structure                                  │
└─────────────────────────────────────────────────────────────────────────────┘

Without compression:
┌──────────────────┬──────────────────┬──────────────────────────────────────┐
│  Packet Length   │    Packet ID     │                Data                  │
│    (VarInt)      │    (VarInt)      │            (ByteArray)               │
└──────────────────┴──────────────────┴──────────────────────────────────────┘

With compression (when size >= threshold):
┌──────────────────┬──────────────────┬────────────────────────────────────────┐
│  Packet Length   │   Data Length    │    Compressed (Packet ID + Data)       │
│    (VarInt)      │    (VarInt)      │              (zlib)                    │
└──────────────────┴──────────────────┴────────────────────────────────────────┘

With compression (when size < threshold):
┌──────────────────┬──────────────────┬──────────────────┬─────────────────────┐
│  Packet Length   │   Data Length    │    Packet ID     │        Data         │
│    (VarInt)      │   (VarInt = 0)   │    (VarInt)      │     (ByteArray)     │
└──────────────────┴──────────────────┴──────────────────┴─────────────────────┘

Connection States

The protocol has 5 states, with automatic transitions:

                    ┌─────────────┐
                    │  Handshake  │  (Initial state)
                    └──────┬──────┘
                           │
              ┌────────────┴────────────┐
              │                         │
              ▼                         ▼
       ┌─────────────┐           ┌─────────────┐
       │   Status    │           │    Login    │
       │  (ping/SLP) │           │   (auth)    │
       └─────────────┘           └──────┬──────┘
                                        │
                                        ▼
                                 ┌─────────────┐
                                 │Configuration│
                                 │  (1.20.2+)  │
                                 └──────┬──────┘
                                        │
                                        ▼
                                 ┌─────────────┐
                                 │    Play     │
                                 │  (in-game)  │
                                 └─────────────┘

Package Structure

conn.go - Encrypted Connection Wrapper

Wraps net.Conn with transparent AES/CFB8 encryption/decryption:

// create connection wrapper
conn := java_protocol.NewConn(netConn)

// enable encryption (after key exchange during login)
conn.Encryption().Enable(sharedSecret)

// read/Write automatically encrypt/decrypt when enabled
conn.Read(buf)   // decrypts if enabled
conn.Write(data) // encrypts if enabled
packet.go - Packet Types

Defines Packet interface and WirePacket struct:

// Packet is the interface all typed packets implement
// Each packet knows its ID, state, and direction
type Packet interface {
    ID() ns.VarInt
    State() State
    Bound() Bound
    Read(buf *ns.PacketBuffer) error
    Write(buf *ns.PacketBuffer) error
}

// WirePacket is the raw wire format (what actually goes over the network)
type WirePacket struct {
    Length   ns.VarInt
    PacketID ns.VarInt
    Data     ns.ByteArray
}

// Example packet implementation
type LoginStartPacket struct {
    Username ns.String
}

func (p *LoginStartPacket) ID() ns.VarInt   { return 0x00 }
func (p *LoginStartPacket) State() State    { return StateLogin }
func (p *LoginStartPacket) Bound() Bound    { return C2S }
func (p *LoginStartPacket) Read(buf *ns.PacketBuffer) error {
    var err error
    p.Username, err = buf.ReadString(16)
    return err
}
func (p *LoginStartPacket) Write(buf *ns.PacketBuffer) error {
    return buf.WriteString(p.Username)
}

// Convert to wire format, then write to connection
wire, err := java_protocol.ToWire(&LoginStartPacket{Username: "Player"})
err = wire.WriteTo(conn, threshold) // handles compression automatically
tcp_client.go - Protocol Client

Minimal client for connecting to Minecraft servers:

client := java_protocol.NewTCPClient()

// connect (also handles SRV record resolution)
host, port, err := client.Connect("example.com")

// set protocol state
client.SetState(java_protocol.StateLogin)

// enable compression after server requests it
client.SetCompressionThreshold(256)

// write packets (packet knows its own state/bound/id)
client.WritePacket(&HandshakePacket{...})

// read raw wire packet
wire, err := client.ReadWirePacket()

// deserialize using generics (type-safe, recommended)
login, err := java_protocol.ReadPacket[LoginSuccessPacket](wire)

// or deserialize into a pre-allocated struct
var loginSuccess LoginSuccessPacket
err = wire.ReadInto(&loginSuccess)

Protocol States

State Value Description
StateHandshake 0 Initial state, client sends intention
StateStatus 1 Server List Ping (SLP)
StateLogin 2 Authentication and encryption
StateConfiguration 3 Server configuration (1.20.2+)
StatePlay 4 Gameplay packets

Packet Direction

Direction Constant Description
Serverbound C2S Client → Server
Clientbound S2C Server → Client

Compression

Compression is enabled by the server via Set Compression packet during login:

// After receiving Set Compression packet
client.SetCompressionThreshold(threshold)

// Threshold behavior:
// -1: compression disabled
// 0+: packets >= threshold bytes are zlib compressed

Encryption

Encryption is enabled during the login sequence after key exchange:

// After completing Encryption Request/Response handshake
sharedSecret := generateSharedSecret()
client.Conn().Encryption().Enable(sharedSecret)

// All subsequent packets are encrypted with AES/CFB8

Address Resolution

The Connect method automatically resolves Minecraft server addresses:

  1. If port is specified (host:port), uses it directly
  2. Otherwise, looks up SRV record _minecraft._tcp.<host>
  3. Falls back to default port 25565
// All equivalent:
client.Connect("mc.example.com")        // uses SRV or :25565
client.Connect("mc.example.com:25565")  // explicit port
client.Connect("play.hypixel.net")      // SRV → mc.hypixel.net:25565

Debug Logging

Enable debug logging to trace packet I/O:

client.EnableDebug(true)
client.SetLogger(log.New(os.Stdout, "[MC] ", log.LstdFlags))

// Output:
// [MC] -> send: state=2 bound=0 id=0x00 len=23 bytes=...
// [MC] <- recv: length=42
// [MC] <- recv: compressed id=0x02 data_len=38

Data Types

The net_structures subpackage provides Minecraft protocol data types:

Type Go Type Description
VarInt int32 Variable-length 32-bit integer
VarLong int64 Variable-length 64-bit integer
String string UTF-8 with VarInt length prefix
Boolean bool Single byte (0x00/0x01)
Int8-Int64 int8-int64 Big-endian signed integers
Uint8-Uint16 uint8-uint16 Big-endian unsigned integers
Float32/Float64 float32/float64 IEEE 754 floats
Position struct Block coordinates packed into 64 bits
UUID [16]byte 128-bit unique identifier
Angle uint8 Rotation (1/256 of full turn)
Identifier string Namespaced ID (e.g., minecraft:stone)
ByteArray []byte Raw bytes with VarInt length prefix

Implementing Packets

Define packets by implementing the Packet interface:

type HandshakePacket struct {
    ProtocolVersion ns.VarInt
    ServerAddress   ns.String
    ServerPort      ns.Uint16
    NextState       ns.VarInt
}

// Metadata - each packet knows its ID, state, and direction
func (p *HandshakePacket) ID() ns.VarInt   { return 0x00 }
func (p *HandshakePacket) State() State    { return StateHandshake }
func (p *HandshakePacket) Bound() Bound    { return C2S }

func (p *HandshakePacket) Read(buf *ns.PacketBuffer) error {
    var err error
    p.ProtocolVersion, err = buf.ReadVarInt()
    if err != nil { return err }
    p.ServerAddress, err = buf.ReadString(255)
    if err != nil { return err }
    p.ServerPort, err = buf.ReadUint16()
    if err != nil { return err }
    p.NextState, err = buf.ReadVarInt()
    return err
}

func (p *HandshakePacket) Write(buf *ns.PacketBuffer) error {
    if err := buf.WriteVarInt(p.ProtocolVersion); err != nil { return err }
    if err := buf.WriteString(p.ServerAddress); err != nil { return err }
    if err := buf.WriteUint16(p.ServerPort); err != nil { return err }
    return buf.WriteVarInt(p.NextState)
}

Reading Packets

Three ways to read packets:

// 1. Generic function (recommended) - type-safe, returns concrete type
wire, _ := client.ReadWirePacket()
login, err := java_protocol.ReadPacket[LoginSuccessPacket](wire)

// 2. ReadInto - fill an existing struct
var login LoginSuccessPacket
wire, _ := client.ReadWirePacket()
err := wire.ReadInto(&login)

// 3. Manual - read wire packet, switch on ID
wire, _ := client.ReadWirePacket()
switch wire.PacketID {
case 0x00:
    var disconnect DisconnectPacket
    wire.ReadInto(&disconnect)
case 0x02:
    var success LoginSuccessPacket
    wire.ReadInto(&success)
}

Packet Size Limits

  • Maximum packet size: 2,097,151 bytes (2^21 - 1)
  • Maximum uncompressed serverbound size: 8,388,608 bytes (2^23)
  • Packet length field: max 3 bytes

References

Documentation

Overview

The `java_protocol` package contains the core structs and functions for working with the Java Edition protocol.

> The Minecraft server accepts connections from TCP clients and communicates with them using packets. A packet is a sequence of bytes sent over the TCP connection (note: see `net_structures.ByteArray`). The meaning of a packet depends both on its packet ID and the current state of the connection (note: each state has its own packet ID counter, so packets in different states can have the same packet ID). The initial state of each connection is Handshaking, and state is switched using the packets 'Handshake' and 'Login Success'."

Packet format:

> Packets cannot be larger than (2^21) − 1 or 2 097 151 bytes (the maximum that can be sent in a 3-byte VarInt). Moreover, the length field must not be longer than 3 bytes, even if the encoded value is within the limit. Unnecessarily long encodings at 3 bytes or below are still allowed. For compressed packets, this applies to the Packet Length field, i. e. the compressed length.

See https://minecraft.wiki/w/Java_Edition_protocol/Packets

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ReadPacket

func ReadPacket[T any, PT interface {
	*T
	Packet
}](wire *WirePacket) (PT, error)

ReadPacket deserializes a WirePacket into a typed Packet using generics. This provides type-safe packet reading without manual type assertions.

Example:

wire, _ := client.ReadWirePacket()
login, err := ReadPacket[LoginSuccessPacket](wire)

Types

type Bound

type Bound uint8

Bound is the direction that the packet is going.

Serverbound: Client -> Server (C2S)

Clientbound: Server -> Client (S2C)

const (
	// Client -> Server (C2S, serverbound)
	C2S Bound = iota
	// Server -> Client (S2C, clientbound)
	S2C
)

type Conn

type Conn struct {
	// contains filtered or unexported fields
}

Conn wraps a net.Conn with optional encryption. It implements io.Reader and io.Writer, transparently handling encryption/decryption when enabled.

func NewConn

func NewConn(conn net.Conn) *Conn

NewConn creates a new Conn wrapping the given net.Conn.

func (*Conn) Close

func (c *Conn) Close() error

Close closes the underlying connection.

func (*Conn) Encryption

func (c *Conn) Encryption() *crypto.Encryption

Encryption returns the encryption instance for configuration.

func (*Conn) LocalAddr

func (c *Conn) LocalAddr() net.Addr

LocalAddr returns the local network address.

func (*Conn) NetConn

func (c *Conn) NetConn() net.Conn

NetConn returns the underlying net.Conn.

func (*Conn) Read

func (c *Conn) Read(p []byte) (int, error)

Read implements io.Reader. If encryption is enabled, data is decrypted.

func (*Conn) RemoteAddr

func (c *Conn) RemoteAddr() net.Addr

RemoteAddr returns the remote network address.

func (*Conn) Write

func (c *Conn) Write(p []byte) (int, error)

Write implements io.Writer. If encryption is enabled, data is encrypted.

type Packet

type Packet interface {
	// ID returns the packet ID for this packet type.
	ID() ns.VarInt
	// State returns the protocol state this packet belongs to.
	State() State
	// Bound returns the direction of this packet (C2S or S2C).
	Bound() Bound
	// Read deserializes the packet data from the buffer.
	Read(buf *ns.PacketBuffer) error
	// Write serializes the packet data to the buffer.
	Write(buf *ns.PacketBuffer) error
}

Packet is the interface that all typed packet implementations must satisfy. Each packet knows its ID, protocol state, and direction.

type State

type State uint8

State is the phase that the packet is in (handshake, status, login, configuration, play). This is not sent over network (server and client automatically transition phases).

const (
	StateHandshake State = iota
	StateStatus
	StateLogin
	StateConfiguration
	StatePlay
)

type TCPClient

type TCPClient struct {
	// contains filtered or unexported fields
}

TCPClient is a Minecraft protocol client connection.

func NewTCPClient

func NewTCPClient() *TCPClient

NewTCPClient creates a new TCP client.

func (*TCPClient) Close

func (c *TCPClient) Close() error

Close closes the connection.

func (*TCPClient) CompressionThreshold

func (c *TCPClient) CompressionThreshold() int

CompressionThreshold returns the current compression threshold.

func (*TCPClient) Conn

func (c *TCPClient) Conn() *Conn

Conn returns the underlying Conn.

func (*TCPClient) Connect

func (c *TCPClient) Connect(address string) (host string, port string, err error)

Connect connects to a Minecraft server. The address can be "host", "host:port", or will use SRV records if no port specified. Returns the resolved host and port.

func (*TCPClient) EnableDebug

func (c *TCPClient) EnableDebug(enabled bool)

EnableDebug enables or disables debug logging.

func (*TCPClient) ReadWirePacket

func (c *TCPClient) ReadWirePacket() (*WirePacket, error)

ReadWirePacket reads a raw WirePacket from the connection.

func (*TCPClient) SetCompressionThreshold

func (c *TCPClient) SetCompressionThreshold(threshold int)

SetCompressionThreshold enables compression with the given threshold. Use -1 to disable compression.

func (*TCPClient) SetConn

func (c *TCPClient) SetConn(conn *Conn)

SetConn sets the underlying connection (for testing or server-accepted connections).

func (*TCPClient) SetLogger

func (c *TCPClient) SetLogger(l *log.Logger)

SetLogger sets a custom logger.

func (*TCPClient) SetState

func (c *TCPClient) SetState(state State)

SetState sets the protocol state.

func (*TCPClient) State

func (c *TCPClient) State() State

State returns the current protocol state.

func (*TCPClient) WritePacket

func (c *TCPClient) WritePacket(p Packet) error

WritePacket writes a typed Packet to the connection. Safe for concurrent use from multiple goroutines.

func (*TCPClient) WriteWirePacket

func (c *TCPClient) WriteWirePacket(pkt *WirePacket) error

WriteWirePacket writes a raw WirePacket to the connection. Safe for concurrent use from multiple goroutines.

type WirePacket

type WirePacket struct {
	// Length is the total length of (PacketID + Data) as read from the wire.
	Length ns.VarInt
	// PacketID is the packet identifier.
	PacketID ns.VarInt
	// Data is the raw payload bytes (without the packet ID).
	Data ns.ByteArray
}

WirePacket represents the raw packet as it appears on the wire. It contains only wire-level data without typed field information.

func ReadWirePacketFrom

func ReadWirePacketFrom(r io.Reader, compressionThreshold int) (*WirePacket, error)

ReadWirePacketFrom reads a WirePacket from the given reader. Handles both compressed and uncompressed packet formats based on compressionThreshold. Use compressionThreshold < 0 to disable compression.

func ToWire

func ToWire(p Packet) (*WirePacket, error)

ToWire converts a typed Packet to a WirePacket by serializing its data. The resulting WirePacket can then be written to a connection via WriteTo() or converted to bytes via ToBytes().

func (*WirePacket) Clone

func (w *WirePacket) Clone() *WirePacket

Clone returns a deep copy of the wire packet.

func (*WirePacket) ReadInto

func (w *WirePacket) ReadInto(p Packet) error

ReadInto deserializes the wire packet's raw data into a typed Packet. Returns an error if the packet ID doesn't match.

func (*WirePacket) WriteTo

func (w *WirePacket) WriteTo(writer io.Writer, compressionThreshold int) error

WriteTo writes the WirePacket to the given writer. Handles both compressed and uncompressed packet formats based on compressionThreshold. Use compressionThreshold < 0 to disable compression.

Compression behavior (per Minecraft protocol):

  • If size >= threshold: packet is zlib compressed
  • If size < threshold: packet is sent uncompressed (with Data Length = 0)
  • The vanilla server rejects compressed packets smaller than the threshold

See https://minecraft.wiki/w/Java_Edition_protocol/Packets#Packet_format

Directories

Path Synopsis
These types handle common patterns like length-prefixed arrays, boolean-prefixed optionals, and bit sets.
These types handle common patterns like length-prefixed arrays, boolean-prefixed optionals, and bit sets.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL