-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
ping_14.go
130 lines (109 loc) · 3.82 KB
/
ping_14.go
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
package minequery
import (
"bytes"
"fmt"
"io"
"strconv"
"strings"
)
var ping14PingPacket = []byte{0xfe, 0x01}
const (
ping14ResponsePayloadFieldSeparator = "§"
)
// Status14 holds status response returned by 1.4 to 1.6 (exclusively) Minecraft servers.
type Status14 struct {
MOTD string
OnlinePlayers int
MaxPlayers int
}
// String returns a user-friendly representation of a server status response.
// It contains presumed (1.4+) Minecraft Server version, online count and naturalized MOTD.
func (s *Status14) String() string {
return fmt.Sprintf("Minecraft Server (1.4+), %d/%d players online, MOTD: %s",
s.OnlinePlayers, s.MaxPlayers, naturalizeMOTD(s.MOTD))
}
// Ping14 pings 1.4 to 1.6 (exclusively) Minecraft servers (Notchian servers of more late versions also respond to
// this ping packet.)
//
//goland:noinspection GoUnusedExportedFunction
func Ping14(host string, port int) (*Status14, error) {
return defaultPinger.Ping14(host, port)
}
// Ping14 pings 1.4 to 1.6 (exclusively) Minecraft servers (Notchian servers of more late versions also respond to
// this ping packet.)
func (p *Pinger) Ping14(host string, port int) (*Status14, error) {
status, err := p.pingGeneric(p.ping14, host, port)
if err != nil {
return nil, err
}
return status.(*Status14), nil
}
func (p *Pinger) ping14(host string, port int) (interface{}, error) {
conn, err := p.openTCPConn(host, port)
if err != nil {
return nil, err
}
defer func() { _ = conn.Close() }()
// Send ping packet
if err = p.ping14WritePingPacket(conn); err != nil {
return nil, fmt.Errorf("could not write ping packet: %w", err)
}
// Read status response (note: uses the same packet reading approach as 1.4)
payload, err := p.pingBeta18ReadResponsePacket(conn)
if err != nil {
return nil, fmt.Errorf("could not read response packet: %w", err)
}
// Parse response data from status packet
res, err := p.ping14ParseResponsePayload(payload)
if err != nil {
return nil, fmt.Errorf("could not parse status from response packet: %w", err)
}
return res, nil
}
// Communication
func (p *Pinger) ping14WritePingPacket(writer io.Writer) error {
// Write 2-byte FE 01 ping packet
_, err := writer.Write(ping14PingPacket)
return err
}
// Response processing
func (p *Pinger) ping14ParseResponsePayload(payload []byte) (*Status14, error) {
// NOTE: Spigot 1.4 servers reply with 1.6 response format.
// See https://github.com/dreamscached/minequery/issues/31 for details.
// Check if data string begins with '§1\x00' (00 a7 00 31 00 00) and pass processing to 1.6 logic in this case.
if bytes.HasPrefix(payload, ping16ResponsePrefix) {
if p.UseStrict {
return nil, fmt.Errorf("%w: server unexpectedly replied with 1.6 response", ErrInvalidStatus)
}
res, err := p.ping16ParseResponsePayload(payload)
if err != nil {
return nil, fmt.Errorf("could not parse status from response packet: %w", err)
}
return &Status14{
MOTD: res.MOTD,
OnlinePlayers: res.OnlinePlayers,
MaxPlayers: res.MaxPlayers,
}, nil
}
// Split status string, parse and map to struct returning errors if conversions fail
fields := strings.Split(string(payload), ping14ResponsePayloadFieldSeparator)
if len(fields) != 3 {
return nil, fmt.Errorf("%w: expected 3 status fields, got %d", ErrInvalidStatus, len(fields))
}
motd, onlineString, maxString := fields[0], fields[1], fields[2]
// Parse online players
online, err := strconv.ParseInt(onlineString, 10, 32)
if err != nil {
return nil, fmt.Errorf("%w: could not parse online players count: %s", ErrInvalidStatus, err)
}
// Parse max players
max, err := strconv.ParseInt(maxString, 10, 32)
if err != nil {
return nil, fmt.Errorf("%w: could not parse max players count: %s", ErrInvalidStatus, err)
}
return &Status14{
MOTD: motd,
OnlinePlayers: int(online),
MaxPlayers: int(max),
}, nil
}