├── .npmignore ├── .gitignore ├── lib ├── index.js ├── bufferEqual.js ├── constants.js ├── Node.js ├── PacketSender.js ├── helpers.js ├── PendingPacket.js ├── Client.js ├── Connection.js ├── Server.js ├── LinkedList.js ├── Packet.js ├── Receiver.js └── Sender.js ├── test ├── unit │ ├── SenderTest.js │ ├── PacketSenderTest.js │ ├── PacketTest.js │ ├── helpersTest.js │ ├── LinkedListTest.js │ └── ReceiverTest.js ├── index.js └── example │ ├── client-server │ ├── client.js │ └── server.js │ ├── simple.js │ └── lossy.js ├── README.md └── package.json /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | out 3 | npm-debug.log -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | npm-debug.log -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | module.exports.Client = require('./Client'); 2 | module.exports.Server = require('./Server'); -------------------------------------------------------------------------------- /lib/bufferEqual.js: -------------------------------------------------------------------------------- 1 | 2 | var buffertools = require('buffertools') 3 | module.exports = function bufferEqual(a, b) { 4 | return buffertools.compare(a, b) === 0 5 | } -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | module.exports.MAX_SIZE = 255; 2 | module.exports.WINDOW_SIZE = 16; 3 | module.exports.TIMEOUT = 800; 4 | module.exports.UDP_SAFE_SEGMENT_SIZE = 512; -------------------------------------------------------------------------------- /test/unit/SenderTest.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | var assert = require('assert'); 3 | var Sender = require('../../lib/Sender'); 4 | var constants = require('../../lib/constants'); -------------------------------------------------------------------------------- /lib/Node.js: -------------------------------------------------------------------------------- 1 | function Node(value) { 2 | this._value = value; 3 | } 4 | 5 | Node.prototype.getValue = function () { 6 | return this._value; 7 | }; 8 | 9 | Node.prototype.getNext = function () { 10 | return this._next; 11 | } -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | 4 | var files = fs.readdirSync(path.join(__dirname, 'unit')); 5 | 6 | files.forEach(function (file) { 7 | require(path.join(__dirname, 'unit', file)); 8 | }); -------------------------------------------------------------------------------- /test/example/client-server/client.js: -------------------------------------------------------------------------------- 1 | var rudp = require('rudp'); 2 | var dgram = require('dgram'); 3 | 4 | var socket = dgram.createSocket('udp4'); 5 | 6 | var client = new rudp.Client(socket, 'someaddress', 5000); 7 | 8 | process.stdin.resume(); 9 | 10 | process.stdin.on('data', function (data) { 11 | client.send(data); 12 | }); 13 | 14 | client.on('data', function (data) { 15 | console.log(data.toString('utf8').trim()); 16 | }); 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rudp 2 | 3 | Reliable UDP implementation for Node.js. 4 | 5 | ## Example 6 | 7 | For a peer-to-peer application, you can write a script like this: 8 | 9 | ```javascript 10 | var rudp = require('rudp'); 11 | var dgram = require('dgram'); 12 | 13 | var socket = dgram.createSocket('udp4'); 14 | 15 | socket.bind(localPort); 16 | 17 | var client = new rudp.Client(socket, remoteAddress, remotePort); 18 | 19 | // And do whatever you want here 20 | ``` 21 | -------------------------------------------------------------------------------- /lib/PacketSender.js: -------------------------------------------------------------------------------- 1 | module.exports = PacketSender; 2 | function PacketSender(socket, address, port) { 3 | if (!socket || !address || !port) { 4 | throw new Error('Expecting a socket, address, and a port.'); 5 | } 6 | this._socket = socket; 7 | this._address = address; 8 | this._port = port; 9 | }; 10 | 11 | PacketSender.prototype.send = function (packet) { 12 | var buffer = packet.toBuffer(); 13 | this._socket.send(buffer, 0, buffer.length, this._port, this._address); 14 | }; -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This will split a an array-like object into chunks of length `length`. 3 | */ 4 | module.exports.splitArrayLike = function (arr, length) { 5 | length = length || 1; 6 | var retval = []; 7 | for (var i = 0; i < arr.length; i += length) { 8 | retval.push(arr.slice(i, i + length)); 9 | } 10 | return retval; 11 | }; 12 | 13 | module.exports.shuffle = function (o) { 14 | for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); 15 | return o; 16 | }; -------------------------------------------------------------------------------- /test/example/client-server/server.js: -------------------------------------------------------------------------------- 1 | var rudp = require('rudp'); 2 | var dgram = require('dgram'); 3 | 4 | var socket = dgram.createSocket('udp4'); 5 | 6 | socket.bind(5000); 7 | 8 | console.log('UDP socket bound to port 5000'); 9 | 10 | var server = new rudp.Server(socket); 11 | 12 | process.stdin.resume(); 13 | 14 | var connections = []; 15 | 16 | process.stdin.on('data', function (data) { 17 | connections.forEach(function (connection) { 18 | connection.send(data); 19 | }); 20 | }); 21 | 22 | server.on('connection', function (connection) { 23 | connections.push(connection); 24 | connection.on('data', function (data) { 25 | console.log(data.toString('utf8').trim()); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /test/example/simple.js: -------------------------------------------------------------------------------- 1 | var gobackn = require('../../lib'); 2 | var dgram = require('dgram'); 3 | 4 | var serverSocket = dgram.createSocket('udp4'); 5 | var clientSocket = dgram.createSocket('udp4'); 6 | 7 | serverSocket.bind(3001); 8 | 9 | console.log('Bound the server socket to port 3001'); 10 | 11 | var server = new gobackn.Server(serverSocket); 12 | var client = new gobackn.Client(clientSocket, '127.0.0.1', 3001); 13 | 14 | server.on('close', function () { 15 | serverSocket.close(); 16 | }); 17 | 18 | client.on('close', function () { 19 | clientSocket.close(); 20 | }); 21 | 22 | server.on('connection', function (connection) { 23 | connection.on('data', function (data) { 24 | console.log(data.toString('utf8')); 25 | }); 26 | 27 | connection.send(new Buffer('Hello, Client!')); 28 | }); 29 | 30 | client.on('data', function (data) { 31 | console.log('Client: %s', data.toString('utf8')); 32 | }); 33 | 34 | client.send(new Buffer('Hello, World!')); 35 | client.send(new Buffer('How are you doing?')); -------------------------------------------------------------------------------- /lib/PendingPacket.js: -------------------------------------------------------------------------------- 1 | var constants = require('./constants'); 2 | var EventEmitter = require('events').EventEmitter; 3 | var util = require('util'); 4 | 5 | /** 6 | * A placeholder for a packet that is awaiting an acknowledgement from the end 7 | * host. 8 | * 9 | * @class PendingPacket 10 | * @constructor 11 | */ 12 | module.exports = PendingPacket; 13 | function PendingPacket(packet, packetSender) { 14 | this._packetSender = packetSender; 15 | this._packet = packet; 16 | } 17 | 18 | util.inherits(PendingPacket, EventEmitter); 19 | 20 | PendingPacket.prototype.send = function () { 21 | var self = this; 22 | this._intervalID = setInterval(function () { 23 | self._packetSender.send(self._packet); 24 | }, constants.TIMEOUT); 25 | self._packetSender.send(self._packet); 26 | }; 27 | 28 | PendingPacket.prototype.getSequenceNumber = function () { 29 | return this._packet.getSequenceNumber(); 30 | }; 31 | 32 | PendingPacket.prototype.acknowledge = function () { 33 | clearInterval(this._intervalID); 34 | this.emit('acknowledge'); 35 | }; -------------------------------------------------------------------------------- /test/unit/PacketSenderTest.js: -------------------------------------------------------------------------------- 1 | var PacketSender = require('../../lib/PacketSender'); 2 | var expect = require('expect.js'); 3 | 4 | describe('PacketSender', function () { 5 | describe('constructor', function () { 6 | it('should throw an error, absent a socket, an address and/or port', function () { 7 | 8 | expect(function () { 9 | new PacketSender(); 10 | }).to.throwError(); 11 | 12 | expect(function () { 13 | new PacketSender({}); 14 | }).to.throwError(); 15 | 16 | expect(function () { 17 | new PacketSender(undefined, 'test.com'); 18 | }).to.throwError(); 19 | 20 | expect(function () { 21 | new PacketSender(undefined, undefined, 4000); 22 | }).to.throwError(); 23 | 24 | expect(function () { 25 | new PacketSender({}, 'test.com'); 26 | }).to.throwError(); 27 | 28 | expect(function () { 29 | new PacketSender({}, undefined, 4000); 30 | }).to.throwError(); 31 | 32 | expect(function () { 33 | new PacketSender(undefined, 'test.com', 4000); 34 | }).to.throwError(); 35 | 36 | new PacketSender({}, 'test.com', 4000); 37 | }); 38 | }); 39 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rudp", 3 | "version": "1.0.0-alpha4", 4 | "description": "Reliable UDP implementation for Node.js", 5 | "homepage": "https://github.com/shovon/node-rudp", 6 | "author": { 7 | "name": "Salehen Shovon Rahman", 8 | "email": "sal@linux.com", 9 | "url": "http://shovon.github.io" 10 | }, 11 | "scripts": { 12 | "gendoc": "./node_modules/yuidocjs/lib/cli.js .", 13 | "viewdoc": "./node_modules/yuidocjs/lib/cli.js . --server 4000", 14 | "test": "./node_modules/mocha/bin/mocha", 15 | "unit-test": "./node_modules/mocha/bin/mocha ./test/unit", 16 | "integration-test": "./node_modules/mocha/bin/mocha ./test/integration" 17 | }, 18 | "main": "./lib/index.js", 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/shovon/node-rudp.git" 22 | }, 23 | "devDependencies": { 24 | "yuidocjs": "^0.3.50", 25 | "mocha": "^1.18.2", 26 | "expect.js": "^0.3.1", 27 | "sinon": "^1.9.1" 28 | }, 29 | "dependencies": { 30 | "buffer-equal": "0.0.1", 31 | "buffertools": "^2.1.2" 32 | }, 33 | "browser": { 34 | "buffertools": false, 35 | "./bufferEqual": "buffer-equal" 36 | }, 37 | "license": "MIT" 38 | } 39 | -------------------------------------------------------------------------------- /lib/Client.js: -------------------------------------------------------------------------------- 1 | var Receiver = require('./Receiver'); 2 | var Sender = require('./Sender'); 3 | var EventEmitter = require('events').EventEmitter; 4 | var util = require('util'); 5 | var PacketSender = require('./PacketSender'); 6 | var Packet = require('./Packet'); 7 | var Connection = require('./Connection'); 8 | 9 | module.exports = Client; 10 | function Client(socket, address, port) { 11 | this._packetSender = new PacketSender(socket, address, port); 12 | this._connection = new Connection(this._packetSender); 13 | 14 | var self = this; 15 | this._connection.on('data', function (data) { 16 | self.emit('data', data); 17 | }); 18 | 19 | socket.on('message', function (message, rinfo) { 20 | if (rinfo.address !== address || rinfo.port !== port) { 21 | return; 22 | } 23 | var packet = new Packet(message); 24 | if (packet.getIsFinish()) { 25 | socket.close(); 26 | return; 27 | } 28 | self._connection.receive(packet); 29 | }); 30 | }; 31 | 32 | util.inherits(Client, EventEmitter); 33 | 34 | Client.prototype.send = function (data) { 35 | this._connection.send(data); 36 | }; 37 | 38 | Client.prototype.close = function () { 39 | this._packetSender.send(Packet.createFinishPacket()); 40 | }; 41 | -------------------------------------------------------------------------------- /lib/Connection.js: -------------------------------------------------------------------------------- 1 | var Sender = require('./Sender'); 2 | var Receiver = require('./Receiver'); 3 | var EventEmitter = require('events').EventEmitter; 4 | var util = require('util'); 5 | 6 | // TODO: have connections refuse packets when closed. 7 | 8 | module.exports = Connection; 9 | function Connection(packetSender) { 10 | this._sender = new Sender(packetSender); 11 | this._receiver = new Receiver(packetSender); 12 | 13 | var self = this; 14 | this._receiver.on('data', function (data) { 15 | self.emit('data', data) 16 | }); 17 | }; 18 | 19 | util.inherits(Connection, EventEmitter); 20 | 21 | /** 22 | * Sends the given buffer to the end host. 23 | * @param {Buffer} data The buffer to send to the end host. 24 | */ 25 | Connection.prototype.send = function (data) { 26 | this._sender.send(data); 27 | }; 28 | 29 | /** 30 | * This is the function called by `Server` and `Client` to process packets as 31 | * they arrive. 32 | * @param {Packet} packet Is a single packet received from an endpoint. 33 | */ 34 | Connection.prototype.receive = function (packet) { 35 | if (packet.getIsAcknowledgement()) { 36 | this._sender.verifyAcknowledgement(packet.getSequenceNumber()); 37 | } else { 38 | this._receiver.receive(packet); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /test/example/lossy.js: -------------------------------------------------------------------------------- 1 | var gobackn = require('../../lib'); 2 | var dgram = require('dgram'); 3 | 4 | var serverSocket = dgram.createSocket('udp4'); 5 | var middleSocket = dgram.createSocket('udp4'); 6 | var clientSocket = dgram.createSocket('udp4'); 7 | 8 | clientSocket.bind(2000); 9 | middleSocket.bind(4000); 10 | serverSocket.bind(3000); 11 | 12 | middleSocket.on('message', function (message, rinfo) { 13 | if (Math.random() > 0.2) { 14 | return; 15 | } 16 | setTimeout(function () { 17 | if (rinfo.port === 2000) { 18 | middleSocket.send(message, 0, message.length, 3000, '127.0.0.1'); 19 | } else if (rinfo.port === 3000) { 20 | middleSocket.send(message, 0, message.length, 2000, '127.0.0.1'); 21 | } 22 | }, Math.floor(Math.random() * 800)); 23 | }); 24 | 25 | console.log('Bound the server socket to port 3000'); 26 | 27 | var server = new gobackn.Server(serverSocket); 28 | var client = new gobackn.Client(clientSocket, '127.0.0.1', 4000); 29 | 30 | server.on('close', function () { 31 | serverSocket.close(); 32 | }); 33 | 34 | client.on('close', function () { 35 | clientSocket.close(); 36 | }); 37 | 38 | server.on('connection', function (connection) { 39 | connection.on('data', function (data) { 40 | console.log(data.toString('utf8')); 41 | }); 42 | }); 43 | 44 | client.send(new Buffer('Hello, World!')); 45 | client.send(new Buffer('How are you doing?')); 46 | -------------------------------------------------------------------------------- /test/unit/PacketTest.js: -------------------------------------------------------------------------------- 1 | var Packet = require('../../lib/Packet'); 2 | var expect = require('expect.js'); 3 | 4 | describe('Packet', function () { 5 | describe('#Packet and #toBuffer', function () { 6 | it('should be able to create a new packet, output a buffer, and from it, create the packet again', function () { 7 | var sequenceNumber = 10; 8 | var isSynchronize = true 9 | var packet = new Packet(sequenceNumber, new Buffer('test'), isSynchronize); 10 | var generatedPacket = new Packet(packet.toBuffer()); 11 | expect(generatedPacket.getIsAcknowledgement()).to.be(false); 12 | expect(generatedPacket.getIsSynchronize()).to.be(isSynchronize); 13 | expect(generatedPacket.getIsFinish()).to.be(false); 14 | expect(generatedPacket.getSequenceNumber()).to.be(sequenceNumber); 15 | expect(generatedPacket.getPayload().toString('utf8')).to.be('test'); 16 | }); 17 | }); 18 | describe('#clone() and #equals()', function () { 19 | it('should clone the packet into two entirely different objects, yet have identical values. Equals should be able to determine equality, in-spite of different objects references', function () { 20 | var sequenceNumber = 10; 21 | var isSynchronize = true; 22 | var isReset = true; 23 | var packet = new Packet(sequenceNumber, new Buffer('test'), isSynchronize, isReset); 24 | var packetCopy = packet.clone(); 25 | expect(packet).to.not.be(packetCopy); 26 | expect(packet.equals(packetCopy)).to.be(true); 27 | }); 28 | }); 29 | }); -------------------------------------------------------------------------------- /test/unit/helpersTest.js: -------------------------------------------------------------------------------- 1 | var helpers = require('../../lib/helpers.js'); 2 | var expect = require('expect.js'); 3 | 4 | describe('splitArrayLike', function () { 5 | it('should return an empty array for buffers of length 0', function () { 6 | expect(helpers.splitArrayLike(new Buffer([])).length).to.be(0); 7 | }); 8 | it('should return an array with one element for buffers of the length specified', function () { 9 | var buf = helpers.splitArrayLike(new Buffer(16), 16); 10 | expect(buf.length).to.be(1); 11 | expect(buf[0].length).to.be(16); 12 | }); 13 | it('should return an array with all elements having a length of the supplied length', function () { 14 | var length = 16; 15 | var numberOfChunks = 10; 16 | var chunks = helpers.splitArrayLike(new Buffer(length * numberOfChunks), length); 17 | 18 | expect(chunks.length).to.be(numberOfChunks); 19 | 20 | chunks.forEach(function (chunk) { 21 | expect(chunk.length).to.be(length); 22 | }); 23 | }); 24 | it('should return an array even for buffers where the number of bytes is not evenly divisible by the specified length of the chunk', function () { 25 | var length = 16; 26 | var numberOfChunks = 10; 27 | var extra = 10; 28 | 29 | var chunks = helpers.splitArrayLike(new Buffer(length * numberOfChunks + extra), length); 30 | 31 | expect(chunks.length).to.be(numberOfChunks + 1); 32 | chunks.forEach(function (chunk, i) { 33 | if (i === chunks.length - 1) { 34 | expect(chunk.length).to.be(extra); 35 | } else { 36 | expect(chunk.length).to.be(length); 37 | } 38 | }); 39 | }); 40 | }); -------------------------------------------------------------------------------- /lib/Server.js: -------------------------------------------------------------------------------- 1 | var Packet = require('./Packet'); 2 | var Connection = require('./Connection'); 3 | var EventEmitter = require('events').EventEmitter; 4 | var util = require('util'); 5 | var PacketSender = require('./PacketSender'); 6 | 7 | module.exports = Server; 8 | 9 | /** 10 | * The constructor function for a "server" in our RUDP construct. 11 | * @param {dgram.Socket} socket Node.js' dgram.Socket. 12 | */ 13 | function Server(socket) { 14 | // A list of all connections. 15 | this._connections = {}; 16 | 17 | var self = this; 18 | 19 | socket.on('message', function (message, rinfo) { 20 | var addressKey = rinfo.address + rinfo.port; 21 | var connection; 22 | 23 | // Get connection. 24 | if (!self._connections[addressKey]) { 25 | // Record a new connection to the list of connections. 26 | 27 | connection = new Connection(new PacketSender(socket, rinfo.address, rinfo.port)); 28 | self._connections[addressKey] = connection; 29 | self.emit('connection', connection); 30 | } else { 31 | // Just get the existing connection. 32 | 33 | connection = self._connections[addressKey]; 34 | } 35 | 36 | // Parse the message. 37 | var packet = new Packet(message); 38 | 39 | if (packet.getIsFinish()) { 40 | // The client requested that the connection be closed. 41 | 42 | delete self._connections[addressKey]; 43 | } else { 44 | // Capture the packet, and place it into the window of packets. 45 | 46 | setImmediate(function () { 47 | connection.receive(packet); 48 | }); 49 | } 50 | 51 | }); 52 | }; 53 | 54 | util.inherits(Server, EventEmitter); -------------------------------------------------------------------------------- /lib/LinkedList.js: -------------------------------------------------------------------------------- 1 | // This is a private class. 2 | function Node(value) { 3 | this.value = value; 4 | this._childNode = null; 5 | } 6 | 7 | // TODO: do note that this is an ordered linked-list. Perhaps this class should 8 | // be renamed to better indicate that. 9 | // TODO: use a generator/iterator pattern for looping through the list. 10 | 11 | /** 12 | * Represents a linkedList, and accepts any type, so long as it can be 13 | * successfully parsed by the ordering function. 14 | * 15 | * @class LinkedList 16 | * @constructor 17 | */ 18 | module.exports = LinkedList; 19 | function LinkedList(orderBy) { 20 | this._childNode = null; 21 | this._orderBy = orderBy; 22 | this._currentNode = null; 23 | } 24 | 25 | var InsertionResult = LinkedList.InsertionResult = { 26 | INSERTED: 'inserted', 27 | EXISTS: 'exists', 28 | FAILED: 'failed' 29 | }; 30 | 31 | LinkedList.prototype.insert = function (object) { 32 | if (!this._childNode) { 33 | this._childNode = new Node(object); 34 | this._currentNode = this._childNode; 35 | return InsertionResult.INSERTED; 36 | } 37 | return this._insert(this, object); 38 | }; 39 | 40 | LinkedList.prototype.clear = function () { 41 | this._childNode = null; 42 | this._currentNode = null; 43 | }; 44 | 45 | LinkedList.prototype.resetIndex = function () { 46 | this._currentNode = this._childNode; 47 | }; 48 | 49 | LinkedList.prototype.seek = function () { 50 | if (!this._currentNode) { 51 | return false; 52 | } 53 | 54 | if (!this._currentNode._childNode) { 55 | return false; 56 | } 57 | 58 | this._currentNode = this._currentNode._childNode; 59 | return true; 60 | }; 61 | 62 | LinkedList.prototype.currentValue = function () { 63 | if (!this._currentNode) { 64 | throw new Error('There aren\'t any nodes on the list.'); 65 | } 66 | return this._currentNode.value; 67 | }; 68 | 69 | LinkedList.prototype.hasValue = function () { 70 | return !!this._childNode; 71 | }; 72 | 73 | LinkedList.prototype.nextValue = function () { 74 | if (!this._currentNode) { 75 | throw new Error('There aren\'t any nodes on the list.'); 76 | } else if (!this._currentNode._childNode) { 77 | throw new Error('The current node does not have any child nodes'); 78 | } 79 | return this._currentNode._childNode.value; 80 | }; 81 | 82 | LinkedList.prototype.hasNext = function () { 83 | return !!this._currentNode._childNode; 84 | }; 85 | 86 | LinkedList.prototype.toArray = function () { 87 | return this._toArray(this, []) 88 | }; 89 | 90 | LinkedList.prototype._toArray = function (node, accum) { 91 | if (!node._childNode) { return accum; } 92 | return this._toArray(node._childNode, accum.concat([node._childNode.value])); 93 | }; 94 | 95 | LinkedList.prototype._insert = function (parentNode, object) { 96 | if (!parentNode._childNode) { 97 | parentNode._childNode = new Node(object); 98 | return InsertionResult.INSERTED; 99 | } 100 | 101 | var order = this._orderBy(object, parentNode._childNode.value); 102 | 103 | if (order <= -1) { 104 | var node = new Node(object); 105 | node._childNode = parentNode._childNode; 106 | parentNode._childNode = node; 107 | return InsertionResult.INSERTED; 108 | } else if (order >= 1) { 109 | return this._insert(parentNode._childNode, object); 110 | } else if (order === 0) { 111 | return InsertionResult.EXISTS; 112 | } 113 | 114 | return InsertionResult.FAILED; 115 | }; -------------------------------------------------------------------------------- /lib/Packet.js: -------------------------------------------------------------------------------- 1 | var bufferEqual = require('./bufferEqual'); 2 | 3 | /** 4 | * Represents a packet. 5 | * 6 | * @class Packet 7 | * @constructor 8 | */ 9 | module.exports = Packet; 10 | function Packet(sequenceNumber, payload, synchronize, reset) { 11 | if (Buffer.isBuffer(sequenceNumber)) { 12 | var segment = sequenceNumber; 13 | 14 | var offset = 0; 15 | 16 | bools = segment.readUInt8(offset); offset++; 17 | this._acknowledgement = !!(bools & 0x80); 18 | this._synchronize = !!(bools & 0x40); 19 | this._finish = !!(bools & 0x20); 20 | this._reset = !!(bools & 0x10); 21 | 22 | this._sequenceNumber = segment.readUInt8(offset); offset++; 23 | this._payload = new Buffer(segment.length - offset); 24 | segment.copy(this._payload, 0, offset); 25 | } else { 26 | this._acknowledgement = false; 27 | this._synchronize = !!synchronize; 28 | this._finish = false; 29 | this._reset = !!reset; 30 | this._sequenceNumber = sequenceNumber; 31 | this._payload = payload; 32 | } 33 | }; 34 | 35 | Packet.createAcknowledgementPacket = function (sequenceNumber) { 36 | var packet = new Packet(sequenceNumber, new Buffer(0), false); 37 | packet._acknowledgement = true; 38 | return packet; 39 | }; 40 | 41 | Packet.createFinishPacket = function () { 42 | var packet = new Packet(0, new Buffer(0), false, false); 43 | packet._finish = true; 44 | return packet; 45 | }; 46 | 47 | Packet.prototype.getIsAcknowledgement = function () { 48 | return this._acknowledgement; 49 | }; 50 | 51 | Packet.prototype.getIsSynchronize = function () { 52 | return this._synchronize; 53 | }; 54 | 55 | Packet.prototype.getIsFinish = function () { 56 | return this._finish; 57 | }; 58 | 59 | Packet.prototype.getSequenceNumber = function () { 60 | return this._sequenceNumber; 61 | }; 62 | 63 | Packet.prototype.getPayload = function () { 64 | return this._payload; 65 | }; 66 | 67 | Packet.prototype.getIsReset = function () { 68 | return this._reset; 69 | }; 70 | 71 | /** 72 | * Get a byte array based on the meta data. 73 | * 74 | * @method toBuffer 75 | */ 76 | Packet.prototype.toBuffer = function () { 77 | var offset = 0; 78 | var retval = new Buffer(2 + this._payload.length); 79 | 80 | var bools = 0 + ( 81 | (this._acknowledgement && 0x80) | 82 | (this._synchronize && 0x40) | 83 | (this._finish && 0x20) | 84 | (this._reset && 0x10) 85 | ); 86 | 87 | retval.writeUInt8(bools, offset); offset++; 88 | retval.writeUInt8(this._sequenceNumber, offset); offset++; 89 | 90 | this._payload.copy(retval, offset, 0); 91 | 92 | return retval; 93 | } 94 | 95 | Packet.prototype.clone = function () { 96 | return new Packet(this.toBuffer()); 97 | }; 98 | 99 | Packet.prototype.toObject = function () { 100 | return { 101 | acknowledgement: this.getIsAcknowledgement(), 102 | synchronize: this.getIsSynchronize(), 103 | finish: this.getIsFinish(), 104 | reset: this.getIsReset(), 105 | sequenceNumber: this.getSequenceNumber(), 106 | payload: this.getPayload().toString('base64') 107 | } 108 | }; 109 | 110 | Packet.prototype.equals = function (packet) { 111 | return ( 112 | this.getIsAcknowledgement() === packet.getIsAcknowledgement() && 113 | this.getIsSynchronize() === packet.getIsSynchronize() && 114 | this.getIsFinish() === packet.getIsFinish() && 115 | this.getIsReset() === packet.getIsReset() && 116 | this.getSequenceNumber() === packet.getSequenceNumber() && 117 | bufferEqual(this.getPayload(), packet.getPayload()) 118 | ) 119 | }; -------------------------------------------------------------------------------- /lib/Receiver.js: -------------------------------------------------------------------------------- 1 | var LinkedList = require('./LinkedList'); 2 | var constants = require('./constants'); 3 | var EventEmitter = require('events').EventEmitter; 4 | var util = require('util'); 5 | var Packet = require('./Packet'); 6 | 7 | // TODO: have this be a DuplexStream instead of an EventEmitter. 8 | // TODO: the Receiver should never send raw packets to the end host. It should 9 | // only be acknowledgement packets. Please see [1] 10 | 11 | module.exports = Receiver; 12 | function Receiver(packetSender) { 13 | this._synced = false; 14 | this._nextSequenceNumber = 0; 15 | this._packets = new LinkedList(function (packetA, packetB) { 16 | return packetA.getSequenceNumber() - packetB.getSequenceNumber(); 17 | }); 18 | this._packetSender = packetSender; 19 | this._closed = false; 20 | } 21 | util.inherits(Receiver, EventEmitter); 22 | 23 | Receiver.prototype.receive = function (packet) { 24 | if (this._closed) { 25 | // Since this is closed, don't do anything. 26 | return; 27 | } 28 | 29 | // Ignores packets that have a sequence number less than the next sequence 30 | // number 31 | if (!packet.getIsSynchronize() && packet.getSequenceNumber() < this._syncSequenceNumber) { 32 | return; 33 | } 34 | 35 | if (packet.getIsSynchronize() && !this._synced) { 36 | // This is the beginning of the stream. 37 | 38 | if (packet.getSequenceNumber() === this._syncSequenceNumber) { 39 | this._packetSender.send(Packet.createAcknowledgementPacket(packet.getSequenceNumber())); 40 | return; 41 | } 42 | 43 | // Send the packet upstream, send acknowledgement packet to end host, and 44 | // increment the next expected packet. 45 | this._packets.clear(); 46 | this.emit('data', packet.getPayload()); 47 | this._packetSender.send(Packet.createAcknowledgementPacket(packet.getSequenceNumber())); 48 | this._packets.insert(packet); 49 | this._nextSequenceNumber = packet.getSequenceNumber() + 1; 50 | this._synced = true; 51 | this._syncSequenceNumber = packet.getSequenceNumber(); 52 | 53 | 54 | if (packet.getIsReset()) { 55 | this.emit('_reset'); 56 | this._synced = false; 57 | } 58 | 59 | // We're done. 60 | return; 61 | 62 | } else if (packet.getIsReset()) { 63 | this.emit('_reset'); 64 | this._synced = false; 65 | } else if (!this._synced) { 66 | // If we are not synchronized with sender, then this means that we should 67 | // wait for the end host to send a synchronization packet. 68 | 69 | // We are done. 70 | return; 71 | } else if (packet.getSequenceNumber() < this._syncSequenceNumber) { 72 | // This is a troll packet. Ignore it. 73 | 74 | return; 75 | } else if ( 76 | packet.getSequenceNumber() >= 77 | this._packets.currentValue().getSequenceNumber() + constants.WINDOW_SIZE 78 | ) { 79 | // This means that the next packet received is not within the window size. 80 | 81 | this.emit('_window_size_exceeded'); 82 | return; 83 | } 84 | 85 | // This means that we should simply insert the packet. If the packet's 86 | // sequence number is the one that we were expecting, then send it upstream, 87 | // acknowledge the packet, and increment the next expected sequence number. 88 | // 89 | // Once acknowledged, check to see if there aren't any more pending packets 90 | // after the current packet. If there are, then check to see if the next 91 | // packet is the expected packet number. If it is, then start the 92 | // acknowledgement process anew. 93 | 94 | var result = this._packets.insert(packet); 95 | if (result === LinkedList.InsertionResult.INSERTED) { 96 | this._pushIfExpectedSequence(packet); 97 | } else if (result === LinkedList.InsertionResult.EXISTS) { 98 | this._packetSender.send(Packet.createAcknowledgementPacket(packet.getSequenceNumber())); 99 | } 100 | }; 101 | 102 | Receiver.prototype._pushIfExpectedSequence = function (packet) { 103 | if (packet.getSequenceNumber() === this._nextSequenceNumber) { 104 | this.emit('data', packet.getPayload()); 105 | // [1] Never send packets directly! 106 | this._packetSender.send(Packet.createAcknowledgementPacket(packet.getSequenceNumber())); 107 | this._nextSequenceNumber++; 108 | this._packets.seek(); 109 | if (this._packets.hasNext()) { 110 | this._pushIfExpectedSequence(this._packets.nextValue()); 111 | } 112 | } 113 | }; 114 | 115 | Receiver.prototype.end = function () { 116 | this._closed = true; 117 | this.emit('end'); 118 | }; 119 | -------------------------------------------------------------------------------- /lib/Sender.js: -------------------------------------------------------------------------------- 1 | var helpers = require('./helpers'); 2 | var constants = require('./constants'); 3 | var PendingPacket = require('./PendingPacket'); 4 | var Packet = require('./Packet'); 5 | var EventEmitter = require('events').EventEmitter; 6 | var util = require('util'); 7 | 8 | // TODO: the sender should never send acknowledgement or finish packets. 9 | // TODO: have the ability to cancel the transfer of any pending packet. 10 | 11 | function Window(packets) { 12 | this._packets = packets; 13 | } 14 | util.inherits(Window, EventEmitter); 15 | 16 | Window.prototype.send = function () { 17 | // Our packets to send. 18 | var pkts = this._packets.slice(); 19 | 20 | // The initial synchronization packet. Always send this first. 21 | this._synchronizationPacket = pkts.shift(); 22 | // The final reset packet. It can equal to the synchronization packet. 23 | this._resetPacket = pkts.length ? pkts.pop() : this._synchronizationPacket 24 | 25 | // This means that the reset packet's acknowledge event thrown will be 26 | // different from that of the synchronization packet. 27 | if (this._resetPacket !== this._synchronizationPacket) { 28 | this._resetPacket.on('acknowledge', function () { 29 | self.emit('done'); 30 | }); 31 | } 32 | 33 | var self = this; 34 | 35 | // Will be used to handle the case when all non sync or reset packets have 36 | // been acknowledged. 37 | var emitter = new EventEmitter(); 38 | 39 | this._synchronizationPacket.on('acknowledge', function () { 40 | // We will either notify the owning class that this window has finished 41 | // sending all of its packets (that is, if this window only had one packet 42 | // in it), or keep looping through each each non sync-reset packets until 43 | // they have been acknowledged. 44 | 45 | if (self._resetPacket === self._synchronizationPacket) { 46 | self.emit('done'); 47 | return; 48 | } else if (pkts.length === 0) { 49 | // This means that this window only had two packets, and the second one 50 | // was a reset packet. 51 | self._resetPacket.send(); 52 | return; 53 | } 54 | 55 | emitter.on('acknowledge', function () { 56 | // This means that it is now time to send the reset packet. 57 | self._resetPacket.send(); 58 | }); 59 | 60 | // And if there are more than two packets in this window, then send all 61 | // other packets. 62 | 63 | var acknowledged = 0; 64 | 65 | function onAcknowledge() { 66 | acknowledged++; 67 | if (acknowledged === pkts.length) { 68 | emitter.emit('acknowledge'); 69 | } 70 | } 71 | 72 | pkts.forEach(function (packet) { 73 | packet.on('acknowledge', onAcknowledge); 74 | packet.send(); 75 | }); 76 | }); 77 | 78 | this._synchronizationPacket.send(); 79 | }; 80 | 81 | Window.prototype.verifyAcknowledgement = function (sequenceNumber) { 82 | for (var i = 0; i < this._packets.length; i++) { 83 | if (this._packets[i].getSequenceNumber() === sequenceNumber) { 84 | this._packets[i].acknowledge(); 85 | } 86 | } 87 | }; 88 | 89 | /** 90 | * An abstraction of sending raw UDP packets using the Go-Back-N protocol. 91 | * 92 | * @class Sender 93 | * @constructor 94 | */ 95 | module.exports = Sender; 96 | function Sender(packetSender) { 97 | this._packetSender = packetSender; 98 | this._windows = []; 99 | this._sending = null; 100 | } 101 | 102 | /** 103 | * Sends data to the remote host. 104 | * 105 | * @class Sender 106 | * @method 107 | */ 108 | Sender.prototype.send = function (data) { 109 | var chunks = helpers.splitArrayLike(data, constants.UDP_SAFE_SEGMENT_SIZE); 110 | var windows = helpers.splitArrayLike(chunks, constants.WINDOW_SIZE); 111 | this._windows = this._windows.concat(windows); 112 | this._push(); 113 | } 114 | 115 | /* 116 | * Pushes out the data to the remote host. 117 | */ 118 | Sender.prototype._push = function () { 119 | var self = this; 120 | if (!this._sending && this._windows.length) { 121 | this._baseSequenceNumber = Math.floor(Math.random() * (constants.MAX_SIZE - constants.WINDOW_SIZE)); 122 | var window = this._windows.shift() 123 | var toSend = new Window(window.map(function (data, i) { 124 | var packet = new Packet(i + self._baseSequenceNumber, data, !i, i === window.length - 1); 125 | return new PendingPacket(packet, self._packetSender); 126 | })); 127 | this._sending = toSend; 128 | var self = this; 129 | this._sending.on('done', function () { 130 | self._sending = null; 131 | self._push(); 132 | }); 133 | toSend.send(); 134 | } 135 | } 136 | 137 | /** 138 | * Verifies whether or not the acknowledgement number is within the Window. 139 | * 140 | * @class Sender 141 | * @method 142 | */ 143 | Sender.prototype.verifyAcknowledgement = function (sequenceNumber) { 144 | if (this._sending) { 145 | this._sending.verifyAcknowledgement(sequenceNumber); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /test/unit/LinkedListTest.js: -------------------------------------------------------------------------------- 1 | var LinkedList = require('../../lib/LinkedList'); 2 | var expect = require('expect.js'); 3 | var helpers = require('../../lib/helpers'); 4 | 5 | describe('LinkedList', function () { 6 | describe('#insert, #seek, #currentValue, #resetIndex', function () { 7 | it('should be able to insert elements of an unordered list, and get back an ordered list', function () { 8 | var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 9 | var shuffled = helpers.shuffle(arr.slice()); 10 | expect(shuffled).to.not.eql(arr); 11 | var list = new LinkedList(function (a, b){ 12 | return a - b; 13 | }); 14 | shuffled.forEach(function (element) { 15 | list.insert(element); 16 | }); 17 | list.resetIndex(); 18 | var ordered = [list.currentValue()]; 19 | while (list.seek()) { 20 | ordered.push(list.currentValue()); 21 | } 22 | expect(ordered).to.eql(arr); 23 | }); 24 | }); 25 | describe('#insert, #clear', function () { 26 | it('should be able to insert elements, and then clear all elements', function () { 27 | var list = new LinkedList(function (a, b) { 28 | return a - b; 29 | }); 30 | [1, 2, 3, 4, 5].forEach(function (element) { 31 | list.insert(element); 32 | }); 33 | var i = 1; 34 | while (list.seek()) { 35 | i++; 36 | } 37 | expect(i).to.be(5); 38 | list.clear(0); 39 | expect(list._childNode).to.be(null); 40 | expect(list._currentNode).to.be(null); 41 | }); 42 | }); 43 | describe('#nextValue', function () { 44 | it('should be able to get the current node\'s child node\'s value', function () { 45 | var list = new LinkedList(function (a, b) { 46 | return a - b; 47 | }); 48 | [1, 2, 3, 4, 5].forEach(function (element) { 49 | list.insert(element); 50 | }); 51 | expect(list.nextValue()).to.be(2); 52 | }); 53 | }); 54 | describe('#hasNext', function () { 55 | it('should return false when the current node is at the head of the list', function () { 56 | var list = new LinkedList(function (a, b) { 57 | return a - b; 58 | }); 59 | [1, 2, 3, 4, 5].forEach(function (element) { 60 | list.insert(element); 61 | }); 62 | while (list.seek()) {} 63 | expect(list.hasNext()).to.be(false); 64 | }); 65 | it('should return true when the current node is not at the head of the list', function () { 66 | var list = new LinkedList(function (a, b) { 67 | return a - b; 68 | }); 69 | [1, 2, 3, 4, 5].forEach(function (element) { 70 | list.insert(element); 71 | }); 72 | expect(list.hasNext()).to.be(true); 73 | list.seek(); 74 | expect(list.hasNext()).to.be(true); 75 | }); 76 | }); 77 | describe('#hasValue', function () { 78 | it('should return false when there aren\'t anything on the list', function () { 79 | var list = new LinkedList(function (a, b) { 80 | return a - b; 81 | }); 82 | expect(list.hasValue()).to.be(false); 83 | }); 84 | it('should return true when there is one thing on the list', function () { 85 | var list = new LinkedList(function (a, b) { 86 | return a - b; 87 | }); 88 | list.insert(1); 89 | expect(list.hasValue()).to.be(true); 90 | }); 91 | it('should return true when there are many things on the list', function () { 92 | var list = new LinkedList(function (a, b) { 93 | return a - b; 94 | }); 95 | [1, 2, 3, 4, 5].forEach(function (element) { 96 | list.insert(element); 97 | }); 98 | expect(list.hasValue()).to.be(true); 99 | }); 100 | it('should return false when there are\'t anything on the list, *after* `clear` has been called', function () { 101 | var list = new LinkedList(function (a, b) { 102 | return a - b; 103 | }); 104 | [1, 2, 3, 4, 5].forEach(function (element) { 105 | list.insert(element); 106 | }); 107 | expect(list.hasValue()).to.be(true); 108 | list.clear(); 109 | expect(list.hasValue()).to.be(false); 110 | }); 111 | it('should return true when there is something on the list, *after* `clear` has been called and elements have been added', function () { 112 | var list = new LinkedList(function (a, b) { 113 | return a - b; 114 | }); 115 | [1, 2, 3, 4, 5].forEach(function (element) { 116 | list.insert(element); 117 | }); 118 | expect(list.hasValue()).to.be(true); 119 | list.clear(); 120 | expect(list.hasValue()).to.be(false); 121 | [1, 2, 3, 4, 5].forEach(function (element) { 122 | list.insert(element); 123 | }); 124 | expect(list.hasValue()).to.be(true); 125 | }); 126 | }); 127 | describe('#toArray', function () { 128 | it('should take the element in the list and turn it into an array', function () { 129 | var list = new LinkedList(function (a, b) { 130 | return a - b; 131 | }); 132 | [1, 2, 3, 4, 5].forEach(function (element) { 133 | list.insert(element); 134 | }); 135 | var arr = list.toArray().map(function (a) { return a.toString(); }).join(''); 136 | expect(arr).to.be('12345'); 137 | }); 138 | }); 139 | }); -------------------------------------------------------------------------------- /test/unit/ReceiverTest.js: -------------------------------------------------------------------------------- 1 | var Receiver = require('../../lib/Receiver'); 2 | var expect = require('expect.js'); 3 | var Packet = require('../../lib/Packet'); 4 | var helpers = require('../../lib/helpers'); 5 | var sinon = require('sinon'); 6 | 7 | describe('Receiver', function () { 8 | // TODO: test whether the receiver is able to send acknowledgement packets. 9 | 10 | describe('#_closed, #end() and event end', function () { 11 | it('should trigger an event called end', function (done) { 12 | var receiver = new Receiver(); 13 | expect(receiver._closed).to.be(false); 14 | receiver.on('end', function () { 15 | expect(receiver._closed).to.be(true); 16 | done(); 17 | }); 18 | receiver.end(); 19 | }); 20 | }); 21 | describe('#receive', function () { 22 | 23 | // TODO: test with non-zero initial sequence numbers. 24 | 25 | it('should deliver a single packet upstream just fine', function (done) { 26 | var spySender = { 27 | send: sinon.spy() 28 | }; 29 | var receiver = new Receiver(spySender); 30 | var dummyData = 'Hello, World! This is a test!'; 31 | var compiled = ''; 32 | receiver.on('data', function (data) { 33 | compiled += data.toString('utf8'); 34 | }); 35 | receiver.on('end', function () { 36 | expect(compiled).to.be(dummyData); 37 | expect(spySender.send.callCount).to.be(1); 38 | done(); 39 | }); 40 | receiver.receive(new Packet(0, new Buffer(dummyData), true)); 41 | receiver.end(); 42 | }); 43 | 44 | it('should deliver multiple unordered packets upstream just fine, and in order', function (done) { 45 | var spySender = { 46 | send: sinon.spy() 47 | }; 48 | var receiver = new Receiver(spySender); 49 | var dummyData = 'Hello, World! HA'; 50 | var packets = dummyData.split('').map(function (character, i) { 51 | return new Packet(i, new Buffer(character), i === 0); 52 | }); 53 | var first = packets.shift(); 54 | helpers.shuffle(packets); 55 | packets.unshift(first); 56 | expect(packets.map(function (element) { 57 | return element.getPayload().toString('utf8'); 58 | }).join('')).to.not.be(dummyData); 59 | var compiled = ''; 60 | receiver.on('data', function (data) { 61 | compiled += data.toString('utf8'); 62 | }); 63 | receiver.on('end', function () { 64 | expect(compiled).to.be(dummyData); 65 | expect(spySender.send.callCount).to.be(16); 66 | done(); 67 | }); 68 | packets.forEach(function (packet) { 69 | receiver.receive(packet); 70 | }); 71 | receiver.end(); 72 | }); 73 | 74 | it('should deliver multiple unordered packets upstream just fine, and in order, even with a non-zero intial sequence number.', function (done) { 75 | var spySender = { 76 | send: sinon.spy() 77 | }; 78 | var receiver = new Receiver(spySender); 79 | var dummyData = 'Hello, World! HA'; 80 | var packets = dummyData.split('').map(function (character, i) { 81 | return new Packet(i + 64, new Buffer(character), i === 0); 82 | }); 83 | var first = packets.shift(); 84 | helpers.shuffle(packets); 85 | packets.unshift(first); 86 | expect(packets.map(function (element) { 87 | return element.getPayload().toString('utf8'); 88 | }).join('')).to.not.be(dummyData); 89 | var compiled = ''; 90 | receiver.on('data', function (data) { 91 | compiled += data.toString('utf8'); 92 | }); 93 | receiver.on('end', function () { 94 | expect(compiled).to.be(dummyData); 95 | expect(spySender.send.callCount).to.be(16); 96 | done(); 97 | }); 98 | packets.forEach(function (packet) { 99 | receiver.receive(packet); 100 | }); 101 | receiver.end(); 102 | }); 103 | 104 | it('should handle duplicate synchronize packets just fine', function (done) { 105 | var spySender = { 106 | send: sinon.spy() 107 | }; 108 | var receiver = new Receiver(spySender); 109 | var dummyData = 'Hello, World! HA'; 110 | var packets = dummyData.split('').map(function (character, i) { 111 | return new Packet(i, new Buffer(character), i === 0); 112 | }); 113 | var first = packets.shift(); 114 | var firstCopy = first.clone(); 115 | packets.push(firstCopy); 116 | helpers.shuffle(packets); 117 | packets.unshift(first); 118 | expect(packets.map(function (element) { 119 | return element.getPayload().toString('utf8'); 120 | }).join('')).to.not.be(dummyData); 121 | var compiled = ''; 122 | receiver.on('data', function (data) { 123 | compiled += data.toString('utf8'); 124 | }); 125 | receiver.on('end', function () { 126 | expect(compiled).to.be(dummyData); 127 | expect(spySender.send.callCount).to.be(17); 128 | done(); 129 | }); 130 | packets.forEach(function (packet) { 131 | receiver.receive(packet); 132 | }); 133 | receiver.end(); 134 | }); 135 | 136 | it('should handle duplicate packets just fine', function (done) { 137 | var spySender = { 138 | send: sinon.spy() 139 | } 140 | var receiver = new Receiver(spySender); 141 | var dummyData = 'Hello, World! HA'; 142 | var packets = dummyData.split('').map(function (character, i) { 143 | return new Packet(i, new Buffer(character), i === 0); 144 | }); 145 | var first = packets.shift(); 146 | var packetClone = packets[Math.floor(Math.random() * packets.length)].clone(); 147 | packets.push(packetClone); 148 | helpers.shuffle(packets); 149 | packets.unshift(first); 150 | expect(packets.map(function (element, i) { 151 | return element.getPayload().toString('utf8'); 152 | }).join('')).to.not.be(dummyData); 153 | var compiled = ''; 154 | receiver.on('data', function (data) { 155 | compiled += data.toString('utf8'); 156 | }); 157 | receiver.on('end', function () { 158 | expect(compiled).to.be(dummyData); 159 | expect(spySender.send.callCount).to.be(17); 160 | done(); 161 | }); 162 | packets.forEach(function (packet) { 163 | receiver.receive(packet); 164 | }); 165 | receiver.end(); 166 | }); 167 | 168 | it('should be able to handle resets and synchronization', function (done) { 169 | var spySender = { 170 | send: sinon.spy() 171 | }; 172 | var receiver = new Receiver(spySender); 173 | var expected = 'Hello, World! This is a test!'; 174 | var dummyData = helpers.splitArrayLike(expected, 16); 175 | var windows = dummyData.map(function (window, i) { 176 | var withPackets = window.split('').map(function (character, j) { 177 | return new Packet(j + 64 / (i + 1), new Buffer(character), j === 0, j === window.length - 1); 178 | }); 179 | var tail = withPackets.shift(); 180 | var head = withPackets.pop(); 181 | helpers.shuffle(withPackets); 182 | withPackets.unshift(tail); 183 | withPackets.push(head); 184 | return withPackets; 185 | }); 186 | var resetSpy = sinon.spy(); 187 | receiver.on('_reset', function () { 188 | resetSpy(); 189 | }); 190 | var compiled = ''; 191 | receiver.on('data', function (data) { 192 | compiled += data.toString('utf8'); 193 | }); 194 | receiver.on('end', function () { 195 | expect(compiled).to.be(expected); 196 | expect(spySender.send.callCount).to.be(expected.length); 197 | done(); 198 | }); 199 | windows.forEach(function (window) { 200 | window.forEach(function (packet) { 201 | receiver.receive(packet); 202 | }); 203 | }); 204 | receiver.end(); 205 | }); 206 | 207 | it('should handle duplicate reset packets', function (done) { 208 | var spySender = { 209 | send: sinon.spy() 210 | }; 211 | var receiver = new Receiver(spySender); 212 | var expected = 'Hello, World! This is a test!'; 213 | var dummyData = helpers.splitArrayLike(expected, 16); 214 | var windows = dummyData.map(function (window, i) { 215 | var withPackets = window.split('').map(function (character, j) { 216 | return new Packet(j + 64 / (i + 1), new Buffer(character), j === 0, j === window.length - 1); 217 | }); 218 | var tail = withPackets.shift(); 219 | var head = withPackets.pop(); 220 | var headCopy = head.clone(); 221 | helpers.shuffle(withPackets); 222 | withPackets.unshift(tail); 223 | withPackets.push(head); 224 | withPackets.push(headCopy); 225 | return withPackets; 226 | }); 227 | var resetSpy = sinon.spy(); 228 | receiver.on('_reset', function () { 229 | resetSpy(); 230 | }); 231 | var compiled = ''; 232 | receiver.on('data', function (data) { 233 | compiled += data.toString('utf8'); 234 | }); 235 | receiver.on('end', function () { 236 | expect(compiled).to.be(expected); 237 | expect(spySender.send.callCount).to.be(expected.length + 2); 238 | done(); 239 | }); 240 | windows.forEach(function (window) { 241 | window.forEach(function (packet) { 242 | receiver.receive(packet); 243 | }); 244 | }); 245 | receiver.end(); 246 | }); 247 | 248 | it('should be able to accept singleton windows', function (done) { 249 | var spySender = { 250 | send: sinon.spy() 251 | }; 252 | var receiver = new Receiver(spySender); 253 | var expected = 'Hello, World!'; 254 | var dummyData = expected.split(''); 255 | var windows = dummyData.map(function (packet, i) { 256 | return new Packet(i, new Buffer(packet), true, true); 257 | }); 258 | var compiled = ''; 259 | receiver.on('data', function (data) { 260 | compiled += data; 261 | }); 262 | receiver.on('end', function () { 263 | expect(compiled).to.be(expected); 264 | done(); 265 | }); 266 | windows.forEach(function (packet) { 267 | receiver.receive(packet); 268 | }); 269 | receiver.end(); 270 | }); 271 | 272 | it('should be able to accept duplicate singletons', function (done) { 273 | var spySender = { 274 | send: sinon.spy() 275 | }; 276 | var receiver = new Receiver(spySender); 277 | var expected = 'Hello, World!'; 278 | var dummyData = expected.split(''); 279 | var windows = dummyData.map(function (packet, i) { 280 | return new Packet(i, new Buffer(packet), true, true); 281 | }); 282 | var index = Math.floor(Math.random() * windows.length); 283 | var duplicate = windows[index].clone(); 284 | var left = windows.slice(0, index); 285 | var right = windows.slice(index, windows.length); 286 | left.push(duplicate); 287 | windows = left.concat(right); 288 | var compiled = ''; 289 | receiver.on('data', function (data) { 290 | compiled += data; 291 | }); 292 | receiver.on('end', function () { 293 | expect(compiled).to.be(expected); 294 | expect(spySender.send.callCount).to.be(windows.length); 295 | done(); 296 | }); 297 | windows.forEach(function (packet) { 298 | receiver.receive(packet); 299 | }); 300 | receiver.end(); 301 | }); 302 | 303 | it('should ignore packets that have a sequence number less than the next sequence number', function (done) { 304 | var spySender = { 305 | send: sinon.spy() 306 | }; 307 | var receiver = new Receiver(spySender); 308 | var expected = 'Hello, World!'; 309 | var dummyData = expected.split(''); 310 | var packets = dummyData.map(function (packet, i) { 311 | return new Packet(i + 10, new Buffer(packet), i === 0, i === dummyData.length - 1); 312 | }); 313 | var compiled = ''; 314 | var packets = packets 315 | .slice(0, Math.floor(packets.length / 2)) 316 | .concat([new Packet(0, new Buffer('test'), false, false)]) 317 | .concat( 318 | packets.slice(Math.floor(packets.length / 2)) 319 | ); 320 | receiver.on('data', function (data) { 321 | compiled += data; 322 | }); 323 | receiver.on('end', function () { 324 | expect(spySender.send.callCount).to.be(packets.length - 1); 325 | done(); 326 | }); 327 | packets.forEach(function (packet) { 328 | receiver.receive(packet); 329 | }); 330 | receiver.end(); 331 | }); 332 | 333 | }); 334 | }); 335 | --------------------------------------------------------------------------------