aboutsummaryrefslogtreecommitdiff
path: root/hotline/tracker.go
diff options
context:
space:
mode:
Diffstat (limited to 'hotline/tracker.go')
-rw-r--r--hotline/tracker.go198
1 files changed, 198 insertions, 0 deletions
diff --git a/hotline/tracker.go b/hotline/tracker.go
new file mode 100644
index 0000000..97ca108
--- /dev/null
+++ b/hotline/tracker.go
@@ -0,0 +1,198 @@
+package hotline
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "github.com/jhalter/mobius/concat"
+ "net"
+ "strconv"
+ "time"
+)
+
+type TrackerRegistration struct {
+ Port []byte // Server’s listening UDP port number TODO: wat?
+ UserCount int // Number of users connected to this particular server
+ PassID []byte // Random number generated by the server
+ Name string // Server’s name
+ Description string // Description of the server
+}
+
+func (tr *TrackerRegistration) Payload() []byte {
+ userCount := make([]byte, 2)
+ binary.BigEndian.PutUint16(userCount, uint16(tr.UserCount))
+
+ return concat.Slices(
+ []byte{0x00, 0x01},
+ tr.Port,
+ userCount,
+ []byte{0x00, 0x00},
+ tr.PassID,
+ []byte{uint8(len(tr.Name))},
+ []byte(tr.Name),
+ []byte{uint8(len(tr.Description))},
+ []byte(tr.Description),
+ )
+}
+
+func register(tracker string, tr TrackerRegistration) error {
+ conn, err := net.Dial("udp", tracker)
+ if err != nil {
+ return err
+ }
+
+ if _, err := conn.Write(tr.Payload()); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+type ServerListing struct {
+}
+
+const trackerTimeout = 5 * time.Second
+
+// All string values use 8-bit ASCII character set encoding.
+// Client Interface with Tracker
+// After establishing a connection with tracker, the following information is sent:
+// Description Size Data Note
+// Magic number 4 ‘HTRK’
+// Version 2 1 or 2 Old protocol (1) or new (2)
+
+// Reply received from the tracker starts with a header:
+//
+
+type TrackerHeader struct {
+ Protocol [4]byte // "HTRK" 0x4854524B
+ Version [2]byte // Old protocol (1) or new (2)
+}
+
+//Message type 2 1 Sending list of servers
+//Message data size 2 Remaining size of this request
+//Number of servers 2 Number of servers in the server list
+//Number of servers 2 Same as previous field
+type ServerInfoHeader struct {
+ MsgType [2]byte // always has value of 1
+ MsgDataSize [2]byte // Remaining size of request
+ SrvCount [2]byte // Number of servers in the server list
+ SrvCountDup [2]byte // Same as previous field ¯\_(ツ)_/¯
+}
+
+type ServerRecord struct {
+ IPAddr []byte
+ Port []byte
+ NumUsers []byte // Number of users connected to this particular server
+ Unused []byte
+ NameSize byte // Length of name string
+ Name []byte // Server’s name
+ DescriptionSize byte
+ Description []byte
+}
+
+func GetListing(addr string) ([]ServerRecord, error) {
+ conn, err := net.DialTimeout("tcp", addr, trackerTimeout)
+ if err != nil {
+ return nil, err
+ }
+ //spew.Dump(conn)
+ _, err = conn.Write(
+ []byte{
+ 0x48, 0x54, 0x52, 0x4B, // HTRK
+ 0x00, 0x01, // Version
+ },
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ totalRead := 0
+
+ buf := make([]byte, 4096) // handshakes are always 12 bytes in length
+ var readLen int
+ if readLen, err = conn.Read(buf); err != nil {
+ return nil, err
+ }
+ totalRead += readLen
+
+ var th TrackerHeader
+ if err := binary.Read(bytes.NewReader(buf[:6]), binary.BigEndian, &th); err != nil {
+ return nil, err
+ }
+
+ var info ServerInfoHeader
+ if err := binary.Read(bytes.NewReader(buf[6:14]), binary.BigEndian, &info); err != nil {
+ return nil, err
+ }
+
+ payloadSize := int(binary.BigEndian.Uint16(info.MsgDataSize[:]))
+
+ if totalRead < payloadSize {
+ for {
+ //fmt.Printf("totalRead: %v", totalRead)
+ //fmt.Printf("readLen: %v Payload size: %v, Server count: %x\n", readLen, payloadSize, info.SrvCount)
+
+ if readLen, err = conn.Read(buf); err != nil {
+ return nil, err
+ }
+ totalRead += readLen
+ if totalRead >= payloadSize {
+ break
+ }
+ }
+ }
+ //fmt.Println("passed read")
+ totalSrv := int(binary.BigEndian.Uint16(info.SrvCount[:]))
+
+ //fmt.Printf("readLen: %v Payload size: %v, Server count: %x\n", readLen, payloadSize, info.SrvCount)
+ srvBuf := buf[14:totalRead]
+
+ totalRead += readLen
+
+ var servers []ServerRecord
+ for {
+ var srv ServerRecord
+ n, _ := srv.Read(srvBuf)
+ servers = append(servers, srv)
+
+ srvBuf = srvBuf[n:]
+ // fmt.Printf("srvBuf len: %v\n", len(srvBuf))
+ if len(servers) == totalSrv {
+ return servers, nil
+ }
+
+ if len(srvBuf) == 0 {
+ if readLen, err = conn.Read(buf); err != nil {
+ return nil, err
+ }
+ srvBuf = buf[8:readLen]
+ }
+ }
+
+ return servers, nil
+}
+
+func (s *ServerRecord) Read(b []byte) (n int, err error) {
+ s.IPAddr = b[0:4]
+ s.Port = b[4:6]
+ s.NumUsers = b[6:8]
+ s.NameSize = b[10]
+ nameLen := int(b[10])
+ s.Name = b[11 : 11+nameLen]
+ s.DescriptionSize = b[11+nameLen]
+ s.Description = b[12+nameLen : 12+nameLen+int(s.DescriptionSize)]
+
+ return 12 + nameLen + int(s.DescriptionSize), nil
+}
+
+func (s *ServerRecord) PortInt() int {
+ data := binary.BigEndian.Uint16(s.Port)
+ return int(data)
+}
+
+func (s *ServerRecord) Addr() string {
+ return fmt.Sprintf("%s:%s",
+ net.IP(s.IPAddr),
+ strconv.Itoa(int(binary.BigEndian.Uint16(s.Port))),
+ )
+}