diff options
Diffstat (limited to 'hotline/tracker.go')
| -rw-r--r-- | hotline/tracker.go | 198 |
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))), + ) +} |