Docker auto-scale and asleep motd status (#488)
This commit is contained in:
+23
-1
@@ -58,7 +58,29 @@ func ReadPacket(reader *bufio.Reader, addr net.Addr, state State) (*Packet, erro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
packet.Data = remainder.Bytes()
|
||||
// For status state, decode based on packet ID:
|
||||
// - 0x00 Status Request: no payload
|
||||
// - 0x01 Ping: 8-byte long payload
|
||||
if state == StateStatus {
|
||||
switch packet.PacketID {
|
||||
case PacketIdStatusRequest:
|
||||
// no payload
|
||||
packet.Data = nil
|
||||
case PacketIdPingRequest:
|
||||
timestamp, err := ReadLong(remainder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
packet.Data = &PingPayload{
|
||||
Timestamp: timestamp,
|
||||
}
|
||||
default:
|
||||
// unknown in status state; keep raw
|
||||
packet.Data = remainder.Bytes()
|
||||
}
|
||||
} else {
|
||||
packet.Data = remainder.Bytes()
|
||||
}
|
||||
|
||||
logrus.
|
||||
WithField("client", addr).
|
||||
|
||||
@@ -79,6 +79,13 @@ const (
|
||||
PacketIdHandshake = 0x00
|
||||
PacketIdLogin = 0x00 // during StateLogin
|
||||
PacketIdLegacyServerListPing = 0xFE
|
||||
PacketIdStatusRequest = 0x00
|
||||
PacketIdPingRequest = 0x01
|
||||
)
|
||||
|
||||
const (
|
||||
PacketIdStatusResponse = 0x00
|
||||
PackedIdPongResponse = 0x01
|
||||
)
|
||||
|
||||
type Handshake struct {
|
||||
@@ -106,6 +113,30 @@ type LegacyServerListPing struct {
|
||||
ServerPort uint16
|
||||
}
|
||||
|
||||
// StatusResponse is a minimal structure for the status JSON
|
||||
type StatusResponse struct {
|
||||
Version struct {
|
||||
Name string `json:"name"`
|
||||
Protocol int `json:"protocol"`
|
||||
} `json:"version"`
|
||||
Players struct {
|
||||
Max int `json:"max"`
|
||||
Online int `json:"online"`
|
||||
Sample []struct {
|
||||
Name string `json:"name"`
|
||||
ID string `json:"id"`
|
||||
} `json:"sample,omitempty"`
|
||||
} `json:"players"`
|
||||
Description map[string]interface{} `json:"description"`
|
||||
Favicon string `json:"favicon,omitempty"`
|
||||
EnforcesSecureChat *bool `json:"enforcesSecureChat,omitempty"`
|
||||
}
|
||||
|
||||
// PingPayload represents the status ping payload (packet 0x01)
|
||||
type PingPayload struct {
|
||||
Timestamp int64
|
||||
}
|
||||
|
||||
type ByteReader interface {
|
||||
ReadByte() (byte, error)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
package mcproto
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
// WriteVarInt writes a VarInt (Minecraft format) to w
|
||||
func WriteVarInt(w io.Writer, value int32) error {
|
||||
var buf [5]byte
|
||||
i := 0
|
||||
v := uint32(value)
|
||||
for {
|
||||
temp := byte(v & 0x7F)
|
||||
v >>= 7
|
||||
if v != 0 {
|
||||
temp |= 0x80
|
||||
}
|
||||
buf[i] = temp
|
||||
i++
|
||||
if v == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
_, err := w.Write(buf[:i])
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteString writes a Minecraft length-prefixed string
|
||||
func WriteString(w io.Writer, s string) error {
|
||||
if err := WriteVarInt(w, int32(len(s))); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := io.WriteString(w, s)
|
||||
return err
|
||||
}
|
||||
|
||||
// buildPacket builds a framed packet: [length VarInt][packetId VarInt][payload]
|
||||
func buildPacket(packetID int32, payload []byte) []byte {
|
||||
var b bytes.Buffer
|
||||
_ = WriteVarInt(&b, packetID)
|
||||
b.Write(payload)
|
||||
|
||||
var framed bytes.Buffer
|
||||
_ = WriteVarInt(&framed, int32(b.Len()))
|
||||
framed.Write(b.Bytes())
|
||||
return framed.Bytes()
|
||||
}
|
||||
|
||||
// WriteStatusJSONPacket writes a Status Response (packet 0x00) with the provided JSON string
|
||||
func WriteStatusJSONPacket(w io.Writer, jsonString string) error {
|
||||
// payload is the JSON as a Minecraft string
|
||||
var payload bytes.Buffer
|
||||
if err := WriteString(&payload, jsonString); err != nil {
|
||||
return err
|
||||
}
|
||||
pkt := buildPacket(PacketIdStatusResponse, payload.Bytes())
|
||||
_, err := w.Write(pkt)
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteStatusFromStruct writes a Status Response from a struct
|
||||
func WriteStatusFromStruct(w io.Writer, status StatusResponse) error {
|
||||
b, err := json.Marshal(status)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return WriteStatusJSONPacket(w, string(b))
|
||||
}
|
||||
|
||||
// WritePongPacket writes Pong (packet 0x01) with the same payload
|
||||
func WritePongPacket(w io.Writer, timestamp int64) error {
|
||||
var pl bytes.Buffer
|
||||
// payload is a signed long (64-bit)
|
||||
var buf [8]byte
|
||||
binary.BigEndian.PutUint64(buf[:], uint64(timestamp))
|
||||
pl.Write(buf[:])
|
||||
pkt := buildPacket(PackedIdPongResponse, pl.Bytes())
|
||||
_, err := w.Write(pkt)
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteLegacySLPResponse writes the 1.6-compatible legacy response packet (0xFF)
|
||||
// Format: FF, [length short], UTF16BE string beginning with "\u00A7\u0031\u0000" then null-delimited fields
|
||||
// fields: protocol, version, motd, online, max
|
||||
func WriteLegacySLPResponse(w io.Writer, protocol int, version string, motd string, online int, max int) error {
|
||||
// Build the string with null separators
|
||||
s := "\u00A7\u0031\u0000" +
|
||||
intToString(protocol) + "\u0000" +
|
||||
version + "\u0000" +
|
||||
motd + "\u0000" +
|
||||
intToString(online) + "\u0000" +
|
||||
intToString(max)
|
||||
|
||||
// Encode UTF-16BE
|
||||
runes := []rune(s)
|
||||
encoded := utf16.Encode(runes)
|
||||
var be bytes.Buffer
|
||||
for _, v := range encoded {
|
||||
var tmp [2]byte
|
||||
binary.BigEndian.PutUint16(tmp[:], v)
|
||||
be.Write(tmp[:])
|
||||
}
|
||||
|
||||
bw := bufio.NewWriter(w)
|
||||
// 0xFF
|
||||
if _, err := bw.Write([]byte{0xFF}); err != nil {
|
||||
return err
|
||||
}
|
||||
// length short in code units
|
||||
var lenBuf [2]byte
|
||||
binary.BigEndian.PutUint16(lenBuf[:], uint16(len(encoded)))
|
||||
if _, err := bw.Write(lenBuf[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := bw.Write(be.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
return bw.Flush()
|
||||
}
|
||||
|
||||
// helpers
|
||||
func intToString(i int) string {
|
||||
if i == 0 {
|
||||
return "0"
|
||||
}
|
||||
neg := false
|
||||
if i < 0 {
|
||||
neg = true
|
||||
i = -i
|
||||
}
|
||||
var buf [20]byte
|
||||
pos := len(buf)
|
||||
for i > 0 {
|
||||
pos--
|
||||
buf[pos] = byte('0' + (i % 10))
|
||||
i /= 10
|
||||
}
|
||||
if neg {
|
||||
pos--
|
||||
buf[pos] = '-'
|
||||
}
|
||||
return string(buf[pos:])
|
||||
}
|
||||
Reference in New Issue
Block a user