Phase8
Some checks failed
CI / Node — lint, typecheck, test, build (push) Failing after 9s
CI / Go — node-agent tests (push) Failing after 9s
CI / Go — edge-gateway build (push) Successful in 17s

This commit is contained in:
smueller
2026-06-26 13:33:03 +02:00
parent ab21f53cdd
commit b335f6a497
39 changed files with 1477 additions and 20 deletions

View File

@@ -0,0 +1,174 @@
package mc
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"strings"
)
type Handshake struct {
ProtocolVersion int32
ServerAddress string
ServerPort uint16
NextState int32
Raw []byte
}
func ReadHandshake(r io.Reader) (*Handshake, error) {
raw, err := readPacket(r)
if err != nil {
return nil, err
}
reader := newReader(raw)
packetID, err := reader.readVarInt()
if err != nil {
return nil, err
}
if packetID != 0 {
return nil, fmt.Errorf("unexpected packet id %d", packetID)
}
protocolVersion, err := reader.readVarInt()
if err != nil {
return nil, err
}
serverAddress, err := reader.readString()
if err != nil {
return nil, err
}
portBytes, err := reader.readBytes(2)
if err != nil {
return nil, err
}
serverPort := binary.BigEndian.Uint16(portBytes)
nextState, err := reader.readVarInt()
if err != nil {
return nil, err
}
return &Handshake{
ProtocolVersion: protocolVersion,
ServerAddress: serverAddress,
ServerPort: serverPort,
NextState: nextState,
Raw: raw,
}, nil
}
func ExtractSlug(hostname, playDomain string) string {
host := strings.ToLower(strings.Split(hostname, "\x00")[0])
domain := strings.ToLower(playDomain)
suffix := "." + domain
if !strings.HasSuffix(host, suffix) {
return ""
}
slug := strings.TrimSuffix(host, suffix)
if slug == "" || strings.Contains(slug, ".") {
return ""
}
return slug
}
func readPacket(r io.Reader) ([]byte, error) {
length, err := readVarIntFrom(r)
if err != nil {
return nil, err
}
if length <= 0 || length > 1<<20 {
return nil, fmt.Errorf("invalid packet length %d", length)
}
payload := make([]byte, length)
if _, err := io.ReadFull(r, payload); err != nil {
return nil, err
}
return payload, nil
}
type packetReader struct {
buf *bytes.Reader
}
func newReader(data []byte) *packetReader {
return &packetReader{buf: bytes.NewReader(data)}
}
func (p *packetReader) readVarInt() (int32, error) {
return readVarIntFrom(p.buf)
}
func (p *packetReader) readString() (string, error) {
length, err := p.readVarInt()
if err != nil {
return "", err
}
if length < 0 || length > 255 {
return "", fmt.Errorf("invalid string length %d", length)
}
data, err := p.readBytes(int(length))
if err != nil {
return "", err
}
return string(data), nil
}
func (p *packetReader) readBytes(n int) ([]byte, error) {
out := make([]byte, n)
if _, err := io.ReadFull(p.buf, out); err != nil {
return nil, err
}
return out, nil
}
func readVarIntFrom(r io.Reader) (int32, error) {
var numRead int
var result int32
for {
var value [1]byte
if _, err := io.ReadFull(r, value[:]); err != nil {
return 0, err
}
result |= int32(value[0]&0x7F) << (7 * numRead)
numRead++
if numRead > 5 {
return 0, fmt.Errorf("varint too big")
}
if value[0]&0x80 == 0 {
break
}
}
return result, nil
}
func WritePacket(w io.Writer, payload []byte) error {
if err := writeVarIntTo(w, int32(len(payload))); err != nil {
return err
}
_, err := w.Write(payload)
return err
}
func writeVarIntTo(w io.Writer, value int32) error {
for {
temp := byte(value & 0x7F)
value >>= 7
if value != 0 {
temp |= 0x80
}
if _, err := w.Write([]byte{temp}); err != nil {
return err
}
if value == 0 {
break
}
}
return nil
}

View File

@@ -0,0 +1,59 @@
package mc
import (
"bytes"
"testing"
)
func TestExtractSlug(t *testing.T) {
if got := ExtractSlug("abc.play.example.net", "play.example.net"); got != "abc" {
t.Fatalf("got %q want abc", got)
}
if got := ExtractSlug("other.net", "play.example.net"); got != "" {
t.Fatalf("expected empty slug")
}
}
func TestReadHandshake(t *testing.T) {
var payload bytes.Buffer
writeVarInt(&payload, 0)
writeVarInt(&payload, 763)
writeString(&payload, "demo.play.example.net")
payload.WriteByte(0x63)
payload.WriteByte(0xdd)
writeVarInt(&payload, 2)
var packet bytes.Buffer
writeVarInt(&packet, int32(payload.Len()))
packet.Write(payload.Bytes())
hs, err := ReadHandshake(&packet)
if err != nil {
t.Fatalf("read handshake: %v", err)
}
if hs.ServerAddress != "demo.play.example.net" {
t.Fatalf("address %q", hs.ServerAddress)
}
if hs.ServerPort != 25565 {
t.Fatalf("port %d", hs.ServerPort)
}
}
func writeVarInt(buf *bytes.Buffer, value int32) {
for {
temp := byte(value & 0x7F)
value >>= 7
if value != 0 {
temp |= 0x80
}
buf.WriteByte(temp)
if value == 0 {
break
}
}
}
func writeString(buf *bytes.Buffer, value string) {
writeVarInt(buf, int32(len(value)))
buf.WriteString(value)
}