import { ExistingPeerError } from '../errors' ; import { P2PEnhancedPeerInfo , P2PPeerInfo } from '../types' ; import { evictPeerRandomlyFromBucket , getBucketId , PEER_TYPE , sanitizeEnhancedPeerInfo , } from '../utils' ; export interface PeerListConfig { readonly numOfBuckets : number ; readonly bucketSize : number ; readonly secret : number ; readonly peerType : PEER_TYPE ; } export type Bucket = Map < string , P2PEnhancedPeerInfo > ; export interface BucketInfo { readonly bucketId : number ; readonly bucket : Bucket ; } export class BaseList { protected bucketIdToBucket : Map < number , Bucket > ; protected peerIdToPeerInfo : Map < string , P2PEnhancedPeerInfo > ; protected type : PEER_TYPE | undefined ; protected readonly peerListConfig : PeerListConfig ; public constructor ( { bucketSize , numOfBuckets , secret , peerType } ) { this . peerListConfig = { bucketSize , numOfBuckets , peerType , secret , } ; this . bucketIdToBucket = new Map < number , Bucket > ( ) ; this . _initBuckets ( ) ; this . peerIdToPeerInfo = new Map < string , P2PEnhancedPeerInfo > ( ) ; } public get peerList ( ) : ReadonlyArray < P2PPeerInfo > { const peerListMap = [ ] ; for ( const peerList of [ ... this . bucketIdToBucket . values ( ) ] ) { for ( const peer of [ ... peerList . values ( ) ] ) { peerListMap . push ( sanitizeEnhancedPeerInfo ( peer ) ) ; } } return peerListMap ; } public hasPeer ( incomingPeerId ) { return this . peerIdToPeerInfo . has ( incomingPeerId ) ; } public addPeer ( incomingPeerInfo ) { if ( this . hasPeer ( incomingPeerInfo . peerId ) ) { throw new ExistingPeerError ( incomingPeerInfo ) ; } const { bucketId , bucket } = this . calculateBucket ( incomingPeerInfo . ipAddress , this . type === PEER_TYPE . NEW_PEER ? incomingPeerInfo . sourceAddress : undefined ) ; const evictedPeer = bucket . size >= this . peerListConfig . bucketSize ? this . makeSpace ( bucket ) : undefined ; const internalPeerInfo = { ... incomingPeerInfo , numOfConnectionFailures : 0 , dateAdded : new Date ( ) , bucketId , } ; bucket . set ( incomingPeerInfo . peerId , internalPeerInfo ) ; this . peerIdToPeerInfo . set ( incomingPeerInfo . peerId , internalPeerInfo ) ; return evictedPeer ; } public getPeer ( incomingPeerId ) { const peerInfo = this . peerIdToPeerInfo . get ( incomingPeerId ) ; if ( ! peerInfo ) { return undefined ; } return sanitizeEnhancedPeerInfo ( peerInfo ) ; } public updatePeer ( incomingPeerInfo ) { const bucket = this . getBucket ( incomingPeerInfo . peerId ) ; if ( ! bucket ) { return false ; } const updatedInternalPeerInfo = { ... bucket . get ( incomingPeerInfo . peerId ) , ... incomingPeerInfo , } ; bucket . set ( incomingPeerInfo . peerId , updatedInternalPeerInfo ) ; this . peerIdToPeerInfo . set ( incomingPeerInfo . peerId , updatedInternalPeerInfo ) ; return true ; } public removePeer ( incomingPeerInfo ) { const bucket = this . getBucket ( incomingPeerInfo . peerId ) ; if ( bucket ?. has ( incomingPeerInfo . peerId ) ) { const removedFromBucket = bucket . delete ( incomingPeerInfo . peerId ) ; const removedFromPeerLookup = this . peerIdToPeerInfo . delete ( incomingPeerInfo . peerId ) ; return removedFromBucket && removedFromPeerLookup ; } return false ; } public makeSpace ( bucket ) { return evictPeerRandomlyFromBucket ( bucket ) ; } public failedConnectionAction ( incomingPeerInfo ) { return this . removePeer ( incomingPeerInfo ) ; } public calculateBucket ( targetAddress , sourceAddress ? ) { const bucketId = getBucketId ( { secret : this . peerListConfig . secret , peerType : this . peerListConfig . peerType , targetAddress , sourceAddress : this . type === PEER_TYPE . NEW_PEER ? sourceAddress : undefined , bucketCount : this . peerListConfig . numOfBuckets , } ) ; return { bucketId , bucket : this . bucketIdToBucket . get ( bucketId ) as Bucket } ; } protected getBucket ( peerId ) { const internalPeerInfo = this . peerIdToPeerInfo . get ( peerId ) ; if ( typeof internalPeerInfo ?. bucketId !== 'number' ) { return undefined ; } const bucket = this . bucketIdToBucket . get ( internalPeerInfo . bucketId ) ; if ( ! bucket ) { return undefined ; } return bucket ; } private _initBuckets ( ) { for ( const bucketId of [ ... new Array ( this . peerListConfig . numOfBuckets ) . keys ( ) ] ) { this . bucketIdToBucket . set ( bucketId , new Map < string , P2PEnhancedPeerInfo > ( ) ) ; } } }