// Package protocol implements the wire-level primitives of the Minecraft // Java Edition protocol (version 26.1.2, protocol 775). // // All multi-byte numeric fields are big-endian. Length-prefixed and // frequently-used integers use the LEB128-style VarInt/VarLong encoding. package protocol import ( "errors" "io" ) // Protocol constants for the targeted Minecraft version. const ( // ProtocolVersion is the handshake protocol number for 26.1.2. ProtocolVersion = 775 // GameVersion is the human-readable version string. GameVersion = "26.1.2" ) // State is a connection state as negotiated during the handshake. The numeric // values of Status/Login match the "next state" field of the handshake packet. type State int const ( StateHandshaking State = iota StateStatus StateLogin StateConfiguration StatePlay ) func (s State) String() string { switch s { case StateHandshaking: return "handshaking" case StateStatus: return "status" case StateLogin: return "login" case StateConfiguration: return "configuration" case StatePlay: return "play" default: return "unknown" } } // Protocol limits guarding against malicious or malformed input. const ( // MaxVarIntLen is the maximum number of bytes a 32-bit VarInt may occupy. MaxVarIntLen = 5 // MaxVarLongLen is the maximum number of bytes a 64-bit VarLong may occupy. MaxVarLongLen = 10 // MaxStringLen bounds decoded strings to the protocol's 32767-char limit. MaxStringLen = 32767 // MaxPacketSize bounds a single uncompressed packet body. MaxPacketSize = 2 * 1024 * 1024 ) var ( // ErrVarIntTooBig is returned when a VarInt/VarLong exceeds its byte limit. ErrVarIntTooBig = errors.New("protocol: varint is too big") // ErrStringTooLong is returned when a string exceeds MaxStringLen. ErrStringTooLong = errors.New("protocol: string too long") // ErrInvalidString is returned for protocol strings that are not UTF-8. ErrInvalidString = errors.New("protocol: invalid UTF-8 string") // ErrPacketTooLarge is returned when a packet length exceeds MaxPacketSize. ErrPacketTooLarge = errors.New("protocol: packet too large") // ErrBadCompression is returned when a frame violates the negotiated // compression threshold or its stream does not match the declared length. ErrBadCompression = errors.New("protocol: invalid compressed packet") ) // ReadVarInt reads a 32-bit VarInt from r, returning the value and the number // of bytes consumed. func ReadVarInt(r io.ByteReader) (value int32, n int, err error) { var result uint32 for i := 0; i < MaxVarIntLen; i++ { b, e := r.ReadByte() if e != nil { return 0, i, e } result |= uint32(b&0x7F) << (7 * i) if b&0x80 == 0 { return int32(result), i + 1, nil } } return 0, MaxVarIntLen, ErrVarIntTooBig } // ReadVarLong reads a 64-bit VarLong from r. func ReadVarLong(r io.ByteReader) (value int64, n int, err error) { var result uint64 for i := 0; i < MaxVarLongLen; i++ { b, e := r.ReadByte() if e != nil { return 0, i, e } result |= uint64(b&0x7F) << (7 * i) if b&0x80 == 0 { return int64(result), i + 1, nil } } return 0, MaxVarLongLen, ErrVarIntTooBig } // AppendVarInt encodes v as a VarInt and appends it to dst. func AppendVarInt(dst []byte, v int32) []byte { u := uint32(v) for { if u&^0x7F == 0 { return append(dst, byte(u)) } dst = append(dst, byte(u&0x7F)|0x80) u >>= 7 } } // AppendVarLong encodes v as a VarLong and appends it to dst. func AppendVarLong(dst []byte, v int64) []byte { u := uint64(v) for { if u&^uint64(0x7F) == 0 { return append(dst, byte(u)) } dst = append(dst, byte(u&0x7F)|0x80) u >>= 7 } } // VarIntLen returns the number of bytes the VarInt encoding of v occupies. func VarIntLen(v int32) int { u := uint32(v) n := 1 for u&^0x7F != 0 { u >>= 7 n++ } return n }