129 lines
2.8 KiB
Go
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
|
|
}
|