2019-08-16 17:30:03 +02:00
|
|
|
'use strict'
|
|
|
|
|
2019-11-07 12:11:50 +01:00
|
|
|
const debug = require('debug')
|
2020-06-24 15:35:18 +02:00
|
|
|
const log = debug('libp2p:identify')
|
|
|
|
log.error = debug('libp2p:identify:error')
|
|
|
|
|
|
|
|
const errCode = require('err-code')
|
2019-11-07 12:11:50 +01:00
|
|
|
const pb = require('it-protocol-buffers')
|
|
|
|
const lp = require('it-length-prefixed')
|
|
|
|
const pipe = require('it-pipe')
|
2020-02-05 17:35:27 +01:00
|
|
|
const { collect, take, consume } = require('streaming-iterables')
|
2020-08-24 11:58:02 +01:00
|
|
|
const uint8ArrayFromString = require('uint8arrays/from-string')
|
2019-11-07 12:11:50 +01:00
|
|
|
|
|
|
|
const PeerId = require('peer-id')
|
|
|
|
const multiaddr = require('multiaddr')
|
2019-12-10 19:26:54 +01:00
|
|
|
const { toBuffer } = require('it-buffer')
|
2019-11-07 12:11:50 +01:00
|
|
|
|
|
|
|
const Message = require('./message')
|
|
|
|
|
2020-06-24 15:35:18 +02:00
|
|
|
const Envelope = require('../record/envelope')
|
|
|
|
const PeerRecord = require('../record/peer-record')
|
2019-11-07 12:11:50 +01:00
|
|
|
|
|
|
|
const {
|
|
|
|
MULTICODEC_IDENTIFY,
|
|
|
|
MULTICODEC_IDENTIFY_PUSH,
|
|
|
|
AGENT_VERSION,
|
|
|
|
PROTOCOL_VERSION
|
|
|
|
} = require('./consts')
|
|
|
|
|
2020-07-15 15:47:45 +02:00
|
|
|
const { codes } = require('../errors')
|
2019-11-07 12:11:50 +01:00
|
|
|
|
|
|
|
class IdentifyService {
|
|
|
|
/**
|
|
|
|
* Takes the `addr` and converts it to a Multiaddr if possible
|
2020-10-06 14:59:43 +02:00
|
|
|
*
|
|
|
|
* @param {Uint8Array | string} addr
|
2019-11-07 12:11:50 +01:00
|
|
|
* @returns {Multiaddr|null}
|
|
|
|
*/
|
|
|
|
static getCleanMultiaddr (addr) {
|
|
|
|
if (addr && addr.length > 0) {
|
|
|
|
try {
|
|
|
|
return multiaddr(addr)
|
|
|
|
} catch (_) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-10-06 14:59:43 +02:00
|
|
|
* @class
|
2019-11-07 12:11:50 +01:00
|
|
|
* @param {object} options
|
2020-04-18 23:26:46 +02:00
|
|
|
* @param {Libp2p} options.libp2p
|
2020-10-06 14:59:43 +02:00
|
|
|
* @param {Map<string, handler>} options.protocols - A reference to the protocols we support
|
2019-11-07 12:11:50 +01:00
|
|
|
*/
|
2020-04-28 16:21:25 +02:00
|
|
|
constructor ({ libp2p, protocols }) {
|
2019-11-07 12:11:50 +01:00
|
|
|
/**
|
2020-04-18 17:06:56 +02:00
|
|
|
* @property {PeerStore}
|
2019-11-07 12:11:50 +01:00
|
|
|
*/
|
2020-04-28 16:21:25 +02:00
|
|
|
this.peerStore = libp2p.peerStore
|
2020-04-18 17:06:56 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @property {ConnectionManager}
|
|
|
|
*/
|
2020-04-28 16:21:25 +02:00
|
|
|
this.connectionManager = libp2p.connectionManager
|
2020-04-18 23:26:46 +02:00
|
|
|
|
2019-11-07 12:11:50 +01:00
|
|
|
/**
|
2020-04-14 14:05:30 +02:00
|
|
|
* @property {PeerId}
|
2019-11-07 12:11:50 +01:00
|
|
|
*/
|
2020-04-28 16:21:25 +02:00
|
|
|
this.peerId = libp2p.peerId
|
2020-04-14 14:05:30 +02:00
|
|
|
|
2020-04-18 23:26:46 +02:00
|
|
|
/**
|
|
|
|
* @property {AddressManager}
|
|
|
|
*/
|
2020-04-28 16:21:25 +02:00
|
|
|
this._libp2p = libp2p
|
2019-11-07 12:11:50 +01:00
|
|
|
|
2020-04-28 16:21:25 +02:00
|
|
|
this._protocols = protocols
|
2019-11-07 12:11:50 +01:00
|
|
|
|
|
|
|
this.handleMessage = this.handleMessage.bind(this)
|
2020-09-23 13:14:53 +02:00
|
|
|
|
|
|
|
this.connectionManager.on('peer:connect', (connection) => {
|
|
|
|
const peerId = connection.remotePeer
|
|
|
|
|
|
|
|
this.identify(connection, peerId).catch(log.error)
|
|
|
|
})
|
|
|
|
|
2020-09-23 18:45:01 +02:00
|
|
|
// When self multiaddrs change, trigger identify-push
|
|
|
|
this.peerStore.on('change:multiaddrs', ({ peerId }) => {
|
|
|
|
if (peerId.toString() === this.peerId.toString()) {
|
|
|
|
this.pushToPeerStore()
|
|
|
|
}
|
2020-09-23 13:14:53 +02:00
|
|
|
})
|
2019-11-07 12:11:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Send an Identify Push update to the list of connections
|
2020-10-06 14:59:43 +02:00
|
|
|
*
|
2019-11-07 12:11:50 +01:00
|
|
|
* @param {Array<Connection>} connections
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
2020-07-15 17:42:05 +02:00
|
|
|
async push (connections) {
|
2020-09-23 18:45:01 +02:00
|
|
|
const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId)
|
2020-08-24 11:58:02 +01:00
|
|
|
const listenAddrs = this._libp2p.multiaddrs.map((ma) => ma.bytes)
|
2020-07-15 17:42:05 +02:00
|
|
|
const protocols = Array.from(this._protocols.keys())
|
|
|
|
|
2019-11-07 12:11:50 +01:00
|
|
|
const pushes = connections.map(async connection => {
|
|
|
|
try {
|
2020-07-15 15:47:45 +02:00
|
|
|
const { stream } = await connection.newStream(MULTICODEC_IDENTIFY_PUSH)
|
2019-11-07 12:11:50 +01:00
|
|
|
|
|
|
|
await pipe(
|
|
|
|
[{
|
2020-07-15 17:42:05 +02:00
|
|
|
listenAddrs,
|
2020-06-20 19:32:39 +02:00
|
|
|
signedPeerRecord,
|
2020-07-15 17:42:05 +02:00
|
|
|
protocols
|
2019-11-07 12:11:50 +01:00
|
|
|
}],
|
|
|
|
pb.encode(Message),
|
2020-02-05 17:35:27 +01:00
|
|
|
stream,
|
|
|
|
consume
|
2019-11-07 12:11:50 +01:00
|
|
|
)
|
|
|
|
} catch (err) {
|
|
|
|
// Just log errors
|
|
|
|
log.error('could not push identify update to peer', err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return Promise.all(pushes)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calls `push` for all peers in the `peerStore` that are connected
|
2020-10-07 17:29:42 +02:00
|
|
|
*
|
2020-09-16 16:43:09 +02:00
|
|
|
* @returns {void}
|
2019-11-07 12:11:50 +01:00
|
|
|
*/
|
2020-09-16 16:43:09 +02:00
|
|
|
pushToPeerStore () {
|
2019-11-07 12:11:50 +01:00
|
|
|
const connections = []
|
|
|
|
let connection
|
2020-09-16 16:43:09 +02:00
|
|
|
for (const peer of this.peerStore.peers.values()) {
|
2020-04-18 17:06:56 +02:00
|
|
|
if (peer.protocols.includes(MULTICODEC_IDENTIFY_PUSH) && (connection = this.connectionManager.get(peer.id))) {
|
2019-11-07 12:11:50 +01:00
|
|
|
connections.push(connection)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.push(connections)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Requests the `Identify` message from peer associated with the given `connection`.
|
|
|
|
* If the identified peer does not match the `PeerId` associated with the connection,
|
|
|
|
* an error will be thrown.
|
|
|
|
*
|
|
|
|
* @async
|
|
|
|
* @param {Connection} connection
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
2019-12-03 20:14:15 +01:00
|
|
|
async identify (connection) {
|
2020-07-15 15:47:45 +02:00
|
|
|
const { stream } = await connection.newStream(MULTICODEC_IDENTIFY)
|
2019-11-07 12:11:50 +01:00
|
|
|
const [data] = await pipe(
|
2020-02-05 17:35:27 +01:00
|
|
|
[],
|
2019-11-07 12:11:50 +01:00
|
|
|
stream,
|
|
|
|
lp.decode(),
|
|
|
|
take(1),
|
|
|
|
toBuffer,
|
|
|
|
collect
|
|
|
|
)
|
|
|
|
|
|
|
|
if (!data) {
|
|
|
|
throw errCode(new Error('No data could be retrieved'), codes.ERR_CONNECTION_ENDED)
|
|
|
|
}
|
|
|
|
|
|
|
|
let message
|
|
|
|
try {
|
|
|
|
message = Message.decode(data)
|
|
|
|
} catch (err) {
|
|
|
|
throw errCode(err, codes.ERR_INVALID_MESSAGE)
|
|
|
|
}
|
|
|
|
|
|
|
|
let {
|
|
|
|
publicKey,
|
|
|
|
listenAddrs,
|
|
|
|
protocols,
|
2020-06-20 19:32:39 +02:00
|
|
|
observedAddr,
|
|
|
|
signedPeerRecord
|
2019-11-07 12:11:50 +01:00
|
|
|
} = message
|
|
|
|
|
|
|
|
const id = await PeerId.createFromPubKey(publicKey)
|
2020-04-09 16:07:18 +02:00
|
|
|
|
2020-01-22 11:47:30 +01:00
|
|
|
if (connection.remotePeer.toB58String() !== id.toB58String()) {
|
2019-11-07 12:11:50 +01:00
|
|
|
throw errCode(new Error('identified peer does not match the expected peer'), codes.ERR_INVALID_PEER)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the observedAddr if there is one
|
|
|
|
observedAddr = IdentifyService.getCleanMultiaddr(observedAddr)
|
|
|
|
|
2020-06-20 19:32:39 +02:00
|
|
|
try {
|
2020-07-15 15:47:45 +02:00
|
|
|
const envelope = await Envelope.openAndCertify(signedPeerRecord, PeerRecord.DOMAIN)
|
2020-06-29 12:10:58 +02:00
|
|
|
if (this.peerStore.addressBook.consumePeerRecord(envelope)) {
|
2020-07-15 14:34:51 +02:00
|
|
|
this.peerStore.protoBook.set(id, protocols)
|
2020-06-29 12:10:58 +02:00
|
|
|
return
|
|
|
|
}
|
2020-06-20 19:32:39 +02:00
|
|
|
} catch (err) {
|
2020-07-15 17:42:05 +02:00
|
|
|
log('received invalid envelope, discard it and fallback to listenAddrs is available', err)
|
2020-06-20 19:32:39 +02:00
|
|
|
}
|
|
|
|
|
2020-07-15 14:34:51 +02:00
|
|
|
// LEGACY: Update peers data in PeerStore
|
2020-06-20 19:32:39 +02:00
|
|
|
try {
|
2020-06-29 12:10:58 +02:00
|
|
|
this.peerStore.addressBook.set(id, listenAddrs.map((addr) => multiaddr(addr)))
|
2020-06-20 19:32:39 +02:00
|
|
|
} catch (err) {
|
2020-07-15 15:47:45 +02:00
|
|
|
log.error('received invalid addrs', err)
|
2020-06-20 19:32:39 +02:00
|
|
|
}
|
|
|
|
|
2020-04-18 17:06:56 +02:00
|
|
|
this.peerStore.protoBook.set(id, protocols)
|
2020-08-24 11:58:02 +01:00
|
|
|
this.peerStore.metadataBook.set(id, 'AgentVersion', uint8ArrayFromString(message.agentVersion))
|
2019-11-07 12:11:50 +01:00
|
|
|
|
|
|
|
// TODO: Track our observed address so that we can score it
|
|
|
|
log('received observed address of %s', observedAddr)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A handler to register with Libp2p to process identify messages.
|
|
|
|
*
|
|
|
|
* @param {object} options
|
2020-10-06 14:59:43 +02:00
|
|
|
* @param {string} options.protocol
|
2019-11-07 12:11:50 +01:00
|
|
|
* @param {*} options.stream
|
|
|
|
* @param {Connection} options.connection
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
|
|
|
handleMessage ({ connection, stream, protocol }) {
|
|
|
|
switch (protocol) {
|
|
|
|
case MULTICODEC_IDENTIFY:
|
|
|
|
return this._handleIdentify({ connection, stream })
|
|
|
|
case MULTICODEC_IDENTIFY_PUSH:
|
|
|
|
return this._handlePush({ connection, stream })
|
|
|
|
default:
|
|
|
|
log.error('cannot handle unknown protocol %s', protocol)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-20 19:32:39 +02:00
|
|
|
* Sends the `Identify` response with the Signed Peer Record
|
|
|
|
* to the requesting peer over the given `connection`
|
2020-10-06 14:59:43 +02:00
|
|
|
*
|
2019-11-07 12:11:50 +01:00
|
|
|
* @private
|
|
|
|
* @param {object} options
|
|
|
|
* @param {*} options.stream
|
|
|
|
* @param {Connection} options.connection
|
|
|
|
*/
|
2020-06-18 15:33:08 +02:00
|
|
|
async _handleIdentify ({ connection, stream }) {
|
2020-08-24 11:58:02 +01:00
|
|
|
let publicKey = new Uint8Array(0)
|
2020-04-14 14:05:30 +02:00
|
|
|
if (this.peerId.pubKey) {
|
|
|
|
publicKey = this.peerId.pubKey.bytes
|
2019-11-07 12:11:50 +01:00
|
|
|
}
|
|
|
|
|
2020-09-23 18:45:01 +02:00
|
|
|
const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId)
|
2020-06-20 19:32:39 +02:00
|
|
|
|
2019-11-07 12:11:50 +01:00
|
|
|
const message = Message.encode({
|
|
|
|
protocolVersion: PROTOCOL_VERSION,
|
|
|
|
agentVersion: AGENT_VERSION,
|
|
|
|
publicKey,
|
2020-08-24 11:58:02 +01:00
|
|
|
listenAddrs: this._libp2p.multiaddrs.map((ma) => ma.bytes),
|
2020-06-24 15:54:24 +02:00
|
|
|
signedPeerRecord,
|
2020-08-24 11:58:02 +01:00
|
|
|
observedAddr: connection.remoteAddr.bytes,
|
2019-11-07 12:11:50 +01:00
|
|
|
protocols: Array.from(this._protocols.keys())
|
|
|
|
})
|
|
|
|
|
2020-06-18 15:33:08 +02:00
|
|
|
try {
|
|
|
|
await pipe(
|
|
|
|
[message],
|
|
|
|
lp.encode(),
|
|
|
|
stream,
|
|
|
|
consume
|
|
|
|
)
|
|
|
|
} catch (err) {
|
|
|
|
log.error('could not respond to identify request', err)
|
|
|
|
}
|
2019-11-07 12:11:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reads the Identify Push message from the given `connection`
|
2020-10-06 14:59:43 +02:00
|
|
|
*
|
2019-11-07 12:11:50 +01:00
|
|
|
* @private
|
|
|
|
* @param {object} options
|
|
|
|
* @param {*} options.stream
|
|
|
|
* @param {Connection} options.connection
|
|
|
|
*/
|
|
|
|
async _handlePush ({ connection, stream }) {
|
|
|
|
let message
|
|
|
|
try {
|
2020-06-18 15:33:08 +02:00
|
|
|
const [data] = await pipe(
|
|
|
|
[],
|
|
|
|
stream,
|
|
|
|
lp.decode(),
|
|
|
|
take(1),
|
|
|
|
toBuffer,
|
|
|
|
collect
|
|
|
|
)
|
2019-11-07 12:11:50 +01:00
|
|
|
message = Message.decode(data)
|
|
|
|
} catch (err) {
|
|
|
|
return log.error('received invalid message', err)
|
|
|
|
}
|
|
|
|
|
2020-06-24 15:54:24 +02:00
|
|
|
const id = connection.remotePeer
|
|
|
|
|
2020-06-20 19:32:39 +02:00
|
|
|
try {
|
2020-07-15 15:47:45 +02:00
|
|
|
const envelope = await Envelope.openAndCertify(message.signedPeerRecord, PeerRecord.DOMAIN)
|
2020-06-29 12:10:58 +02:00
|
|
|
if (this.peerStore.addressBook.consumePeerRecord(envelope)) {
|
2020-07-15 14:34:51 +02:00
|
|
|
this.peerStore.protoBook.set(id, message.protocols)
|
2020-06-29 12:10:58 +02:00
|
|
|
return
|
|
|
|
}
|
2020-06-20 19:32:39 +02:00
|
|
|
} catch (err) {
|
2020-07-15 17:42:05 +02:00
|
|
|
log('received invalid envelope, discard it and fallback to listenAddrs is available', err)
|
2020-06-20 19:32:39 +02:00
|
|
|
}
|
|
|
|
|
2020-07-15 14:34:51 +02:00
|
|
|
// LEGACY: Update peers data in PeerStore
|
2020-06-20 19:32:39 +02:00
|
|
|
try {
|
2020-06-29 12:10:58 +02:00
|
|
|
this.peerStore.addressBook.set(id, message.listenAddrs.map((addr) => multiaddr(addr)))
|
2020-06-20 19:32:39 +02:00
|
|
|
} catch (err) {
|
2020-07-15 15:47:45 +02:00
|
|
|
log.error('received invalid addrs', err)
|
2020-06-20 19:32:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Update the protocols
|
|
|
|
this.peerStore.protoBook.set(id, message.protocols)
|
|
|
|
}
|
2019-11-07 12:11:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports.IdentifyService = IdentifyService
|
|
|
|
/**
|
|
|
|
* The protocols the IdentifyService supports
|
2020-10-06 14:59:43 +02:00
|
|
|
*
|
2019-11-07 12:11:50 +01:00
|
|
|
* @property multicodecs
|
|
|
|
*/
|
|
|
|
module.exports.multicodecs = {
|
|
|
|
IDENTIFY: MULTICODEC_IDENTIFY,
|
2020-07-15 15:47:45 +02:00
|
|
|
IDENTIFY_PUSH: MULTICODEC_IDENTIFY_PUSH
|
2019-11-07 12:11:50 +01:00
|
|
|
}
|
|
|
|
module.exports.Message = Message
|