mcstatus/client.go
2022-06-22 04:25:03 +01:00

129 lines
2.8 KiB
Go

package main
import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"io"
"net"
"time"
)
type client struct {
conn net.Conn
addr string
port int
version uint64
}
// newClient Initialises a new connection
func newClient(conn net.Conn, addr string, port int, ver uint64) client {
return client{conn, addr, port, ver}
}
// read the response from the Minecraft server
func (c *client) read() (*Response, error) {
buf := make([]byte, 0, 32768)
tmp := make([]byte, 256)
for {
count, err := c.conn.Read(tmp)
if count > 0 {
buf = append(buf, tmp[:count]...)
}
if err != nil {
if err == io.EOF {
break
} else {
return nil, err
}
}
}
return c.handle(buf)
}
// write send handshake request to the Minecraft server
func (c *client) write() error {
_, err := c.conn.Write(handshake(c.addr, c.port, c.version))
if err != nil {
return err
}
return nil
}
// handle parses response from Minecraft server and converts the json output to a protocol buffer
func (c *client) handle(message []byte) (*Response, error) {
var response Response
b := bytes.Split(message, []byte("{"))
ne := bytes.SplitAfterN(message, b[0], 2)
trim := bytes.TrimSuffix(ne[1], []byte("\x00"))
if err := json.Unmarshal(trim, &response); err != nil {
return nil, err
}
return &response, nil
}
// ping sends ping payload to Minecraft server
func (c *client) ping() (time.Duration, error) {
start := time.Now()
if _, err := c.conn.Write([]byte{0x01, byte(start.Unix())}); err != nil {
return 0, err
}
buf := make([]byte, 2)
for {
fmt.Println("ping")
_, err := c.conn.Read(buf)
if err != nil {
if err == io.EOF {
break
} else {
return 0, err
}
}
}
return time.Since(start), nil
}
func (c *client) pingtest() (time.Duration, error) {
start := time.Now()
if _, err := c.conn.Write([]byte{0x01, byte(start.Unix())}); err != nil {
return 0, err
}
_, err := io.ReadAll(c.conn)
if err != nil {
return 0, err
}
return time.Since(start), nil
}
/* handshake constructs the handshake packet to be sent to the Minecraft server
see: https://wiki.vg/Server_List_Ping#Handshake */
func handshake(addr string, port int, ver uint64) []byte {
id := []byte{0x00}
state := []byte{0x01}
version := make([]byte, 2)
binary.PutUvarint(version, ver)
p := make([]byte, 2)
binary.BigEndian.PutUint16(p, uint16(port))
length := packetlength(id, version, []byte(addr), p, state) + 1
var handshake bytes.Buffer
handshake.WriteByte(byte(length))
handshake.Write(id)
handshake.Write(version)
handshake.WriteByte(byte(len(addr)))
handshake.WriteString(addr)
handshake.Write(p)
handshake.Write(state)
handshake.Write([]byte{0x01, 0x00})
return handshake.Bytes()
}
func packetlength(b ...[]byte) (length int) {
for _, bytes := range b {
length += len(bytes)
}
return length
}