hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/internal/server/ping.go
blob: 4783ec79fee656907f921a2456d4dbfed33b4074 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package server

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net"
	"time"
)

type pingResponse struct {
	Version struct {
		Name string `json:"name"`
	} `json:"version"`
	Players struct {
		Online int `json:"online"`
		Max    int `json:"max"`
	} `json:"players"`
	Description json.RawMessage `json:"description"`
}

func (p pingResponse) motdText() string {
	var text string
	if json.Unmarshal(p.Description, &text) == nil {
		return text
	}

	var obj struct {
		Text string `json:"text"`
	}
	if json.Unmarshal(p.Description, &obj) == nil {
		return obj.Text
	}
	return ""
}

func pingServer(host string, port int, timeout time.Duration) (*pingResponse, error) {
	conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), timeout)
	if err != nil {
		return nil, err
	}
	defer conn.Close()
	conn.SetDeadline(time.Now().Add(timeout))

	var handshake bytes.Buffer
	writeVarInt(&handshake, 0x00)
	writeVarInt(&handshake, -1)
	writeString(&handshake, host)
	binary.Write(&handshake, binary.BigEndian, uint16(port))
	writeVarInt(&handshake, 1)
	if err := writePacket(conn, handshake.Bytes()); err != nil {
		return nil, err
	}

	if err := writePacket(conn, []byte{0x00}); err != nil {
		return nil, err
	}

	reader := bufio.NewReader(conn)
	if _, err := readVarInt(reader); err != nil {
		return nil, err
	}
	packetID, err := readVarInt(reader)
	if err != nil {
		return nil, err
	}
	if packetID != 0x00 {
		return nil, fmt.Errorf("unexpected packet id %d", packetID)
	}

	jsonLen, err := readVarInt(reader)
	if err != nil {
		return nil, err
	}
	payload := make([]byte, jsonLen)
	if _, err := io.ReadFull(reader, payload); err != nil {
		return nil, err
	}

	var resp pingResponse
	if err := json.Unmarshal(payload, &resp); err != nil {
		return nil, err
	}
	return &resp, nil
}

func writePacket(conn net.Conn, data []byte) error {
	var buf bytes.Buffer
	writeVarInt(&buf, len(data))
	buf.Write(data)
	_, err := conn.Write(buf.Bytes())
	return err
}

func writeString(buf *bytes.Buffer, s string) {
	writeVarInt(buf, len(s))
	buf.WriteString(s)
}

func writeVarInt(buf *bytes.Buffer, value int) {
	uv := uint32(value)
	for {
		if uv&^uint32(0x7F) == 0 {
			buf.WriteByte(byte(uv))
			return
		}
		buf.WriteByte(byte(uv&0x7F | 0x80))
		uv >>= 7
	}
}

func readVarInt(r io.ByteReader) (int, error) {
	var result uint32
	var shift uint
	for {
		b, err := r.ReadByte()
		if err != nil {
			return 0, err
		}
		result |= uint32(b&0x7F) << shift
		if b&0x80 == 0 {
			break
		}
		shift += 7
		if shift >= 32 {
			return 0, errors.New("varint too long")
		}
	}
	return int(result), nil
}