package main import ( "bytes" "encoding/binary" "fmt" "io" "mcstatus/mcstatuspb" "net" "time" "google.golang.org/protobuf/encoding/protojson" ) 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() (*mcstatuspb.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) (*mcstatuspb.Response, error) { var response mcstatuspb.Response b := bytes.Split(message, []byte("{")) ne := bytes.SplitAfterN(message, b[0], 2) trim := bytes.TrimSuffix(ne[1], []byte("\x00")) js := &protojson.UnmarshalOptions{DiscardUnknown: true} if err := js.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 }