/*
* Copyright (c) 2017 Sebastian Rager
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
const dgram = require('dgram');
const debug = require('debug')('zyre:zbeacon');
const ZHelper = require('./zhelper');
// Static header: Z R E {version}
const BEACON_VERSION = 1;
const BEACON_HEADER = Buffer.from([0x5a, 0x52, 0x45, BEACON_VERSION]);
function createDataGramBuffer(identity, mailbox) {
// Write mailbox port in network order
const dgMailbox = Buffer.alloc(2);
dgMailbox.writeUInt16BE(mailbox);
return Buffer.concat([BEACON_HEADER, identity, dgMailbox]);
}
function readDataGramBuffer(dgBuffer) {
try {
if (dgBuffer.length !== 22) throw Error;
if (dgBuffer.compare(BEACON_HEADER, 0, 4, 0, 4) !== 0) throw Error;
const dataGram = {};
dataGram.identity = dgBuffer.toString('hex', 4, 20);
dataGram.mailbox = dgBuffer.readUInt16BE(20);
return dataGram;
} catch (err) {
return undefined;
}
}
/**
* ZBeacon implements the discovery beacon defined in the ZRE protocol, it listens for foreign
* beacons and broadcasts his own datagram.
*/
class ZBeacon {
/**
* @param {object} options - Options object
* @param {Buffer} options.identity - 16 byte UUID as Buffer
* @param {number} options.mailbox - Mailbox of the zyre node
* @param {IfaceData} options.ifaceData - Interface data
* @param {number} [options.port=5670] - Broadcast port
* @param {number} [options.interval=1000] - Interval of the beacon in ms
* @param {ZyrePeers} options.zyrePeers - Global ZyrePeers object
*/
constructor({
identity,
mailbox,
ifaceData,
port = 5670,
interval = 1000,
zyrePeers,
}) {
this._identity = identity;
this._mailbox = mailbox;
this._network = ifaceData.network;
this._netmask = ifaceData.netmask;
this._broadcast = ifaceData.broadcast;
this._port = port;
this._interval = interval;
this._zyrePeers = zyrePeers;
this._dgBuffer = createDataGramBuffer(identity, mailbox);
this._createHandler();
}
/**
* Starts broadcasting the beacon.
*
* @return {Promise}
*/
startBroadcasting() {
this._nodeSock = dgram.createSocket('udp4');
this._sendBroadcast = () => {
this._nodeSock.send(this._dgBuffer, this._port, this._broadcast, () => {
debug(`sent beacon to ${this._broadcast}:${this._port}`);
});
};
return new Promise((resolve) => {
this._nodeSock.bind(() => {
this._nodeSock.setBroadcast(true);
this._sendBroadcast();
this._broadcastTimer = setInterval(this._sendBroadcast, this._interval);
resolve();
});
});
}
/**
* Starts listening for foreign beacons and pushes discovered peers to the ZyrePeers object.
*
* @return {Promise}
*/
startListening() {
this._peerSock = dgram.createSocket({
type: 'udp4',
reuseAddr: true,
});
this._peerSock.on('listening', () => {
const address = this._peerSock.address();
debug(`listening on ${address.address}:${address.port}`);
});
this._peerSock.on('message', this._messageHandler);
return new Promise((resolve) => {
this._peerSock.bind({
address: '0.0.0.0',
port: this._port,
}, () => {
resolve();
});
});
}
/**
* Starts listening and broadcasting.
*
* @return {Promise}
*/
start() {
return new Promise((resolve) => {
this.startListening().then(() => {
this.startBroadcasting().then(() => {
resolve();
});
});
});
}
/**
* Sends disconnect beacon and stops every activity.
*
* @return {Promise}
*/
stop() {
clearInterval(this._broadcastTimer);
if (typeof this._peerSock !== 'undefined') {
this._peerSock.removeAllListeners();
this._peerSock.close();
}
return new Promise((resolve) => {
if (typeof this._nodeSock !== 'undefined') {
const dcdgBuffer = createDataGramBuffer(this._identity, 0);
this._nodeSock.send(dcdgBuffer, this._port, this._broadcast, () => {
debug(`sent disconnect beacon to ${this._broadcast}:${this._port}`);
this._nodeSock.close(() => {
resolve();
});
});
} else {
resolve();
}
});
}
/**
* Creates handler as object properties in a separate method to ensure proper scope via arrow
* functions.
*
* @protected
*/
_createHandler() {
/**
* Parses the given msg if it is a valid ZRE Beacon and updates the peer information
*
* @protected
* @param {Buffer} msg - Message as binary Buffer
* @param {object} rinfo - Information about remote client
*/
this._messageHandler = (msg, rinfo) => {
// Return if received own beacon
if (msg.equals(this._dgBuffer)) return;
// Return if received udp beacon from different subnet
if (!ZHelper.ipInSubnet(rinfo.address, this._network, this._netmask)) return;
// Return if received no valid zbeacon datagram
const dataGram = readDataGramBuffer(msg);
if (typeof dataGram === 'undefined') return;
debug(`got beacon from ${rinfo.address}:${rinfo.port} (${dataGram.identity})`);
this._zyrePeers.push({
identity: dataGram.identity,
address: rinfo.address,
mailbox: dataGram.mailbox,
});
};
}
}
module.exports = ZBeacon;