X-Git-Url: https://git.r.bdr.sh/rbdr/mobius/blobdiff_plain/22c599abc18895f73e96095f35b71cf3357d41b4..af0e8409bd0eb3bbd97ce8d2a249344ac4d2894d:/hotline/tracker.go?ds=sidebyside diff --git a/hotline/tracker.go b/hotline/tracker.go index 97ca108..4d4c8a6 100644 --- a/hotline/tracker.go +++ b/hotline/tracker.go @@ -1,7 +1,7 @@ package hotline import ( - "bytes" + "bufio" "encoding/binary" "fmt" "github.com/jhalter/mobius/concat" @@ -10,21 +10,23 @@ import ( "time" ) +// TrackerRegistration represents the payload a Hotline server sends to a Tracker to register 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 + Port [2]byte // Server listening port number + UserCount int // Number of users connected to this particular server + PassID []byte // Random number generated by the server + Name string // Server name + Description string // Description of the server } -func (tr *TrackerRegistration) Payload() []byte { +// TODO: reimplement as io.Reader +func (tr *TrackerRegistration) Read() []byte { userCount := make([]byte, 2) binary.BigEndian.PutUint16(userCount, uint16(tr.UserCount)) return concat.Slices( []byte{0x00, 0x01}, - tr.Port, + tr.Port[:], userCount, []byte{0x00, 0x00}, tr.PassID, @@ -35,22 +37,21 @@ func (tr *TrackerRegistration) Payload() []byte { ) } -func register(tracker string, tr TrackerRegistration) error { +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 { + b := tr.Read() + + if _, err := conn.Write(b); err != nil { return err } return nil } -type ServerListing struct { -} - const trackerTimeout = 5 * time.Second // All string values use 8-bit ASCII character set encoding. @@ -61,17 +62,15 @@ const trackerTimeout = 5 * time.Second // 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 +// 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 @@ -80,12 +79,12 @@ type ServerInfoHeader struct { } type ServerRecord struct { - IPAddr []byte - Port []byte - NumUsers []byte // Number of users connected to this particular server - Unused []byte + IPAddr [4]byte + Port [2]byte + NumUsers [2]byte // Number of users connected to this particular server + Unused [2]byte NameSize byte // Length of name string - Name []byte // Server’s name + Name []byte // Server name DescriptionSize byte Description []byte } @@ -93,9 +92,10 @@ type ServerRecord struct { func GetListing(addr string) ([]ServerRecord, error) { conn, err := net.DialTimeout("tcp", addr, trackerTimeout) if err != nil { - return nil, err + return []ServerRecord{}, err } - //spew.Dump(conn) + defer func() { _ = conn.Close() }() + _, err = conn.Write( []byte{ 0x48, 0x54, 0x52, 0x4B, // HTRK @@ -106,78 +106,67 @@ func GetListing(addr string) ([]ServerRecord, error) { 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 { + if err := binary.Read(conn, 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 { + if err := binary.Read(conn, 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 + scanner := bufio.NewScanner(conn) + scanner.Split(serverScanner) var servers []ServerRecord for { + scanner.Scan() var srv ServerRecord - n, _ := srv.Read(srvBuf) - servers = append(servers, srv) + _, _ = srv.Read(scanner.Bytes()) - srvBuf = srvBuf[n:] - // fmt.Printf("srvBuf len: %v\n", len(srvBuf)) + servers = append(servers, srv) 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] + break } } return servers, nil } +// serverScanner implements bufio.SplitFunc for parsing the tracker list into ServerRecords tokens +// Example payload: +// 00000000 18 05 30 63 15 7c 00 02 00 00 10 54 68 65 20 4d |..0c.|.....The M| +// 00000010 6f 62 69 75 73 20 53 74 72 69 70 40 48 6f 6d 65 |obius Strip@Home| +// 00000020 20 6f 66 20 74 68 65 20 4d 6f 62 69 75 73 20 48 | of the Mobius H| +// 00000030 6f 74 6c 69 6e 65 20 73 65 72 76 65 72 20 61 6e |otline server an| +// 00000040 64 20 63 6c 69 65 6e 74 20 7c 20 54 52 54 50 48 |d client | TRTPH| +// 00000050 4f 54 4c 2e 63 6f 6d 3a 35 35 30 30 2d 4f 3a b2 |OTL.com:5500-O:.| +// 00000060 15 7c 00 00 00 00 08 53 65 6e 65 63 74 75 73 20 |.|.....Senectus | +func serverScanner(data []byte, _ bool) (advance int, token []byte, err error) { + if len(data) < 10 { + return 0, nil, nil + } + + // A server entry has two variable length fields: the name and description. + // To get the token length, we first need the name length from the 10th byte: + nameLen := int(data[10]) + + // Next we need the description length from the 11+nameLen byte: + descLen := int(data[11+nameLen]) + + return 12 + nameLen + descLen, data[0 : 12+nameLen+descLen], nil +} + +// Read implements io.Reader for ServerRecord 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] + copy(s.IPAddr[:], b[0:4]) + copy(s.Port[:], b[4:6]) + copy(s.NumUsers[:], b[6:8]) 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)] @@ -185,14 +174,9 @@ func (s *ServerRecord) Read(b []byte) (n int, err error) { 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))), + net.IP(s.IPAddr[:]), + strconv.Itoa(int(binary.BigEndian.Uint16(s.Port[:]))), ) }