UNPKG

swim

Version:

Gossip protocol based on SWIM

361 lines (296 loc) 10.1 kB
'use strict'; var debug = require('debug'); var dgram = require('dgram'); var events = require('events'); var util = require('util'); var MessageType = require('./message-type'); function Net(opts) { this.swim = opts.swim; this.udp = { port: opts.udp.port, type: opts.udp.type || Net.Default.udp.type, maxDgramSize: opts.udp.maxDgramSize || Net.Default.udp.maxDgramSize }; this.errorListener = this.emit.bind(this, Net.EventType.Error); this.listeningListener = this.emit.bind(this, Net.EventType.Listening); this.messageListener = this.onNetMessage.bind(this); this.udpSocket = dgram.createSocket(this.udp.type); this.debug = debug('swim:net').bind(undefined, opts.debugIdentifier); } util.inherits(Net, events.EventEmitter); Net.prototype.listen = function listen(callback) { this.udpSocket.on('error', this.errorListener); this.udpSocket.on('listening', this.listeningListener); this.udpSocket.on('message', this.messageListener); this.udpSocket.bind(this.udp.port, callback); }; Net.prototype.close = function close() { this.udpSocket.removeListener('error', this.errorListener); this.udpSocket.removeListener('listening', this.listeningListener); this.udpSocket.removeListener('message', this.messageListener); this.udpSocket.close(); this.udpSocket = dgram.createSocket(this.udp.type); }; Net.prototype.onNetMessage = function onNetMessage(buffer, rinfo) { this.debug('received buffer', { from: formatRinfo(rinfo), length: buffer.length }); this.onMessage(buffer, rinfo); }; Net.prototype.onMessage = function onMessage(buffer, rinfo) { if (buffer.length < Net.MessageTypeSize) { return this.onUnknown(buffer, rinfo); } var messageType = Net.ReadMessageType.call(buffer, 0); switch (messageType) { case MessageType.Compound: this.onCompound(buffer.slice(Net.MessageTypeSize), rinfo); break; case MessageType.Ping: this.onPing(buffer.slice(Net.MessageTypeSize), rinfo); break; case MessageType.PingReq: this.onPingReq(buffer.slice(Net.MessageTypeSize), rinfo); break; case MessageType.Sync: this.onSync(buffer.slice(Net.MessageTypeSize), rinfo); break; case MessageType.Ack: this.onAck(buffer.slice(Net.MessageTypeSize), rinfo); break; case MessageType.Update: this.onUpdate(buffer.slice(Net.MessageTypeSize), rinfo); break; default: this.onUnknown(buffer, rinfo); } }; Net.prototype.onCompound = function onCompound(buffer, rinfo) { this.debug('received compound message'); if (buffer.length < Net.LengthSize) { this.debug('cannot parse number of messages in compound message', { from: formatRinfo(rinfo), length: buffer.length, buffer: buffer.toString() }); return; } var numberOfMessages = Net.ReadLength.call(buffer, 0); var readIndex = Net.LengthSize; var length; var i; for (i = 0; i < numberOfMessages; i++) { if (buffer.length - readIndex < Net.LengthSize) { this.debug('cannot parse length of message in compound message', { readIndex: readIndex, from: formatRinfo(rinfo), length: buffer.length, buffer: buffer.toString() }); break; } length = Net.ReadLength.call(buffer, readIndex); readIndex += Net.LengthSize; this.onMessage(buffer.slice(readIndex, readIndex + length), rinfo); readIndex += length; } }; Net.prototype.onPing = function onPing(buffer, rinfo) { var data; try { data = this.swim.codec.decode(buffer); } catch (e) { this.debug('failed to decode data', { stack: e.stack, from: formatRinfo(rinfo), length: buffer.length, buffer: buffer.toString() }); return; } this.debug('received ping message', { from: formatRinfo(rinfo), data: data }); this.emit(Net.EventType.Ping, data, formatRinfo(rinfo)); }; Net.prototype.onPingReq = function onPingReq(buffer, rinfo) { var data; try { data = this.swim.codec.decode(buffer); } catch (e) { this.debug('failed to decode data', { stack: e.stack, from: formatRinfo(rinfo), length: buffer.length, buffer: buffer.toString() }); return; } this.debug('received pingreq message', { from: formatRinfo(rinfo), data: data }); this.emit(Net.EventType.PingReq, data, formatRinfo(rinfo)); }; Net.prototype.onSync = function onSync(buffer, rinfo) { var data; try { data = this.swim.codec.decode(buffer); } catch (e) { this.debug('failed to decode data', { stack: e.stack, from: formatRinfo(rinfo), length: buffer.length, buffer: buffer.toString() }); return; } this.debug('received sync message', { from: formatRinfo(rinfo), data: data }); this.emit(Net.EventType.Sync, data, formatRinfo(rinfo)); }; Net.prototype.onAck = function onAck(buffer, rinfo) { var data; try { data = this.swim.codec.decode(buffer); } catch (e) { this.debug('failed to decode data', { stack: e.stack, from: formatRinfo(rinfo), length: buffer.length, buffer: buffer.toString() }); return; } this.debug('received ack message', { from: formatRinfo(rinfo), data: data }); this.emit(Net.EventType.Ack, data, formatRinfo(rinfo)); }; Net.prototype.onUpdate = function onUpdate(buffer, rinfo) { var data; try { data = this.swim.codec.decode(buffer); } catch (e) { this.debug('failed to decode data', { stack: e.stack, from: formatRinfo(rinfo), length: buffer.length, buffer: buffer.toString() }); return; } this.debug('received update message', { from: formatRinfo(rinfo), data: data }); this.emit(Net.EventType.Update, data, formatRinfo(rinfo)); }; Net.prototype.onUnknown = function onUnknown(buffer, rinfo) { this.debug('received unknown buffer', { from: formatRinfo(rinfo), buffer: buffer.toString() }); this.emit(Net.EventType.Unknown, buffer, formatRinfo(rinfo)); }; Net.prototype.sendMessages = function sendMessages(messages, host) { var self = this; var bytesAvailable = self.udp.maxDgramSize - Net.MessageTypeSize - Net.LengthSize; var buffers = []; var buffer; var message; var i; for (i = 0; i < messages.length; i++) { message = messages[i]; buffer = new Buffer(Net.MessageTypeSize); Net.WriteMessageType.call(buffer, message.type, 0); buffer = Buffer.concat([buffer, self.swim.codec.encode(message.data)]); if (buffer.length + Net.LengthSize < bytesAvailable) { buffers.push(buffer); bytesAvailable -= buffer.length + Net.LengthSize; } else if (buffers.length === 0) { this.debug('oversized message', { length: buffer.length, message: message }); } else { self.sendBuffer(self.makeCompoundMessage(buffers), host); bytesAvailable = self.udp.maxDgramSize - Net.LengthSize; buffers = []; i--; } } if (buffers.length > 0) { self.sendBuffer(self.makeCompoundMessage(buffers), host); } }; Net.prototype.sendMessage = function sendMessage(message, host) { var header = new Buffer(Net.MessageTypeSize); Net.WriteMessageType.call(header, message.type, 0); if (message.data) { this.piggybackAndSend(Buffer.concat([header, this.swim.codec.encode(message.data)]), host); } else { this.piggybackAndSend(header, host); } }; Net.prototype.piggybackAndSend = function piggybackAndSend(buffer, host) { var bytesAvailable = this.udp.maxDgramSize - Net.MessageTypeSize - Net.LengthSize * 2 - buffer.length; var buffers = this.swim.disseminator.getUpdatesUpTo(bytesAvailable); if (buffers.length === 0) { return this.sendBuffer(buffer, host); } buffers.unshift(buffer); this.sendBuffer(this.makeCompoundMessage(buffers), host); }; Net.prototype.makeCompoundMessage = function makeCompoundMessage(buffers) { var header = new Buffer(Net.MessageTypeSize + Net.LengthSize); Net.WriteMessageType.call(header, MessageType.Compound, 0); Net.WriteLength.call(header, buffers.length, Net.MessageTypeSize); buffers = buffers.map(function toBuffer(buffer) { var header = new Buffer(Net.LengthSize); Net.WriteLength.call(header, buffer.length, 0); return Buffer.concat([header, buffer]); }); buffers.unshift(header); return Buffer.concat(buffers); }; Net.prototype.sendBuffer = function sendBuffer(buffer, host) { var address = host.split(':')[0]; var port = host.split(':')[1]; this.udpSocket.send(buffer, 0, buffer.length, port, address); this.debug('sent buffer', { to: host, length: buffer.length }); }; Net.MessageTypeSize = 1; Net.LengthSize = 2; Net.ReadMessageType = Buffer.prototype.readUInt8; Net.ReadLength = Buffer.prototype.readUInt16LE; Net.WriteMessageType = Buffer.prototype.writeUInt8; Net.WriteLength = Buffer.prototype.writeUInt16LE; Net.Default = { udp: { type: 'udp4', maxDgramSize: 512 } }; Net.EventType = { Error: 'error', Listening: 'listening', Ping: 'ping', PingReq: 'pingreq', Sync: 'sync', Ack: 'ack', Update: 'update', Unknown: 'unknown' }; function formatRinfo(rinfo) { return rinfo.address + ':' + rinfo.port; } module.exports = Net;