ntrnetwork
Version:
Transledger peer to peer wire communication
208 lines (181 loc) • 6.88 kB
JavaScript
const { EventEmitter } = require('events')
const dgram = require('dgram')
const ms = require('ms')
const LRUCache = require('lru-cache')
const message = require('./message')
const { keccak256, pk2id, createDeferred } = require('../util')
const VERSION = 0x010;
let interblockchainKey = 0x00;
const createSocketUDP4 = dgram.createSocket.bind(null, 'udp4');
const debug = (process.env.SERVER) ? process.env.SERVER : require('../environment').debug.server;
// channelID is set by default to 0x16;
// the channelID used to discriminate the channels can be overloaded.
class Server extends EventEmitter {
constructor (dpt, privateKey, options) {
super()
this._dpt = dpt;
this._privateKey = privateKey;
if (!this._channelID)
this._channelID = interblockchainKey;
if (!this._monitor)
this._monitor = false;
this._timeout = options.timeout || ms('10s')
this._endpoint = options.endpoint || { address: '0.0.0.0', udpPort: null, tcpPort: null }
this._requests = new Map()
this._parityRequestMap = new Map()
this._requestsCache = new LRUCache({ max: 1000, maxAge: ms('1s'), stale: false })
const createSocket = options.createSocket || createSocketUDP4
this._socket = createSocket()
this._socket.once('listening', () => this.emit('listening'))
this._socket.once('close', () => this.emit('close'))
this._socket.on('error', (err) => this.emit('error', err))
this._socket.on('message', (msg, rinfo) => {
try {
if (msg) this._handler(msg, rinfo);
} catch (err) {
this.emit('error', err);
}
})
}
set channelID (channel) {
this._channelID = channel;
}
get channelID () {
return this._channelID;
}
bind (...args) {
this._isAliveCheck()
debug ? console.log(`${Date().toString().substring(0, 24)} dpt/server.bind`) : null;
this._socket.bind(...args)
}
destroy (...args) {
this._isAliveCheck()
debug ? console.log(`${Date().toString().substring(0, 24)} dpt/server.destroy`) : null;
this._socket.close(...args)
this._socket = null
}
async ping (peer) {
this._isAliveCheck()
const rckey = `${peer.address}:${peer.udpPort}`
const promise = this._requestsCache.get(rckey)
if (promise !== undefined) return promise
const hash = this._send(peer, 'ping', {
version: VERSION,
from: this._endpoint,
to: peer,
interblockchain: this._channelID
})
const deferred = createDeferred()
const rkey = hash.toString('hex')
this._requests.set(rkey, {
peer,
deferred,
timeoutId: setTimeout(() => {
if (this._requests.get(rkey) !== undefined) {
debug ? console.log(`${Date().toString().substring(0, 24)} dpt/server.ping timeout: ${peer.address}:${peer.udpPort}`) : null;
this._requests.delete(rkey)
deferred.reject(new Error(`${Date().toString().substring(0, 24)} dpt/server.ping: Timeout error: ping ${peer.address}:${peer.udpPort}`))
} else {
return deferred.promise
}
}, this._timeout)
})
this._requestsCache.set(rckey, deferred.promise)
return deferred.promise
}
findneighbours (peer, id) {
this._isAliveCheck()
this._send(peer, 'findneighbours', {
version: VERSION,
from: this._endpoint,
to: peer,
interblockchain: this._channelID
})
}
_isAliveCheck () {
if (this._socket === null) throw new Error('Server already destroyed')
}
_send (peer, typename, data) {
if (peer && typename && data) {
debug ? console.log(`${Date().toString().substring(0, 24)} dpt/server.send ${typename} to ${peer.address}:${peer.udpPort}`) : null;
const msg = message.encode(typename, data, this._privateKey) ;
this._socket.send(msg, 0, msg.length, peer.udpPort, peer.address);
return msg.slice(0, 32); // message id
}
}
_handler (msg, rinfo) {
const info = message.decode(msg);
const peerId = pk2id(info.publicKey);
debug ? console.log(`${Date().toString().substring(0, 24)} dpt/server._handler: received ${info.typename} from ${rinfo.address}:${rinfo.port}`) : null;
// add peer if not in our table
const peer = this._dpt.getPeer(peerId)
if (peer === null && info.typename === 'ping' && info.data.from.udpPort !== null) {
setTimeout(() => this.emit('peers', [ info.data.from ]), ms('100ms'))
}
// all handled messages are filtered with a channelID
switch (info.typename) {
case 'ping':
if (info.data.interblockchain == this.channelID
||
this.channelID == 0x00
||
info.data.interblockchain == 0x00) {
Object.assign(rinfo, { id: peerId, udpPort: rinfo.port });
this._send(rinfo, 'pong', {
to: {
address: rinfo.address,
udpPort: rinfo.port,
tcpPort: info.data.from.tcpPort
},
version: VERSION,
interblockchain: this._channelID,
hash: msg.slice(0, 32)
});
}
break
case 'pong':
if (info.data.interblockchain == this.channelID
||
this.channelID == 0x00
||
info.data.interblockchain == 0x00) {
var rkey = info.data.hash.toString('hex');
const request = this._requests.get(rkey);
if (request) {
this._requests.delete(rkey)
request.deferred.resolve({
id: peerId,
address: request.peer.address,
udpPort: request.peer.udpPort,
tcpPort: request.peer.tcpPort
})
}
}
break;
case 'findneighbours':
if (info.data.interblockchain == this.channelID
||
this.channelID == 0x00
||
info.data.interblockchain == 0x00) {
Object.assign(rinfo, { id: peerId, udpPort: rinfo.port })
this._send(rinfo, 'neighbours', {
version: VERSION,
interblockchain: this._channelID,
peers: this._dpt.getClosestPeers(info.data.id)
})
}
break
case 'neighbours':
if (info.data.interblockchain == this.channelID
||
this.channelID == 0x00
||
info.data.interblockchain == 0x00) {
this.emit('peers', info.data.peers.map((peer) => peer.endpoint))
}
break
}
}
}
module.exports = Server;