package mainline import ( "net" "sync" "time" ) type IndexingService struct { // Private protocol *Protocol started bool interval time.Duration eventHandlers IndexingServiceEventHandlers nodeID []byte // []byte type would be a much better fit for the keys but unfortunately (and quite // understandably) slices cannot be used as keys (since they are not hashable), and using arrays // (or even the conversion between each other) is a pain; hence map[string]net.UDPAddr // ^~~~~~ routingTable map[string]*net.UDPAddr routingTableMutex *sync.Mutex counter uint16 getPeersRequests map[[2]byte][20]byte // GetPeersQuery.`t` -> infohash } type IndexingServiceEventHandlers struct { OnResult func(IndexingResult) } type IndexingResult struct { infoHash [20]byte peerAddr *net.TCPAddr } func (ir IndexingResult) InfoHash() [20]byte { return ir.infoHash } func (ir IndexingResult) PeerAddr() *net.TCPAddr { return ir.peerAddr } func NewIndexingService(laddr string, interval time.Duration, eventHandlers IndexingServiceEventHandlers) *IndexingService { service := new(IndexingService) service.interval = interval service.protocol = NewProtocol( laddr, ProtocolEventHandlers{ OnGetPeersResponse: service.onGetPeersResponse, OnSampleInfohashesResponse: service.onSampleInfohashesResponse, }, ) service.nodeID = make([]byte, 20) service.routingTable = make(map[string]*net.UDPAddr) service.routingTableMutex = new(sync.Mutex) service.eventHandlers = eventHandlers return service } func (is *IndexingService) onGetPeersResponse(msg *Message, addr *net.UDPAddr) { var t [2]byte copy(t[:], msg.T) infoHash := is.getPeersRequests[t] // We got a response, so free the key! delete(is.getPeersRequests, t) // BEP 51 specifies that // The new sample_infohashes remote procedure call requests that a remote node return a string of multiple // concatenated infohashes (20 bytes each) FOR WHICH IT HOLDS GET_PEERS VALUES. // ^^^^^^ // So theoretically we should never hit the case where `values` is empty, but c'est la vie. if len(msg.R.Values) == 0 { return } for _, peer := range msg.R.Values { is.eventHandlers.OnResult(IndexingResult{ infoHash: infoHash, peerAddr: &net.TCPAddr{ IP: peer.IP, Port: peer.Port, }, }) } } func (is *IndexingService) onSampleInfohashesResponse(msg *Message, addr *net.UDPAddr) { for i := 0; i < len(msg.R.Samples)/20; i++ { var infoHash [20]byte copy(infoHash[:], msg.R.Samples[i:(i+1)*20]) msg := NewGetPeersQuery(is.nodeID, infoHash[:]) t := uint16BE(is.counter) msg.T = t[:] is.protocol.SendMessage(msg, addr) is.getPeersRequests[t] = infoHash is.counter++ } } func uint16BE(v uint16) (b [2]byte) { b[0] = byte(v >> 8) b[1] = byte(v) return }