package p2p import ( "net" "sync" ) // IPeerSet has a (immutable) subset of the methods of PeerSet. type IPeerSet interface { Has(key ID) bool HasIP(ip net.IP) bool Get(key ID) Peer List() []Peer Size() int } //----------------------------------------------------------------------------- // PeerSet is a special structure for keeping a table of peers. // Iteration over the peers is super fast and thread-safe. type PeerSet struct { mtx sync.Mutex lookup map[ID]*peerSetItem list []Peer } type peerSetItem struct { peer Peer index int } // NewPeerSet creates a new peerSet with a list of initial capacity of 256 items. func NewPeerSet() *PeerSet { return &PeerSet{ lookup: make(map[ID]*peerSetItem), list: make([]Peer, 0, 256), } } // Add adds the peer to the PeerSet. // It returns an error carrying the reason, if the peer is already present. func (ps *PeerSet) Add(peer Peer) error { ps.mtx.Lock() defer ps.mtx.Unlock() if ps.lookup[peer.ID()] != nil { return ErrSwitchDuplicatePeerID{peer.ID()} } if ps.hasIP(peer.RemoteIP()) { return ErrSwitchDuplicatePeerIP{peer.RemoteIP()} } index := len(ps.list) // Appending is safe even with other goroutines // iterating over the ps.list slice. ps.list = append(ps.list, peer) ps.lookup[peer.ID()] = &peerSetItem{peer, index} return nil } // Has returns true iff the PeerSet contains // the peer referred to by this peerKey. func (ps *PeerSet) Has(peerKey ID) bool { ps.mtx.Lock() _, ok := ps.lookup[peerKey] ps.mtx.Unlock() return ok } // HasIP returns true if the PeerSet contains the peer referred to by this IP // address. func (ps *PeerSet) HasIP(peerIP net.IP) bool { ps.mtx.Lock() defer ps.mtx.Unlock() return ps.hasIP(peerIP) } // hasIP does not acquire a lock so it can be used in public methods which // already lock. func (ps *PeerSet) hasIP(peerIP net.IP) bool { for _, item := range ps.lookup { if item.peer.RemoteIP().Equal(peerIP) { return true } } return false } // Get looks up a peer by the provided peerKey. func (ps *PeerSet) Get(peerKey ID) Peer { ps.mtx.Lock() defer ps.mtx.Unlock() item, ok := ps.lookup[peerKey] if ok { return item.peer } return nil } // Remove discards peer by its Key, if the peer was previously memoized. func (ps *PeerSet) Remove(peer Peer) { ps.mtx.Lock() defer ps.mtx.Unlock() item := ps.lookup[peer.ID()] if item == nil { return } index := item.index // Create a new copy of the list but with one less item. // (we must copy because we'll be mutating the list). newList := make([]Peer, len(ps.list)-1) copy(newList, ps.list) // If it's the last peer, that's an easy special case. if index == len(ps.list)-1 { ps.list = newList delete(ps.lookup, peer.ID()) return } // Replace the popped item with the last item in the old list. lastPeer := ps.list[len(ps.list)-1] lastPeerKey := lastPeer.ID() lastPeerItem := ps.lookup[lastPeerKey] newList[index] = lastPeer lastPeerItem.index = index ps.list = newList delete(ps.lookup, peer.ID()) } // Size returns the number of unique items in the peerSet. func (ps *PeerSet) Size() int { ps.mtx.Lock() defer ps.mtx.Unlock() return len(ps.list) } // List returns the threadsafe list of peers. func (ps *PeerSet) List() []Peer { ps.mtx.Lock() defer ps.mtx.Unlock() return ps.list }