From 30ec23a5bc2e983fe01e0e47e46ecedf4c0eab5d Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 16 Mar 2022 16:08:25 +0000 Subject: [PATCH] fix: update interfaces (#116) Updates to latest code from https://github.com/libp2p/js-libp2p-interfaces/pull/180 --- README.md | 4 +- package.json | 19 ++--- src/compat/index.ts | 25 ++++--- src/compat/querier.ts | 103 +++++++-------------------- src/compat/responder.ts | 94 ++++++++++++------------ src/compat/utils.ts | 81 +++++++++++++++++++++ src/index.ts | 43 ++++++----- src/query.ts | 26 ++++--- test/compat/go-multicast-dns.spec.ts | 86 +++++++++++----------- test/compat/querier.spec.ts | 44 +++++++----- test/compat/responder.spec.ts | 95 ++++++++++++------------ test/compliance.spec.ts | 17 +++-- test/multicast-dns.spec.ts | 52 +++++++------- 13 files changed, 365 insertions(+), 324 deletions(-) create mode 100644 src/compat/utils.ts diff --git a/README.md b/README.md index 1e8188f..c5d423c 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,12 @@ ## Usage ```JavaScript -const MDNS = require('libp2p-mdns') +import { MDNS } from '@libp2p/mdns' const mdns = new MDNS(options) mdns.on('peer', (peerData) => { - console.log('Found a peer in the local network', peerData.id.toString(base58btc), peerData.multiaddrs) + console.log('Found a peer in the local network', peerData.id.toString(), peerData.multiaddrs) }) // Broadcast for 20 seconds diff --git a/package.json b/package.json index 6cc006b..1622e6e 100644 --- a/package.json +++ b/package.json @@ -123,26 +123,27 @@ "dep-check": "aegir dep-check dist/src/**/*.js dist/test/**/*.js", "build": "tsc", "pretest": "npm run build", - "test": "aegir test -f ./dist/test", + "test": "aegir test -f ./dist/test/*.js -f ./dist/test/*/*.js", "test:node": "npm run test -- -t node --cov", "test:electron-main": "npm run test -- -t electron-main", "release": "semantic-release" }, "dependencies": { - "@libp2p/logger": "^1.0.2", - "@libp2p/peer-id": "^1.1.1", - "@multiformats/multiaddr": "^10.0.0", + "@libp2p/logger": "^1.1.2", + "@libp2p/peer-id": "^1.1.8", + "@multiformats/multiaddr": "^10.1.5", "multicast-dns": "^7.2.0", "multiformats": "^9.6.3" }, "devDependencies": { - "@libp2p/interface-compliance-tests": "^1.1.3", - "@libp2p/interfaces": "^1.3.3", - "@libp2p/peer-id-factory": "^1.0.4", + "@libp2p/interface-compliance-tests": "^1.1.16", + "@libp2p/interfaces": "^1.3.14", + "@libp2p/peer-id-factory": "^1.0.8", "@types/multicast-dns": "^7.2.1", - "aegir": "^36.1.0", + "aegir": "^36.1.3", "delay": "^5.0.0", "p-defer": "^4.0.0", - "p-wait-for": "^4.1.0" + "p-wait-for": "^4.1.0", + "ts-sinon": "^2.0.2" } } diff --git a/src/compat/index.ts b/src/compat/index.ts index 9e23c6d..ef2b7cc 100644 --- a/src/compat/index.ts +++ b/src/compat/index.ts @@ -2,27 +2,27 @@ import { EventEmitter, CustomEvent } from '@libp2p/interfaces' import { Responder } from './responder.js' import { Querier } from './querier.js' -import type { Multiaddr } from '@multiformats/multiaddr' -import type { PeerId } from '@libp2p/interfaces/peer-id' import type { PeerDiscovery, PeerDiscoveryEvents } from '@libp2p/interfaces/peer-discovery' +import type { Components, Initializable } from '@libp2p/interfaces/components' -export class GoMulticastDNS extends EventEmitter implements PeerDiscovery { +export interface GoMulticastDNSInit { + queryPeriod?: number + queryInterval?: number +} + +export class GoMulticastDNS extends EventEmitter implements PeerDiscovery, Initializable { private _started: boolean private readonly _responder: Responder private readonly _querier: Querier - constructor (options: { peerId: PeerId, multiaddrs: Multiaddr[], queryPeriod?: number, queryInterval?: number }) { + constructor (options: GoMulticastDNSInit = {}) { super() - const { peerId, multiaddrs, queryPeriod, queryInterval } = options + const { queryPeriod, queryInterval } = options this._started = false - this._responder = new Responder({ - peerId, - multiaddrs - }) + this._responder = new Responder() this._querier = new Querier({ - peerId, queryInterval, queryPeriod }) @@ -32,6 +32,11 @@ export class GoMulticastDNS extends EventEmitter implements }) } + init (components: Components): void { + this._responder.init(components) + this._querier.init(components) + } + isStarted () { return this._started } diff --git a/src/compat/querier.ts b/src/compat/querier.ts index ebaef73..1bb1273 100644 --- a/src/compat/querier.ts +++ b/src/compat/querier.ts @@ -1,19 +1,16 @@ -import { EventEmitter } from '@libp2p/interfaces' +import { CustomEvent, EventEmitter } from '@libp2p/interfaces' import MDNS from 'multicast-dns' -import { Multiaddr } from '@multiformats/multiaddr' -import type { PeerId } from '@libp2p/interfaces/peer-id' import { logger } from '@libp2p/logger' import { SERVICE_TAG_LOCAL, MULTICAST_IP, MULTICAST_PORT } from './constants.js' -import { base58btc } from 'multiformats/bases/base58' -import { peerIdFromString } from '@libp2p/peer-id' import type { PeerDiscovery, PeerDiscoveryEvents } from '@libp2p/interfaces/peer-discovery' import type { ResponsePacket } from 'multicast-dns' import type { RemoteInfo } from 'dgram' +import { Components, Initializable } from '@libp2p/interfaces/components' +import { findPeerDataInAnswers } from './utils.js' const log = logger('libp2p:mdns:compat:querier') -export interface QuerierOptions { - peerId: PeerId +export interface QuerierInit { queryInterval?: number queryPeriod?: number } @@ -22,24 +19,17 @@ export interface Handle { stop: () => Promise } -export class Querier extends EventEmitter implements PeerDiscovery { - private readonly _peerIdStr: string - private readonly _options: Required +export class Querier extends EventEmitter implements PeerDiscovery, Initializable { + private readonly _init: Required private _handle?: Handle + private components: Components = new Components() - constructor (options: QuerierOptions) { + constructor (init: QuerierInit = {}) { super() - const { peerId, queryInterval, queryPeriod } = options - - if (peerId == null) { - throw new Error('missing peerId parameter') - } - - this._peerIdStr = peerId.toString(base58btc) - this._options = { - peerId, + const { queryInterval, queryPeriod } = init + this._init = { // Re-query in leu of network change detection (every 60s by default) queryInterval: queryInterval ?? 60000, // Time for which the MDNS server will stay alive waiting for responses @@ -52,6 +42,10 @@ export class Querier extends EventEmitter implements PeerDi this._onResponse = this._onResponse.bind(this) } + init (components: Components): void { + this.components = components + } + isStarted () { return Boolean(this._handle) } @@ -83,76 +77,31 @@ export class Querier extends EventEmitter implements PeerDi } } }, { - period: this._options.queryPeriod, - interval: this._options.queryInterval + period: this._init.queryPeriod, + interval: this._init.queryInterval }) } _onResponse (event: ResponsePacket, info: RemoteInfo) { + log.trace('received mDNS query response') const answers = event.answers ?? [] - const ptrRecord = answers.find(a => a.type === 'PTR' && a.name === SERVICE_TAG_LOCAL) - - // Only deal with responses for our service tag - if (ptrRecord == null) return - - log('got response', event, info) - - const txtRecord = answers.find(a => a.type === 'TXT') - if (txtRecord == null || txtRecord.type !== 'TXT') { - return log('missing TXT record in response') - } - let peerIdStr - try { - peerIdStr = txtRecord.data[0].toString() - } catch (err) { - return log('failed to extract peer ID from TXT record data', txtRecord, err) - } - - if (this._peerIdStr === peerIdStr) { - return log('ignoring reply to myself') - } + const peerData = findPeerDataInAnswers(answers, this.components.getPeerId()) - let peerId - try { - peerId = peerIdFromString(peerIdStr) - } catch (err) { - return log('failed to create peer ID from TXT record data', peerIdStr, err) + if (peerData == null) { + log('could not read peer data from query response') + return } - const srvRecord = answers.find(a => a.type === 'SRV') - if (srvRecord == null || srvRecord.type !== 'SRV') { - return log('missing SRV record in response') + if (peerData.multiaddrs.length === 0) { + log('could not parse multiaddrs from mDNS response') + return } - log('peer found', peerIdStr) - - const { port } = srvRecord.data ?? {} - const protos = { A: 'ip4', AAAA: 'ip6' } - - const multiaddrs = answers - .filter(a => ['A', 'AAAA'].includes(a.type)) - .reduce((addrs, a) => { - if (a.type !== 'A' && a.type !== 'AAAA') { - return addrs - } - - const maStr = `/${protos[a.type]}/${a.data}/tcp/${port}` - try { - addrs.push(new Multiaddr(maStr)) - log(maStr) - } catch (err) { - log(`failed to create multiaddr from ${a.type} record data`, maStr, port, err) - } - return addrs - }, []) + log('discovered peer in mDNS qeury response %p', peerData.id) this.dispatchEvent(new CustomEvent('peer', { - detail: { - id: peerId, - multiaddrs, - protcols: [] - } + detail: peerData })) } diff --git a/src/compat/responder.ts b/src/compat/responder.ts index fefe145..beeff81 100644 --- a/src/compat/responder.ts +++ b/src/compat/responder.ts @@ -2,51 +2,44 @@ import OS from 'os' import MDNS, { QueryPacket } from 'multicast-dns' import { logger } from '@libp2p/logger' import { SERVICE_TAG_LOCAL } from './constants.js' -import type { PeerId } from '@libp2p/interfaces/peer-id' -import type { Multiaddr, MultiaddrObject } from '@multiformats/multiaddr' -import { base58btc } from 'multiformats/bases/base58' +import { MultiaddrObject, protocols } from '@multiformats/multiaddr' import type { RemoteInfo } from 'dgram' import type { Answer } from 'dns-packet' +import { Components, Initializable } from '@libp2p/interfaces/components' const log = logger('libp2p:mdns:compat:responder') -export interface ResponderOptions { - peerId: PeerId - multiaddrs: Multiaddr[] -} - -export class Responder { - private readonly _peerIdStr: string - private readonly _multiaddrs: Multiaddr[] +export class Responder implements Initializable { + private components: Components = new Components() private _mdns?: MDNS.MulticastDNS - constructor (options: ResponderOptions) { - const { peerId, multiaddrs } = options - - if (peerId == null) { - throw new Error('missing peerId parameter') - } - - this._peerIdStr = peerId.toString(base58btc) - this._multiaddrs = multiaddrs + constructor () { this._onQuery = this._onQuery.bind(this) } + init (components: Components): void { + this.components = components + } + start () { this._mdns = MDNS() this._mdns.on('query', this._onQuery) } _onQuery (event: QueryPacket, info: RemoteInfo) { - const addresses = this._multiaddrs.reduce((acc, addr) => { + const addresses = this.components.getAddressManager().getAddresses().reduce((acc, addr) => { + addr = addr.decapsulateCode(protocols('p2p').code) + if (addr.isThinWaistAddress()) { acc.push(addr.toOptions()) } + return acc }, []) // Only announce TCP for now if (addresses.length === 0) { + log('no tcp addresses configured so cannot respond to mDNS query') return } @@ -55,10 +48,10 @@ export class Responder { // Only respond to queries for our service tag if (!questions.some(q => q.name === SERVICE_TAG_LOCAL)) return - log('got query', event, info) + log.trace('got query', event, info) const answers: Answer[] = [] - const peerServiceTagLocal = `${this._peerIdStr}.${SERVICE_TAG_LOCAL}` + const peerServiceTagLocal = `${this.components.getPeerId().toString()}.${SERVICE_TAG_LOCAL}` answers.push({ name: SERVICE_TAG_LOCAL, @@ -68,44 +61,45 @@ export class Responder { data: peerServiceTagLocal }) - // Only announce TCP multiaddrs for now - const port = addresses[0].port - - answers.push({ - name: peerServiceTagLocal, - type: 'SRV', - class: 'IN', - ttl: 120, - data: { - priority: 10, - weight: 1, - port, - target: OS.hostname() - } - }) - answers.push({ name: peerServiceTagLocal, type: 'TXT', class: 'IN', ttl: 120, - data: [Buffer.from(this._peerIdStr)] + data: [Buffer.from(this.components.getPeerId().toString())] }) - addresses.forEach((ma) => { - if ([4, 6].includes(ma.family)) { - answers.push({ - name: OS.hostname(), - type: ma.family === 4 ? 'A' : 'AAAA', - class: 'IN', - ttl: 120, - data: ma.host - }) + addresses.forEach(ma => { + if (![4, 6].includes(ma.family)) { + return } + + answers.push({ + name: peerServiceTagLocal, + type: 'SRV', + class: 'IN', + ttl: 120, + data: { + priority: 10, + weight: 1, + port: ma.port, + target: OS.hostname() + } + }) + + answers.push({ + name: OS.hostname(), + type: ma.family === 4 ? 'A' : 'AAAA', + class: 'IN', + ttl: 120, + data: ma.host + }) }) if (this._mdns != null) { - log('responding to query', answers) + log.trace('responding to query') + log.trace('query answers', answers) + this._mdns.respond(answers, info) } } diff --git a/src/compat/utils.ts b/src/compat/utils.ts new file mode 100644 index 0000000..a6094ef --- /dev/null +++ b/src/compat/utils.ts @@ -0,0 +1,81 @@ +import type { PeerData } from '@libp2p/interfaces/peer-data' +import type { PeerId } from '@libp2p/interfaces/peer-id' +import { logger } from '@libp2p/logger' +import { peerIdFromString } from '@libp2p/peer-id' +import { Multiaddr } from '@multiformats/multiaddr' +import type { Answer } from 'dns-packet' +import { SERVICE_TAG_LOCAL } from './constants.js' + +const log = logger('libp2p:mdns:compat:utils') + +export function findPeerDataInAnswers (answers: Answer[], ourPeerId: PeerId): PeerData | undefined { + const ptrRecord = answers.find(a => a.type === 'PTR' && a.name === SERVICE_TAG_LOCAL) + + // Only deal with responses for our service tag + if (ptrRecord == null) { + return + } + + log.trace('got response', SERVICE_TAG_LOCAL) + + const txtRecord = answers.find(a => a.type === 'TXT') + if (txtRecord == null || txtRecord.type !== 'TXT') { + log('missing TXT record in response') + return + } + + let peerIdStr: string + try { + peerIdStr = txtRecord.data[0].toString() + } catch (err) { + log('failed to extract peer ID from TXT record data', txtRecord, err) + return + } + + let peerId: PeerId + try { + peerId = peerIdFromString(peerIdStr) + } catch (err) { + log('failed to create peer ID from TXT record data', peerIdStr, err) + return + } + + if (ourPeerId.equals(peerId)) { + log('ignoring reply to myself') + return + } + + const multiaddrs: Multiaddr[] = [] + const hosts: { A: Record, AAAA: Record } = { + A: {}, + AAAA: {} + } + + answers.forEach(answer => { + if (answer.type === 'A') { + hosts.A[answer.name] = answer.data + } + + if (answer.type === 'AAAA') { + hosts.AAAA[answer.name] = answer.data + } + }) + + answers.forEach(answer => { + if (answer.type === 'SRV') { + if (hosts.A[answer.data.target] != null) { + multiaddrs.push(new Multiaddr(`/ip4/${hosts.A[answer.data.target]}/tcp/${answer.data.port}/p2p/${peerId.toString()}`)) + } else if (hosts.AAAA[answer.data.target] != null) { + multiaddrs.push(new Multiaddr(`/ip6/${hosts.AAAA[answer.data.target]}/tcp/${answer.data.port}/p2p/${peerId.toString()}`)) + } else { + multiaddrs.push(new Multiaddr(`/dnsaddr/${answer.data.target}/tcp/${answer.data.port}/p2p/${peerId.toString()}`)) + } + } + }) + + return { + id: peerId, + multiaddrs, + protocols: [] + } +} diff --git a/src/index.ts b/src/index.ts index 023170d..5edb9cd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,26 +3,23 @@ import { CustomEvent, EventEmitter } from '@libp2p/interfaces' import { logger } from '@libp2p/logger' import * as query from './query.js' import { GoMulticastDNS } from './compat/index.js' -import type { PeerId } from '@libp2p/interfaces/peer-id' import type { PeerDiscovery, PeerDiscoveryEvents } from '@libp2p/interfaces/peer-discovery' -import type { Multiaddr } from '@multiformats/multiaddr' import type { PeerData } from '@libp2p/interfaces/peer-data' +import { Components, Initializable } from '@libp2p/interfaces/components' const log = logger('libp2p:mdns') export interface MulticastDNSOptions { - peerId: PeerId broadcast?: boolean interval?: number serviceTag?: string port?: number - multiaddrs?: Multiaddr[] compat?: boolean compatQueryPeriod?: number compatQueryInterval?: number } -export class MulticastDNS extends EventEmitter implements PeerDiscovery { +export class MulticastDNS extends EventEmitter implements PeerDiscovery, Initializable { static tag = 'mdns' public mdns?: multicastDNS.MulticastDNS @@ -31,24 +28,17 @@ export class MulticastDNS extends EventEmitter implements P private readonly interval: number private readonly serviceTag: string private readonly port: number - private readonly peerId: PeerId - private readonly peerMultiaddrs: Multiaddr[] // TODO: update this when multiaddrs change? - private _queryInterval: NodeJS.Timer | null + private _queryInterval: ReturnType | null private readonly _goMdns?: GoMulticastDNS + private components: Components = new Components() - constructor (options: MulticastDNSOptions) { + constructor (options: MulticastDNSOptions = {}) { super() - if (options.peerId == null) { - throw new Error('needs own PeerId to work') - } - this.broadcast = options.broadcast !== false this.interval = options.interval ?? (1e3 * 10) this.serviceTag = options.serviceTag ?? 'ipfs.local' this.port = options.port ?? 5353 - this.peerId = options.peerId - this.peerMultiaddrs = options.multiaddrs ?? [] this._queryInterval = null this._onPeer = this._onPeer.bind(this) this._onMdnsQuery = this._onMdnsQuery.bind(this) @@ -56,8 +46,6 @@ export class MulticastDNS extends EventEmitter implements P if (options.compat !== false) { this._goMdns = new GoMulticastDNS({ - multiaddrs: this.peerMultiaddrs, - peerId: options.peerId, queryPeriod: options.compatQueryPeriod, queryInterval: options.compatQueryInterval }) @@ -65,6 +53,12 @@ export class MulticastDNS extends EventEmitter implements P } } + init (components: Components): void { + this.components = components + + this._goMdns?.init(components) + } + isStarted () { return Boolean(this.mdns) } @@ -95,20 +89,25 @@ export class MulticastDNS extends EventEmitter implements P return } - query.gotQuery(event, this.mdns, this.peerId, this.peerMultiaddrs, this.serviceTag, this.broadcast) + log.trace('received incoming mDNS query') + query.gotQuery(event, this.mdns, this.components.getPeerId(), this.components.getAddressManager().getAddresses(), this.serviceTag, this.broadcast) } _onMdnsResponse (event: multicastDNS.ResponsePacket) { + log.trace('received mDNS query response') + try { - const foundPeer = query.gotResponse(event, this.peerId, this.serviceTag) + const foundPeer = query.gotResponse(event, this.components.getPeerId(), this.serviceTag) if (foundPeer != null) { - this.dispatchEvent(new CustomEvent('peer', { + log('discovered peer in mDNS qeury response %p', foundPeer.id) + + this.dispatchEvent(new CustomEvent('peer', { detail: foundPeer })) } } catch (err) { - log('Error processing peer response', err) + log.error('Error processing peer response', err) } } @@ -117,7 +116,7 @@ export class MulticastDNS extends EventEmitter implements P return } - this.dispatchEvent(new CustomEvent('peer', { + this.dispatchEvent(new CustomEvent('peer', { detail: evt.detail })) } diff --git a/src/query.ts b/src/query.ts index b5ef4d4..33e6492 100644 --- a/src/query.ts +++ b/src/query.ts @@ -1,18 +1,18 @@ import os from 'os' import { logger } from '@libp2p/logger' -import { Multiaddr, MultiaddrObject } from '@multiformats/multiaddr' -import { base58btc } from 'multiformats/bases/base58' +import { Multiaddr, MultiaddrObject, protocols } from '@multiformats/multiaddr' import { peerIdFromString } from '@libp2p/peer-id' import type { PeerId } from '@libp2p/interfaces/peer-id' import type { PeerData } from '@libp2p/interfaces/peer-data' import type { MulticastDNS, ResponsePacket, QueryPacket } from 'multicast-dns' import type { SrvAnswer, StringAnswer, TxtAnswer, Answer } from 'dns-packet' -const log = logger('libp2p:mdns') +const log = logger('libp2p:mdns:query') export function queryLAN (mdns: MulticastDNS, serviceTag: string, interval: number) { const query = () => { log('query', serviceTag) + mdns.query({ questions: [{ name: serviceTag, @@ -82,14 +82,16 @@ export function gotResponse (rsp: ResponsePacket, localPeerId: PeerId, serviceTa } }) - if (localPeerId.toString(base58btc) === b58Id) { + if (localPeerId.toString() === b58Id) { return // replied to myself, ignore } - log('peer found -', b58Id) + const id = peerIdFromString(b58Id) + + log('peer found %p', id) return { - id: peerIdFromString(b58Id), + id, multiaddrs, protocols: [] } @@ -97,11 +99,12 @@ export function gotResponse (rsp: ResponsePacket, localPeerId: PeerId, serviceTa export function gotQuery (qry: QueryPacket, mdns: MulticastDNS, peerId: PeerId, multiaddrs: Multiaddr[], serviceTag: string, broadcast: boolean) { if (!broadcast) { + log('not responding to mDNS query as broadcast mode is false') return } const addresses: MultiaddrObject[] = multiaddrs.reduce((acc, addr) => { - if (addr.isThinWaistAddress()) { + if (addr.decapsulateCode(protocols('p2p').code).isThinWaistAddress()) { acc.push(addr.toOptions()) } return acc @@ -109,6 +112,7 @@ export function gotQuery (qry: QueryPacket, mdns: MulticastDNS, peerId: PeerId, // Only announce TCP for now if (addresses.length === 0) { + log('no thin waist addresses present, cannot respond to query') return } @@ -120,14 +124,14 @@ export function gotQuery (qry: QueryPacket, mdns: MulticastDNS, peerId: PeerId, type: 'PTR', class: 'IN', ttl: 120, - data: peerId.toString(base58btc) + '.' + serviceTag + data: peerId.toString() + '.' + serviceTag }) // Only announce TCP multiaddrs for now const port = addresses[0].port answers.push({ - name: peerId.toString(base58btc) + '.' + serviceTag, + name: peerId.toString() + '.' + serviceTag, type: 'SRV', class: 'IN', ttl: 120, @@ -140,11 +144,11 @@ export function gotQuery (qry: QueryPacket, mdns: MulticastDNS, peerId: PeerId, }) answers.push({ - name: peerId.toString(base58btc) + '.' + serviceTag, + name: peerId.toString() + '.' + serviceTag, type: 'TXT', class: 'IN', ttl: 120, - data: peerId.toString(base58btc) + data: peerId.toString() }) addresses.forEach((addr) => { diff --git a/test/compat/go-multicast-dns.spec.ts b/test/compat/go-multicast-dns.spec.ts index 8473dc9..ce9f5e0 100644 --- a/test/compat/go-multicast-dns.spec.ts +++ b/test/compat/go-multicast-dns.spec.ts @@ -3,38 +3,46 @@ import { expect } from 'aegir/utils/chai.js' import { Multiaddr } from '@multiformats/multiaddr' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import pDefer from 'p-defer' -import type { PeerId } from '@libp2p/interfaces/peer-id' import { GoMulticastDNS } from '../../src/compat/index.js' +import { Components } from '@libp2p/interfaces/components' +import { stubInterface } from 'ts-sinon' +import type { AddressManager } from '@libp2p/interfaces' +import type { PeerData } from '@libp2p/interfaces/peer-data' -describe('GoMulticastDNS', () => { - const peerAddrs = [ - new Multiaddr('/ip4/127.0.0.1/tcp/20001'), - new Multiaddr('/ip4/127.0.0.1/tcp/20002') - ] - let peerIds: PeerId[] - - before(async () => { - peerIds = await Promise.all([ - createEd25519PeerId(), - createEd25519PeerId() - ]) +let port = 20000 + +async function createGoMulticastDNS () { + const peerId = await createEd25519PeerId() + const addressManager = stubInterface() + addressManager.getAddresses.returns([ + new Multiaddr(`/ip4/127.0.0.1/tcp/${port++}/p2p/${peerId.toString()}`), + new Multiaddr(`/ip4/127.0.0.1/tcp/${port++}/p2p/${peerId.toString()}`) + ]) + + const components = new Components({ + peerId, + addressManager }) + const mdns = new GoMulticastDNS() + mdns.init(components) + + return { + mdns, + components + } +} + +describe('GoMulticastDNS', () => { it('should start and stop', async () => { - const mdns = new GoMulticastDNS({ - peerId: peerIds[0], - multiaddrs: [peerAddrs[0]] - }) + const { mdns } = await createGoMulticastDNS() await mdns.start() return await mdns.stop() }) it('should ignore multiple start calls', async () => { - const mdns = new GoMulticastDNS({ - peerId: peerIds[0], - multiaddrs: [peerAddrs[0]] - }) + const { mdns } = await createGoMulticastDNS() await mdns.start() await mdns.start() @@ -43,46 +51,38 @@ describe('GoMulticastDNS', () => { }) it('should ignore unnecessary stop calls', async () => { - const mdns = new GoMulticastDNS({ - peerId: peerIds[0], - multiaddrs: [peerAddrs[0]] - }) + const { mdns } = await createGoMulticastDNS() + await mdns.stop() }) it('should emit peer data when peer is discovered', async () => { - const mdnsA = new GoMulticastDNS({ - peerId: peerIds[0], - multiaddrs: [peerAddrs[0]] - }) - const mdnsB = new GoMulticastDNS({ - peerId: peerIds[1], - multiaddrs: [peerAddrs[1]] - }) - const defer = pDefer() + const { mdns: mdnsA } = await createGoMulticastDNS() + const { mdns: mdnsB, components: componentsB } = await createGoMulticastDNS() + const defer = pDefer() mdnsA.addEventListener('peer', (evt) => { - const { id, multiaddrs } = evt.detail + const { id } = evt.detail - if (!peerIds[1].equals(id)) { + if (!componentsB.getPeerId().equals(id)) { return } - expect(multiaddrs.some((m) => m.equals(peerAddrs[1]))).to.be.true() - defer.resolve() + defer.resolve(evt.detail) }) // Start in series - void Promise.all([ - mdnsA.start(), - mdnsB.start() - ]) + await mdnsA.start() + await mdnsB.start() - await defer.promise + const peerData = await defer.promise await Promise.all([ mdnsA.stop(), mdnsB.stop() ]) + + expect(peerData.id.equals(componentsB.getPeerId())).to.be.true() + expect(peerData.multiaddrs.map(ma => ma.toString())).includes(componentsB.getAddressManager().getAddresses()[1].toString()) }) }) diff --git a/test/compat/querier.spec.ts b/test/compat/querier.spec.ts index 1737680..292eadb 100644 --- a/test/compat/querier.spec.ts +++ b/test/compat/querier.spec.ts @@ -7,9 +7,9 @@ import delay from 'delay' import { Querier } from '../../src/compat/querier.js' import { SERVICE_TAG_LOCAL } from '../../src/compat/constants.js' import type { PeerId } from '@libp2p/interfaces/peer-id' -import { base58btc } from 'multiformats/bases/base58' import type { RemoteInfo } from 'dgram' import type { Answer } from 'dns-packet' +import { Components } from '@libp2p/interfaces/components' describe('Querier', () => { let querier: Querier @@ -35,14 +35,17 @@ describe('Querier', () => { }) it('should start and stop', async () => { - const querier = new Querier({ peerId: peerIds[0] }) + querier = new Querier() + querier.init(new Components({ peerId: peerIds[0] })) await querier.start() await querier.stop() }) it('should query on interval', async () => { - querier = new Querier({ peerId: peerIds[0], queryPeriod: 0, queryInterval: 10 }) + querier = new Querier({ queryPeriod: 0, queryInterval: 10 }) + querier.init(new Components({ peerId: peerIds[0] })) + mdns = MDNS() let queryCount = 0 @@ -61,7 +64,7 @@ describe('Querier', () => { it('should not emit peer for responses with non matching service tags', async () => { return await ensureNoPeer(event => { - const peerServiceTagLocal = `${peerIds[1].toString(base58btc)}.${SERVICE_TAG_LOCAL}` + const peerServiceTagLocal = `${peerIds[1].toString()}.${SERVICE_TAG_LOCAL}` const bogusServiceTagLocal = '_ifps-discovery._udp' return [{ @@ -76,7 +79,7 @@ describe('Querier', () => { it('should not emit peer for responses with missing TXT record', async () => { return await ensureNoPeer(event => { - const peerServiceTagLocal = `${peerIds[1].toString(base58btc)}.${SERVICE_TAG_LOCAL}` + const peerServiceTagLocal = `${peerIds[1].toString()}.${SERVICE_TAG_LOCAL}` return [{ name: SERVICE_TAG_LOCAL, @@ -90,7 +93,7 @@ describe('Querier', () => { it('should not emit peer for responses with missing peer ID in TXT record', async () => { return await ensureNoPeer(event => { - const peerServiceTagLocal = `${peerIds[1].toString(base58btc)}.${SERVICE_TAG_LOCAL}` + const peerServiceTagLocal = `${peerIds[1].toString()}.${SERVICE_TAG_LOCAL}` return [{ name: SERVICE_TAG_LOCAL, @@ -110,7 +113,7 @@ describe('Querier', () => { it('should not emit peer for responses to self', async () => { return await ensureNoPeer(event => { - const peerServiceTagLocal = `${peerIds[1].toString(base58btc)}.${SERVICE_TAG_LOCAL}` + const peerServiceTagLocal = `${peerIds[1].toString()}.${SERVICE_TAG_LOCAL}` return [{ name: SERVICE_TAG_LOCAL, @@ -123,15 +126,15 @@ describe('Querier', () => { type: 'TXT', class: 'IN', ttl: 120, - data: peerIds[0].toString(base58btc) + data: peerIds[0].toString() }] }) }) // TODO: unskip when https://github.com/libp2p/js-peer-id/issues/83 is resolved - it.skip('should not emit peer for responses with invalid peer ID in TXT record', async () => { + it('should not emit peer for responses with invalid peer ID in TXT record', async () => { return await ensureNoPeer(event => { - const peerServiceTagLocal = `${peerIds[1].toString(base58btc)}.${SERVICE_TAG_LOCAL}` + const peerServiceTagLocal = `${peerIds[1].toString()}.${SERVICE_TAG_LOCAL}` return [{ name: SERVICE_TAG_LOCAL, @@ -151,7 +154,7 @@ describe('Querier', () => { it('should not emit peer for responses with missing SRV record', async () => { return await ensureNoPeer(event => { - const peerServiceTagLocal = `${peerIds[1].toString(base58btc)}.${SERVICE_TAG_LOCAL}` + const peerServiceTagLocal = `${peerIds[1].toString()}.${SERVICE_TAG_LOCAL}` return [{ name: SERVICE_TAG_LOCAL, @@ -164,14 +167,14 @@ describe('Querier', () => { type: 'TXT', class: 'IN', ttl: 120, - data: peerIds[1].toString(base58btc) + data: peerIds[1].toString() }] }) }) it('should emit peer for responses even if no multiaddrs', async () => { return await ensurePeer(event => { - const peerServiceTagLocal = `${peerIds[1].toString(base58btc)}.${SERVICE_TAG_LOCAL}` + const peerServiceTagLocal = `${peerIds[1].toString()}.${SERVICE_TAG_LOCAL}` return [{ name: SERVICE_TAG_LOCAL, @@ -184,7 +187,7 @@ describe('Querier', () => { type: 'TXT', class: 'IN', ttl: 120, - data: peerIds[1].toString(base58btc) + data: peerIds[1].toString() }, { name: peerServiceTagLocal, type: 'SRV', @@ -202,7 +205,7 @@ describe('Querier', () => { it('should emit peer for responses with valid multiaddrs', async () => { return await ensurePeer(event => { - const peerServiceTagLocal = `${peerIds[1].toString(base58btc)}.${SERVICE_TAG_LOCAL}` + const peerServiceTagLocal = `${peerIds[1].toString()}.${SERVICE_TAG_LOCAL}` return [{ name: SERVICE_TAG_LOCAL, @@ -215,7 +218,7 @@ describe('Querier', () => { type: 'TXT', class: 'IN', ttl: 120, - data: peerIds[1].toString(base58btc) + data: peerIds[1].toString() }, { name: peerServiceTagLocal, type: 'SRV', @@ -243,7 +246,8 @@ describe('Querier', () => { * @param {Function} getResponse - Given a query, construct a response to test the querier */ async function ensurePeer (getResponse: (event: QueryPacket, info: RemoteInfo) => Answer[]) { - querier = new Querier({ peerId: peerIds[0] }) + const querier = new Querier() + querier.init(new Components({ peerId: peerIds[0] })) mdns = MDNS() mdns.on('query', (event, info) => { @@ -266,6 +270,8 @@ describe('Querier', () => { await querier.start() await delay(100) + await querier.stop() + if (peerId == null) { throw new Error('Missing peer') } @@ -277,7 +283,8 @@ describe('Querier', () => { * @param {Function} getResponse - Given a query, construct a response to test the querier */ async function ensureNoPeer (getResponse: (event: QueryPacket, info: RemoteInfo) => Answer[]) { - querier = new Querier({ peerId: peerIds[0] }) + const querier = new Querier() + querier.init(new Components({ peerId: peerIds[0] })) mdns = MDNS() mdns.on('query', (event, info) => { @@ -305,6 +312,7 @@ describe('Querier', () => { await querier.start() await delay(100) + await querier.stop() if (peerId == null) { return diff --git a/test/compat/responder.spec.ts b/test/compat/responder.spec.ts index bfba7ac..af89440 100644 --- a/test/compat/responder.spec.ts +++ b/test/compat/responder.spec.ts @@ -3,29 +3,41 @@ import { expect } from 'aegir/utils/chai.js' import { Multiaddr } from '@multiformats/multiaddr' import { createEd25519PeerId } from '@libp2p/peer-id-factory' -import MDNS from 'multicast-dns' +import mDNS from 'multicast-dns' import delay from 'delay' import pDefer from 'p-defer' import { Responder } from '../../src/compat/responder.js' import { SERVICE_TAG_LOCAL, MULTICAST_IP, MULTICAST_PORT } from '../../src/compat/constants.js' -import { base58btc } from 'multiformats/bases/base58' import type { PeerId } from '@libp2p/interfaces/peer-id' import type { ResponsePacket } from 'multicast-dns' +import { Components } from '@libp2p/interfaces/components' +import { stubInterface } from 'ts-sinon' +import { findPeerDataInAnswers } from '../../src/compat/utils.js' +import type { AddressManager } from '@libp2p/interfaces' +import type { PeerData } from '@libp2p/interfaces/peer-data' describe('Responder', () => { let responder: Responder - let mdns: MDNS.MulticastDNS - const peerAddrs = [ - new Multiaddr('/ip4/127.0.0.1/tcp/20001'), - new Multiaddr('/ip4/127.0.0.1/tcp/20002') - ] + let mdns: mDNS.MulticastDNS let peerIds: PeerId[] + let components: Components + let multiadddrs: Multiaddr[] - before(async () => { + beforeEach(async () => { peerIds = await Promise.all([ createEd25519PeerId(), createEd25519PeerId() ]) + + multiadddrs = [ + new Multiaddr(`/ip4/127.0.0.1/tcp/20001/p2p/${peerIds[0].toString()}`), + new Multiaddr(`/ip4/127.0.0.1/tcp/20002/p2p/${peerIds[0].toString()}`) + ] + + const addressManager = stubInterface() + addressManager.getAddresses.returns(multiadddrs) + + components = new Components({ peerId: peerIds[0], addressManager }) }) afterEach(async () => { @@ -36,10 +48,8 @@ describe('Responder', () => { }) it('should start and stop', async () => { - const responder = new Responder({ - peerId: peerIds[0], - multiaddrs: [peerAddrs[0]] - }) + responder = new Responder() + responder.init(components) await responder.start() await responder.stop() @@ -47,11 +57,10 @@ describe('Responder', () => { it('should not respond to a query if no TCP addresses', async () => { const peerId = await createEd25519PeerId() - responder = new Responder({ - peerId, - multiaddrs: [] - }) - mdns = MDNS({ multicast: false, interface: '0.0.0.0', port: 0 }) + responder = new Responder() + components.getAddressManager().getAddresses = () => [] + responder.init(components) + mdns = mDNS({ multicast: false, interface: '0.0.0.0', port: 0 }) await responder.start() @@ -76,11 +85,9 @@ describe('Responder', () => { }) it('should not respond to a query with non matching service tag', async () => { - responder = new Responder({ - peerId: peerIds[0], - multiaddrs: [peerAddrs[0]] - }) - mdns = MDNS({ multicast: false, interface: '0.0.0.0', port: 0 }) + responder = new Responder() + responder.init(components) + mdns = mDNS({ multicast: false, interface: '0.0.0.0', port: 0 }) await responder.start() @@ -107,43 +114,24 @@ describe('Responder', () => { }) it('should respond correctly', async () => { - responder = new Responder({ - peerId: peerIds[0], - multiaddrs: [peerAddrs[0]] - }) - mdns = MDNS({ multicast: false, interface: '0.0.0.0', port: 0 }) - + responder = new Responder() + responder.init(components) await responder.start() - const defer = pDefer() + const defer = pDefer() + mdns = mDNS({ multicast: false, interface: '0.0.0.0', port: 0 }) mdns.on('response', event => { if (!isResponseFrom(event, peerIds[0])) { return } - const srvRecord = event.answers.find(a => a.type === 'SRV') - if (srvRecord == null || srvRecord.type !== 'SRV') { - return defer.reject(new Error('Missing SRV record')) - } - - const { port } = srvRecord.data ?? {} - const protos = { A: 'ip4', AAAA: 'ip6' } - - const addrs = event.answers - .filter(a => ['A', 'AAAA'].includes(a.type)) - .map(a => { - if (a.type !== 'A' && a.type !== 'AAAA') { - throw new Error('Incorrect type') - } + const peerData = findPeerDataInAnswers(event.answers, peerIds[1]) - return `/${protos[a.type]}/${a.data}/tcp/${port}` - }) - - if (!addrs.includes(peerAddrs[0].toString())) { - return defer.reject(new Error(`Missing peer address in response: ${peerAddrs[0].toString()}`)) + if (peerData == null) { + return defer.reject(new Error('Could not read PeerData from mDNS query response')) } - defer.resolve() + defer.resolve(peerData) }) mdns.query({ @@ -154,7 +142,10 @@ describe('Responder', () => { port: MULTICAST_PORT }) - await defer.promise + const peerData = await defer.promise + + expect(peerData.multiaddrs.map(ma => ma.toString())).to.include(multiadddrs[0].toString()) + expect(peerData.multiaddrs.map(ma => ma.toString())).to.include(multiadddrs[1].toString()) }) }) @@ -176,7 +167,9 @@ function isResponseFrom (res: ResponsePacket, fromPeerId: PeerId) { } // Ignore response from someone else - if (fromPeerId.toString(base58btc) !== peerIdStr) return false + if (fromPeerId.toString() !== peerIdStr) { + return false + } return true } diff --git a/test/compliance.spec.ts b/test/compliance.spec.ts index 13bf6eb..46d0fee 100644 --- a/test/compliance.spec.ts +++ b/test/compliance.spec.ts @@ -4,25 +4,34 @@ import tests from '@libp2p/interface-compliance-tests/peer-discovery' import { Multiaddr } from '@multiformats/multiaddr' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { MulticastDNS } from '../src/index.js' -import { CustomEvent } from '@libp2p/interfaces' +import { AddressManager, CustomEvent } from '@libp2p/interfaces' +import { Components } from '@libp2p/interfaces/components' +import { stubInterface } from 'ts-sinon' let mdns: MulticastDNS describe('compliance tests', () => { - let intervalId: NodeJS.Timer + let intervalId: ReturnType tests({ async setup () { const peerId1 = await createEd25519PeerId() const peerId2 = await createEd25519PeerId() + const addressManager = stubInterface() + addressManager.getAddresses.returns([ + new Multiaddr(`/ip4/127.0.0.1/tcp/13921/p2p/${peerId1.toString()}`) + ]) + mdns = new MulticastDNS({ - peerId: peerId1, - multiaddrs: [], broadcast: false, port: 50001, compat: true }) + mdns.init(new Components({ + peerId: peerId1, + addressManager + })) // Trigger discovery const maStr = '/ip4/127.0.0.1/tcp/15555/ws/p2p-webrtc-star/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSooo2d' diff --git a/test/multicast-dns.spec.ts b/test/multicast-dns.spec.ts index 8603fa3..ca3a271 100644 --- a/test/multicast-dns.spec.ts +++ b/test/multicast-dns.spec.ts @@ -5,9 +5,18 @@ import { Multiaddr } from '@multiformats/multiaddr' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import pWaitFor from 'p-wait-for' import { MulticastDNS } from './../src/index.js' -import { base58btc } from 'multiformats/bases/base58' import type { PeerId } from '@libp2p/interfaces/peer-id' import type { PeerData } from '@libp2p/interfaces/peer-data' +import { stubInterface } from 'ts-sinon' +import type { AddressManager } from '@libp2p/interfaces' +import { Components } from '@libp2p/interfaces/components' + +function getComponents (peerId: PeerId, multiaddrs: Multiaddr[]) { + const addressManager = stubInterface() + addressManager.getAddresses.returns(multiaddrs) + + return new Components({ peerId, addressManager }) +} describe('MulticastDNS', () => { let pA: PeerId @@ -56,19 +65,17 @@ describe('MulticastDNS', () => { this.timeout(40 * 1000) const mdnsA = new MulticastDNS({ - peerId: pA, - multiaddrs: aMultiaddrs, broadcast: false, // do not talk to ourself port: 50001, compat: false }) + mdnsA.init(getComponents(pA, aMultiaddrs)) const mdnsB = new MulticastDNS({ - peerId: pB, - multiaddrs: bMultiaddrs, port: 50001, // port must be the same compat: false }) + mdnsB.init(getComponents(pB, bMultiaddrs)) await mdnsA.start() await mdnsB.start() @@ -77,7 +84,7 @@ describe('MulticastDNS', () => { once: true })) - expect(pB.toString(base58btc)).to.eql(id.toString(base58btc)) + expect(pB.toString()).to.eql(id.toString()) await Promise.all([mdnsA.stop(), mdnsB.stop()]) }) @@ -86,33 +93,30 @@ describe('MulticastDNS', () => { this.timeout(40 * 1000) const mdnsA = new MulticastDNS({ - peerId: pA, - multiaddrs: aMultiaddrs, broadcast: false, // do not talk to ourself port: 50003, compat: false }) + mdnsA.init(getComponents(pA, aMultiaddrs)) const mdnsC = new MulticastDNS({ - peerId: pC, - multiaddrs: cMultiaddrs, port: 50003, // port must be the same compat: false }) + mdnsC.init(getComponents(pC, cMultiaddrs)) const mdnsD = new MulticastDNS({ - peerId: pD, - multiaddrs: dMultiaddrs, port: 50003, // port must be the same compat: false }) + mdnsD.init(getComponents(pD, dMultiaddrs)) await mdnsA.start() await mdnsC.start() await mdnsD.start() const peers = new Map() - const expectedPeer = pC.toString(base58btc) + const expectedPeer = pC.toString() - const foundPeer = (evt: CustomEvent) => peers.set(evt.detail.id.toString(base58btc), evt.detail) + const foundPeer = (evt: CustomEvent) => peers.set(evt.detail.id.toString(), evt.detail) mdnsA.addEventListener('peer', foundPeer) await pWaitFor(() => peers.has(expectedPeer)) @@ -131,19 +135,17 @@ describe('MulticastDNS', () => { this.timeout(40 * 1000) const mdnsA = new MulticastDNS({ - peerId: pA, - multiaddrs: aMultiaddrs, broadcast: false, // do not talk to ourself port: 50001, compat: false }) + mdnsA.init(getComponents(pA, aMultiaddrs)) const mdnsB = new MulticastDNS({ - peerId: pB, - multiaddrs: bMultiaddrs, port: 50001, compat: false }) + mdnsB.init(getComponents(pB, bMultiaddrs)) await mdnsA.start() await mdnsB.start() @@ -152,7 +154,7 @@ describe('MulticastDNS', () => { once: true })) - expect(pB.toString(base58btc)).to.eql(id.toString(base58btc)) + expect(pB.toString()).to.eql(id.toString()) expect(multiaddrs.length).to.equal(2) await Promise.all([mdnsA.stop(), mdnsB.stop()]) @@ -162,18 +164,16 @@ describe('MulticastDNS', () => { this.timeout(40 * 1000) const mdnsA = new MulticastDNS({ - peerId: pA, - multiaddrs: aMultiaddrs, port: 50004, // port must be the same compat: false }) + mdnsA.init(getComponents(pA, aMultiaddrs)) const mdnsC = new MulticastDNS({ - peerId: pC, - multiaddrs: cMultiaddrs, port: 50004, compat: false }) + mdnsC.init(getComponents(pD, dMultiaddrs)) await mdnsA.start() await new Promise((resolve) => setTimeout(resolve, 1000)) @@ -192,10 +192,9 @@ describe('MulticastDNS', () => { it('should start and stop with go-libp2p-mdns compat', async () => { const mdns = new MulticastDNS({ - peerId: pA, - multiaddrs: aMultiaddrs, port: 50004 }) + mdns.init(getComponents(pA, aMultiaddrs)) await mdns.start() await mdns.stop() @@ -203,10 +202,9 @@ describe('MulticastDNS', () => { it('should not emit undefined peer ids', async () => { const mdns = new MulticastDNS({ - peerId: pA, - multiaddrs: aMultiaddrs, port: 50004 }) + mdns.init(getComponents(pA, aMultiaddrs)) await mdns.start() await new Promise((resolve, reject) => {