diff --git a/lib/cipherkeyiv.js b/lib/cipherkeyiv.js index 1f9dd546..86401aa3 100644 --- a/lib/cipherkeyiv.js +++ b/lib/cipherkeyiv.js @@ -7,8 +7,8 @@ var utils = require('./utils'); * Create a new random cipher key and initialization vector * @constructor * @license LGPL-3.0 - * @param {String|Buffer} password - The unique cipher password - * @param {String|Buffer} salt - The unique salt + * @param {String|Buffer} [password] - The unique cipher password + * @param {String|Buffer} [salt] - The unique salt */ function DataCipherKeyIv(pass, salt) { if (!(this instanceof DataCipherKeyIv)) { diff --git a/lib/datachannel/client.js b/lib/datachannel/client.js index c13d1317..7e177350 100644 --- a/lib/datachannel/client.js +++ b/lib/datachannel/client.js @@ -34,6 +34,17 @@ function DataChannelClient(contact) { this._client.on('error', this._handleChannelError.bind(this)); } +/** + * Triggered when the connection is opened + * @event DataChannelClient#open + */ + +/** + * Triggered when a error occurs + * @event DataChannelClient#error + * @param {Error} error - The error object + */ + inherits(DataChannelClient, events.EventEmitter); /** diff --git a/lib/datachannel/server.js b/lib/datachannel/server.js index d1d533de..9e4a8c46 100644 --- a/lib/datachannel/server.js +++ b/lib/datachannel/server.js @@ -40,6 +40,30 @@ function DataChannelServer(options) { this._server.on('error', this._handleError.bind(this)); } +/** + * Triggered when a shard has finished uploading to this instance + * @event DataChannelServer#shardUploaded + * @param {StorageItem} item - The item associated with the upload + */ + +/** + * Triggered when a shard has finished downloading from this instance + * @event DataChannelServer#shardDownloaded + * @param {StorageItem} item - The item associated with the download + */ + +/** + * Triggered when a connection is opened + * @event DataChannelServer#connection + * @param {WebSocket} socket - The socket connection opened + */ + +/** + * Triggered when a error occurs + * @event DataChannelServer#error + * @param {Error} error - The error object + */ + inherits(DataChannelServer, events.EventEmitter); /** @@ -230,6 +254,7 @@ DataChannelServer.prototype._handleConsignStream = function(socket, token) { item.shard.end(); self._closeSocketSuccess(socket, 'Consignment completed', token); + self.emit('shardUploaded', item); }); }); }; @@ -267,6 +292,7 @@ DataChannelServer.prototype._handleRetrieveStream = function(socket, token) { filestream.on('end', function() { self._closeSocketSuccess(socket, 'File transfer complete', token); + self.emit('shardDownloaded', item); }); }); }; diff --git a/lib/network/index.js b/lib/network/index.js index eb79347a..46dba4ed 100644 --- a/lib/network/index.js +++ b/lib/network/index.js @@ -150,19 +150,20 @@ Network.prototype.join = function(callback) { callback(err, self); } - this._manager.open(function(err) { - if (err) { - return callback(err); - } - - self._setupTunnelClient(function(err) { + async.series( + [ + utils.ensureNtpClockIsSynchronized, + this._manager.open.bind(this._manager), + this._setupTunnelClient.bind(this), + ], + function(err) { if (err) { return callback(err); } self._enterOverlay(onJoinComplete); - }); - }); + } + ); }; /** diff --git a/lib/uploadstate.js b/lib/uploadstate.js index 1fb2946a..a9a972cd 100644 --- a/lib/uploadstate.js +++ b/lib/uploadstate.js @@ -43,6 +43,7 @@ UploadState.prototype.cleanup = function() { fs.unlinkSync(tmpFilePath); } }); + this.queue.kill(); }; module.exports = UploadState; diff --git a/lib/utils.js b/lib/utils.js index b7080530..c810c989 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -5,10 +5,12 @@ 'use strict'; +var constants = require('./constants'); var KeyPair = require('./keypair'); var crypto = require('crypto'); var semver = require('semver'); var ip = require('ip'); +var ntp = require('ntp-client'); var bitcore = require('bitcore-lib'); var ECIES = require('bitcore-ecies'); var base58 = bitcore.deps.bs58; @@ -201,6 +203,49 @@ module.exports.simpleDecrypt = function(password, str) { return buf.toString('utf8'); }; +/** + * Returns the delta between system time and NTP time + * @param {Function} callback - Called with (err, delta) + */ +module.exports.getNtpTimeDelta = function(callback) { + var timeBeforeRequest = new Date(); + + ntp.getNetworkTime( + ntp.defaultNtpServer, + ntp.defaultNtpPort, + function(err, networkTime) { + if (err) { + return callback(err); + } + + var timeAfterResponse = new Date(); + var latency = timeAfterResponse - timeBeforeRequest; + var systemTime = Date.now(); + var delta = networkTime.getTime() - Math.abs(systemTime - latency); + + callback(null, delta); + } + ); +}; + +/** + * Determines if the system clock is syncronized with network + * @param {Function} callback - Called with (err, delta) + */ +module.exports.ensureNtpClockIsSynchronized = function(callback) { + module.exports.getNtpTimeDelta(function(err, delta) { + if (err) { + return callback(err); + } + + if (delta > constants.NONCE_EXPIRE) { + return callback(new Error('System clock is not syncronized with NTP')); + } + + callback(null, delta); + }); +}; + /** * Empty function stub * @private diff --git a/package.json b/package.json index 121a4d53..2f60d75f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "storj", - "version": "2.1.1", + "version": "2.2.0", "description": "implementation of the storj protocol for node.js and the browser", "main": "index.js", "directories": { @@ -79,6 +79,7 @@ "nat-upnp": "^1.0.2", "node-tar.gz": "^1.0.0", "node-uuid": "^1.4.7", + "ntp-client": "^0.5.3", "portfinder": "^1.0.3", "prompt": "^1.0.0", "readable-stream": "^2.0.6", diff --git a/test/network/index.unit.js b/test/network/index.unit.js index 1cc842be..8bc0c877 100644 --- a/test/network/index.unit.js +++ b/test/network/index.unit.js @@ -971,6 +971,7 @@ describe('Network (private)', function() { }); it('should use bridge to get seeds and use them', function(done) { + this.timeout(4000); var net = Network({ keypair: KeyPair(), manager: Manager(RAMStorageAdapter()), @@ -994,6 +995,7 @@ describe('Network (private)', function() { }); it('should do nothing if no seeds or bridge', function(done) { + this.timeout(4000); var net = Network({ keypair: KeyPair(), manager: Manager(RAMStorageAdapter()), diff --git a/test/tunnel/server.unit.js b/test/tunnel/server.unit.js index 316f1908..5e0988f7 100644 --- a/test/tunnel/server.unit.js +++ b/test/tunnel/server.unit.js @@ -64,7 +64,11 @@ describe('TunnelServer', function() { describe('#createGateway', function() { it('should refuse to open tunnel if max is reached', function(done) { - var ts = new TunnelServer({ port: 0, maxTunnels: 0 }); + var ts = new TunnelServer({ + port: 0, + maxTunnels: 0, + portRange: { min: 0, max: 0 } + }); ts.createGateway(function(err) { expect(err.message).to.equal('Maximum number of tunnels open'); done(); @@ -72,7 +76,11 @@ describe('TunnelServer', function() { }); it('should emit the locked event when max reached', function(done) { - var ts = new TunnelServer({ port: 0, maxTunnels: 1 }); + var ts = new TunnelServer({ + port: 0, + maxTunnels: 1, + portRange: { min: 0, max: 0 } + }); var gw = null; ts.on('locked', function() { ts.on('unlocked', done); diff --git a/test/utils.unit.js b/test/utils.unit.js index 40f98d45..6caea3b9 100644 --- a/test/utils.unit.js +++ b/test/utils.unit.js @@ -5,6 +5,9 @@ var utils = require('../lib/utils'); var semver = require('semver'); var version = require('../lib/version'); var KeyPair = require('../lib/keypair'); +var proxyquire = require('proxyquire'); +var sinon = require('sinon'); +var constants = require('../lib/constants'); describe('utils', function() { @@ -149,4 +152,58 @@ describe('utils', function() { }); + describe('#getNtpTimeDelta', function() { + + it('should bubble errors from the ntp client', function(done) { + var stubbedUtils = proxyquire('../lib/utils', { + 'ntp-client': { + getNetworkTime: sinon.stub().callsArgWith( + 2, + new Error('Time paradox occurred') + ) + } + }); + stubbedUtils.getNtpTimeDelta(function(err) { + expect(err.message).to.equal('Time paradox occurred'); + done(); + }); + }); + + }); + + describe('#ensureNtpClockIsSynchronized', function() { + + it('should bubble errors from the ntp client', function(done) { + var stubbedUtils = proxyquire('../lib/utils', { + 'ntp-client': { + getNetworkTime: sinon.stub().callsArgWith( + 2, + new Error('Time paradox occurred') + ) + } + }); + stubbedUtils.ensureNtpClockIsSynchronized(function(err) { + expect(err.message).to.equal('Time paradox occurred'); + done(); + }); + }); + + it('should error is delta is greater than NONCE_EXPIRE', function(done) { + var time = new Date(); + time.setTime(time.getTime() + constants.NONCE_EXPIRE + 2000); + var stubbedUtils = proxyquire('../lib/utils', { + 'ntp-client': { + getNetworkTime: sinon.stub().callsArgWith(2, null, time) + } + }); + stubbedUtils.ensureNtpClockIsSynchronized(function(err) { + expect(err.message).to.equal( + 'System clock is not syncronized with NTP' + ); + done(); + }); + }); + + }); + });