diff --git a/js/models/network/Async.js b/js/models/network/Async.js new file mode 100644 index 000000000..d7cf029a4 --- /dev/null +++ b/js/models/network/Async.js @@ -0,0 +1,410 @@ +'use strict'; + +var imports = require('soop').imports(); +var EventEmitter = imports.EventEmitter || require('events').EventEmitter; +var bitcore = require('bitcore'); +var AuthMessage = bitcore.AuthMessage; +var util = bitcore.util; +var extend = require('util')._extend; +var io = require('socket.io-client'); + +/* + * Emits + * 'connect' + * when network layout has change (new/lost peers, etc) + * + * 'data' + * when an unknown data type arrives + * + * Provides + * send(toPeerIds, {data}, cb?) + * + */ + +function Network(opts) { + var self = this; + opts = opts || {}; + this.host = opts.host || 'localhost'; + this.port = opts.port || 3001; + this.retryDelay = opts.retryDelay || 3000; + this.reconnectAttempts = opts.reconnectAttempts || 3; + this.cleanUp(); +} + +Network.parent = EventEmitter; + +Network.prototype.cleanUp = function() { + this.started = false; + this.connectedPeers = []; + this.peerId = null; + this.privkey = null; + this.key = null; + this.copayerId = null; + this.allowedCopayerIds = null; + this.isInboundPeerAuth = []; + this.copayerForPeer = {}; + this.connections = {}; + this.criticalErr = ''; + this.closing = 0; + this.tries = 0; + this.removeAllListeners(); +}; + +Network.parent = EventEmitter; + +// Array helpers +Network._arrayDiff = function(a, b) { + var seen = []; + var diff = []; + + for (var i = 0; i < b.length; i++) + seen[b[i]] = true; + + for (var j = 0; j < a.length; j++) + if (!seen[a[j]]) + diff.push(a[j]); + + return diff; +}; + +Network._inArray = function(el, array) { + return array.indexOf(el) > -1; +}; + +Network._arrayPushOnce = function(el, array) { + var ret = false; + if (!Network._inArray(el, array)) { + array.push(el); + ret = true; + } + return ret; +}; + +Network._arrayRemove = function(el, array) { + var pos = array.indexOf(el); + if (pos >= 0) array.splice(pos, 1); + return array; +}; + +Network.prototype.connectedCopayers = function() { + var ret = []; + for (var i in this.connectedPeers) { + var copayerId = this.copayerForPeer[this.connectedPeers[i]]; + if (copayerId) ret.push(copayerId); + } + return ret; +}; + +Network.prototype.connectToCopayers = function(copayerIds) { + var self = this; + var arrayDiff = Network._arrayDiff(copayerIds, self.connectedCopayers()); + + arrayDiff.forEach(function(copayerId) { + if (self.allowedCopayerIds && !self.allowedCopayerIds[copayerId]) { + self._deletePeer(self.peerFromCopayer(copayerId)); + } else { + self.connectTo(copayerId); + } + }); +}; + +Network.prototype._sendHello = function(copayerId) { + this.send(copayerId, { + type: 'hello', + copayerId: this.copayerId, + }); +}; + +Network.prototype._deletePeer = function(peerId) { + delete this.isInboundPeerAuth[peerId]; + delete this.copayerForPeer[peerId]; + + if (this.connections[peerId]) { + this.connections[peerId].close(); + } + delete this.connections[peerId]; + this.connectedPeers = Network._arrayRemove(peerId, this.connectedPeers); +}; + +Network.prototype._addConnectedCopayer = function(copayerId, isInbound) { + var peerId = this.peerFromCopayer(copayerId); + this._addCopayerMap(peerId, copayerId); + Network._arrayPushOnce(peerId, this.connectedPeers); + this.emit('connect', copayerId); +}; + +Network.prototype.getKey = function() { + if (!this.key) { + var key = new bitcore.Key(); + key.private = new Buffer(this.privkey, 'hex'); + key.regenerateSync(); + this.key = key; + } + return this.key; +}; + +//hex version of one's own nonce +Network.prototype.setHexNonce = function(networkNonce) { + if (networkNonce) { + if (networkNonce.length !== 16) + throw new Error('incorrect length of hex nonce'); + this.networkNonce = new Buffer(networkNonce, 'hex'); + } else + this.iterateNonce(); +}; + +//hex version of copayers' nonces +Network.prototype.setHexNonces = function(networkNonces) { + for (var i in networkNonces) { + if (!this.networkNonces) + this.networkNonces = {}; + if (networkNonces[i].length === 16) + this.networkNonces[i] = new Buffer(networkNonces[i], 'hex'); + } +}; + +//for oneself +Network.prototype.getHexNonce = function() { + return this.networkNonce.toString('hex'); +}; + +//for copayers +Network.prototype.getHexNonces = function() { + var networkNoncesHex = []; + for (var i in this.networkNonces) { + networkNoncesHex[i] = this.networkNonces[i].toString('hex'); + } + return networkNoncesHex; +}; + +Network.prototype.iterateNonce = function() { + if (!this.networkNonce || this.networkNonce.length !== 8) { + this.networkNonce = new Buffer(8); + this.networkNonce.fill(0); + } + //the first 4 bytes of a nonce is a unix timestamp in seconds + //the second 4 bytes is just an iterated "sub" nonce + //the whole thing is interpreted as one big endian number + var noncep1 = this.networkNonce.slice(0, 4); + noncep1.writeUInt32BE(Math.floor(Date.now() / 1000), 0); + var noncep2uint = this.networkNonce.slice(4, 8).readUInt32BE(0); + var noncep2 = this.networkNonce.slice(4, 8); + noncep2.writeUInt32BE(noncep2uint + 1, 0); + return this.networkNonce; +}; + +Network.prototype._onMessage = function(enc) { + var key = this.getKey(); + + try { + var prevnonce = this.networkNonces ? this.networkNonces[peerId] : undefined; + var opts = { + prevnonce: prevnonce + }; + var decoded = AuthMessage.decode(key, enc, opts); + + //if no error thrown in the last step, we can set the copayer's nonce + if (!this.networkNonces) + this.networkNonces = {}; + this.networkNonces[peerId] = decoded.nonce; + + var payload = decoded.payload; + } catch (e) { + this._deletePeer(peerId); + return; + } + + + if (this.allowedCopayerIds && !this.allowedCopayerIds[payload.copayerId]) { + this._deletePeer(peerId); + return; + } + + + if (!this.copayerForPeer[peerId] || (isInbound && !this.isInboundPeerAuth[peerId])) { + this._deletePeer(peerId); + return; + } + + var self = this; + switch (payload.type) { + case 'disconnect': + this._onClose(peerId); + break; + default: + this.emit('data', self.copayerForPeer[peerId], payload, isInbound); + } +}; + +Network.prototype._checkAnyPeer = function(msg) { + if (this.connectedPeers.length === 1) { + this.emit('onlyYou'); + } +}; + +Network.prototype._setupConnectionHandlers = function(toCopayerId) { + preconditions.checkState(this.socket); + var self = this; + + var isInbound = toCopayerId ? false : true; + + self.socket.on('connect', function() { + console.log('CONNECTED!'); + self.socket.on('disconnect', function() { + console.log('DISCONNECTED'); + self.cleanUp(); + }); + }); + self.socket.on('message', self._onMessage); + self.socket.on('error', self._handlePeerError); + +}; + +Network.prototype._handlePeerError = function(err) { + console.log('RECV ERROR: ', err); + if (err.message.match(/Could\snot\sconnect\sto peer/)) { + this._checkAnyPeer(); + } else { + this.criticalError = err.message; + } +}; + +Network.prototype._addCopayerMap = function(peerId, copayerId) { + if (!this.copayerForPeer[peerId]) { + if (Object.keys(this.copayerForPeer).length < this.maxPeers) { + this.copayerForPeer[peerId] = copayerId; + } else {} + } +}; + +Network.prototype._setInboundPeerAuth = function(peerId, isAuthenticated) { + this.isInboundPeerAuth[peerId] = isAuthenticated; +}; + +Network.prototype.setCopayerId = function(copayerId) { + if (this.started) { + throw new Error('network already started: can not change peerId') + } + this.copayerId = copayerId; + this.copayerIdBuf = new Buffer(copayerId, 'hex'); + this.peerId = this.peerFromCopayer(this.copayerId); + this._addCopayerMap(this.peerId, copayerId); +}; + + +// TODO cache this. +Network.prototype.peerFromCopayer = function(hex) { + var SIN = bitcore.SIN; + return new SIN(new Buffer(hex, 'hex')).toString(); +}; + +Network.prototype.start = function(opts, openCallback) { + opts = opts || {}; + + if (this.started) return openCallback(); + + if (!this.privkey) + this.privkey = opts.privkey; + + this.maxPeers = opts.maxPeers || this.maxPeers; + + if (opts.token) + this.opts.token = opts.token; + + if (!this.copayerId) + this.setCopayerId(opts.copayerId); + + var self = this; + var setupPeer = function() { + if (self.connectedPeers.length > 0) return; // Already connected! + if (self.socket) { + self.socket.destroy(); + self.socket.removeAllListeners(); + } + + if (!self.criticalError && self.tries < self.reconnectAttempts) { + self.tries++; + self.opts.token = util.sha256(self.peerId).toString('hex'); + self.socket = io.connect(self.host, { + port: self.port + }); + self.socket.emit('subscribe', pubkey); + self.socket.emit('sync', ts); + self.started = true; + self._setupConnectionHandlers(self.socket, copayerId); + + setTimeout(setupPeer, self.retryDelay); // Schedule retry + return; + } + if (self.criticalError && self.criticalError.match(/taken/)) { + self.criticalError = ' Looks like you are already connected to this wallet please close all other Copay Wallets ' + } + + self.emit('serverError', self.criticalError); + self.cleanUp(); + } + + this.tries = 0; + setupPeer(); +}; + +Network.prototype.getOnlinePeerIDs = function() { + return this.connectedPeers; +}; + +Network.prototype.getPeer = function() { + return this.peer; +}; + + +Network.prototype.send = function(copayerIds, payload, cb) { + if (!payload) return cb(); + + var self = this; + if (!copayerIds) { + copayerIds = this.connectedCopayers(); + payload.isBroadcast = 1; + } + + if (typeof copayerIds === 'string') + copayerIds = [copayerIds]; + + var l = copayerIds.length; + var i = 0; + copayerIds.forEach(function(copayerId) { + self.iterateNonce(); + var opts = { + nonce: self.networkNonce + }; + var copayerIdBuf = new Buffer(copayerId, 'hex'); + var message = AuthMessage.encode(copayerIdBuf, self.getKey(), payload, opts); + self.socket.emit('message', message); + if (++i === l && typeof cb === 'function') cb(); + }); +}; + + +Network.prototype.isOnline = function() { + return !!this.socket; +}; + + +Network.prototype.lockIncommingConnections = function(allowedCopayerIdsArray) { + this.allowedCopayerIds = {}; + for (var i in allowedCopayerIdsArray) { + this.allowedCopayerIds[allowedCopayerIdsArray[i]] = true; + } +}; + +Network.prototype.disconnect = function(cb, forced) { + var self = this; + self.closing = 1; + self.send(null, { + type: 'disconnect' + }, function() { + self.cleanUp(); + if (typeof cb === 'function') cb(); + }); +}; + +module.exports = require('soop')(Network); diff --git a/package.json b/package.json index 61b315609..74fa2d40d 100644 --- a/package.json +++ b/package.json @@ -53,10 +53,7 @@ "grunt-contrib-uglify": "^0.5.1", "grunt-contrib-watch": "0.5.3", "grunt-markdown": "0.5.0", - "browser-pack": "2.0.1", "bitcore": "0.1.35", - "node-cryptojs-aes": "0.4.0", - "blanket": "1.1.6", "grunt-mocha-test": "0.8.2", "grunt-shell": "0.6.4", "istanbul": "0.2.10", @@ -69,6 +66,8 @@ "mocha-lcov-reporter": "0.0.1", "mock-fs": "^2.3.1", "node-cryptojs-aes": "0.4.0", + "socket.io-client": "^1.0.6", + "soop": "0.1.5", "travis-cov": "0.2.5", "uglifyify": "1.2.3" }, diff --git a/test/test.network.Async.js b/test/test.network.Async.js new file mode 100644 index 000000000..2c3839e70 --- /dev/null +++ b/test/test.network.Async.js @@ -0,0 +1,507 @@ +'use strict'; + +var chai = chai || require('chai'); +var should = chai.should(); +var expect = chai.expect; +var sinon = sinon || require('sinon'); +var bitcore = bitcore || require('bitcore'); +var Async = require('../js/models/network/Async'); + +describe('Network / Async', function() { + + it('should create an instance', function() { + var n = new Async(); + should.exist(n); + }); + + describe('#Async constructor', function() { + + it('should set reconnect attempts', function() { + var n = new Async(); + n.reconnectAttempts.should.equal(3); + }); + + it('should call cleanUp', function() { + var save = Async.prototype.cleanUp; + Async.prototype.cleanUp = sinon.spy(); + var n = new Async(); + n.cleanUp.calledOnce.should.equal(true); + Async.prototype.cleanUp = save; + }); + }); + + describe('#cleanUp', function() { + + it('should not set netKey', function() { + var n = new Async(); + (n.netKey === undefined).should.equal(true); + }); + + it('should set privkey to null', function() { + var n = new Async(); + n.cleanUp(); + expect(n.privkey).to.equal(null); + }); + + it('should remove handlers', function() { + var n = new Async(); + var save = Async.prototype.removeAllListeners; + var spy = Async.prototype.removeAllListeners = sinon.spy(); + n.cleanUp(); + spy.calledOnce.should.equal(true); + Async.prototype.removeAllListeners = save; + }); + }); + + + describe('#_setupPeerHandlers', function() { + var n = new Async(); + n.peer = {}; + var spy = n.peer.on = sinon.spy(); + it('should setup handlers', function() { + n._setupPeerHandlers(); + spy.calledWith('connection').should.equal(true); + spy.calledWith('open').should.equal(true); + spy.calledWith('error').should.equal(true); + }); + }); + + describe('#_handlePeerOpen', function() { + var n = new Async(); + it('should call openCallback handler', function(done) { + n.peerId = 1; + n.copayerId = 2; + n._handlePeerOpen(function() { + n.connectedPeers.should.deep.equal([1]); + n.copayerForPeer.should.deep.equal({ + 1: 2 + }); + done(); + }); + }); + }); + + describe('#_handlePeerError', function() { + var log = console.log; + var n = new Async(); + it('should call _checkAnyPeer on could not connect error', function() { + var save = n._checkAnyPeer; + var spy = n._checkAnyPeer = sinon.spy(); + var logSpy = console.log = sinon.spy(); + n._handlePeerError({ + message: 'Could not connect to peer xxx' + }); + console.log = log; + spy.called.should.equal(true); + logSpy.called.should.equal(true); + n._checkAnyPeer = save; + }); + + it('should call not call _checkAnyPeer other error', function() { + var save = n._checkAnyPeer; + var spy = n._checkAnyPeer = sinon.spy(); + var otherMessage = 'Could connect to peer xxx'; + var logSpy = console.log = sinon.spy(); + n._handlePeerError({ + message: otherMessage, + }); + console.log = log; + spy.called.should.equal(false); + n.criticalError.should.equal(otherMessage); + logSpy.called.should.equal(true); + n._checkAnyPeer = save; + }); + + }); + + + + describe('#_encode', function() { + + it('should encode data successfully', function() { + var n = new Async(); + var data = new bitcore.Buffer('my data to encode'); + var privkeystr = new bitcore.Buffer('test privkey'); + var privkey = bitcore.util.sha256(privkeystr); + var key = new bitcore.Key(); + key.private = privkey; + key.regenerateSync(); + var encoded = n._encode(key.public, key, data); + should.exist(encoded); + encoded.sig.length.should.not.equal(0); + encoded.pubkey.length.should.not.equal(0); + encoded.encrypted.length.should.not.equal(0); + }); + + }); + + describe('#_decode', function() { + + it('should decode that which was encoded', function() { + var n = new Async(); + var data = new bitcore.Buffer('my data to encrypt'); + var privkeystr = new bitcore.Buffer('test privkey'); + var privkey = bitcore.util.sha256(privkeystr); + var key = new bitcore.Key(); + key.private = privkey; + key.regenerateSync(); + var encoded = n._encode(key.public, key, data); + var decoded = n._decode(key, encoded); + encoded.sig.should.not.equal(0); + decoded.payload.toString().should.equal('my data to encrypt'); + }); + + }); + + describe('#send', function() { + + it('should call _sendToOne for a copayer', function(done) { + var n = new Async(); + n.privkey = bitcore.util.sha256('test'); + + var data = new bitcore.Buffer('my data to send'); + + var privkeystr = new bitcore.Buffer('test privkey'); + var privkey = bitcore.util.sha256(privkeystr); + var key = new bitcore.Key(); + key.private = privkey; + key.regenerateSync(); + + var copayerId = key.public.toString('hex'); + n._sendToOne = function(a1, a2, cb) { + cb(); + }; + var opts = {}; + n.send(copayerId, data, function() { + done(); + }); + + }); + + it('should call _sendToOne with encrypted data for a copayer', function(done) { + var n = new Async(); + n.privkey = bitcore.util.sha256('test'); + + var data = new bitcore.Buffer('my data to send'); + + var privkeystr = new bitcore.Buffer('test privkey'); + var privkey = bitcore.util.sha256(privkeystr); + var key = new bitcore.Key(); + key.private = privkey; + key.regenerateSync(); + + var copayerId = key.public.toString('hex'); + n._sendToOne = function(a1, enc, cb) { + var encPayload = JSON.parse(enc.toString()); + encPayload.sig.length.should.be.greaterThan(0); + cb(); + }; + var opts = {}; + n.send(copayerId, data, function() { + done(); + }); + + }); + + it('should call _sendToOne for a list of copayers', function(done) { + var n = new Async(); + n.privkey = bitcore.util.sha256('test'); + + var data = new bitcore.Buffer('my data to send'); + + var privkeystr = new bitcore.Buffer('test privkey'); + var privkey = bitcore.util.sha256(privkeystr); + var key = new bitcore.Key(); + key.private = privkey; + key.regenerateSync(); + + var copayerIds = [key.public.toString('hex')]; + n._sendToOne = function(a1, a2, cb) { + cb(); + }; + var opts = {}; + n.send(copayerIds, data, function() { + done(); + }); + + }); + }); + + describe('#_onData', function() { + var privkey1 = bitcore.util.sha256('test privkey 1'); + var privkey2 = bitcore.util.sha256('test privkey 2'); + var privkey3 = bitcore.util.sha256('test privkey 2'); + + var key1 = new bitcore.Key(); + key1.private = privkey1; + key1.regenerateSync(); + + var key2 = new bitcore.Key(); + key2.private = privkey2; + key2.regenerateSync(); + + var key3 = new bitcore.Key(); + key3.private = privkey3; + key3.regenerateSync(); + + it('should not reject data sent from a peer with hijacked pubkey', function() { + var n = new Async(); + n.privkey = key2.private.toString('hex'); + + var message = { + type: 'hello', + copayerId: key1.public.toString('hex') + }; + var messagestr = JSON.stringify(message); + var messagebuf = new Buffer(messagestr); + + var encoded = n._encode(key2.public, key1, messagebuf); + var encodedstr = JSON.stringify(encoded); + var encodeduint = new Buffer(encodedstr); + + var isInbound = true; + var peerId = new bitcore.SIN(key1.public); + + n._deletePeer = sinon.spy(); + + n._onData(encodeduint, isInbound, peerId); + n._deletePeer.calledOnce.should.equal(false); + }); + + it('should reject data sent from a peer with hijacked pubkey', function() { + var n = new Async(); + n.privkey = key2.private.toString('hex'); + + var message = { + type: 'hello', + copayerId: key3.public.toString('hex') //MITM pubkey 3 + }; + var messagestr = JSON.stringify(message); + var messagebuf = new Buffer(messagestr); + + var encoded = n._encode(key2.public, key1, messagebuf); + var encodedstr = JSON.stringify(encoded); + var encodeduint = new Buffer(encodedstr); + + var isInbound = true; + var peerId = new bitcore.SIN(key1.public); + + n._deletePeer = sinon.spy(); + + n._onData(encodeduint, isInbound, peerId); + n._deletePeer.calledOnce.should.equal(true); + n._deletePeer.getCall(0).args[0].should.equal(peerId); + n._deletePeer.getCall(0).args[1].should.equal('incorrect pubkey for peerId'); + }); + + it('should not reject data sent from a peer with no previously set nonce but who is setting one now', function() { + var n = new Async(); + n.privkey = key2.private.toString('hex'); + //n.networkNonces = {}; + //n.networkNonces[(new bitcore.SIN(key1.public)).toString()] = new Buffer('0000000000000001', 'hex'); //previously used nonce + + var message = { + type: 'hello', + copayerId: key1.public.toString('hex') + }; + var messagestr = JSON.stringify(message); + var messagebuf = new Buffer(messagestr); + + var opts = {nonce: new Buffer('0000000000000001', 'hex')}; //message send with new nonce + var encoded = n._encode(key2.public, key1, messagebuf, opts); + var encodedstr = JSON.stringify(encoded); + var encodeduint = new Buffer(encodedstr); + + var isInbound = true; + var peerId = new bitcore.SIN(key1.public); + + n._deletePeer = sinon.spy(); + + n._onData(encodeduint, isInbound, peerId); + n._deletePeer.calledOnce.should.equal(false); + n.getHexNonces()[(new bitcore.SIN(key1.public)).toString()].toString('hex').should.equal('0000000000000001'); + }); + + it('should not reject data sent from a peer with a really big new nonce', function() { + var n = new Async(); + n.privkey = key2.private.toString('hex'); + n.networkNonces = {}; + n.networkNonces[(new bitcore.SIN(key1.public)).toString()] = new Buffer('5000000000000001', 'hex'); //previously used nonce + + var message = { + type: 'hello', + copayerId: key1.public.toString('hex') + }; + var messagestr = JSON.stringify(message); + var messagebuf = new Buffer(messagestr); + + var opts = {nonce: new Buffer('5000000000000002', 'hex')}; //message send with new nonce + var encoded = n._encode(key2.public, key1, messagebuf, opts); + var encodedstr = JSON.stringify(encoded); + var encodeduint = new Buffer(encodedstr); + + var isInbound = true; + var peerId = new bitcore.SIN(key1.public); + + n._deletePeer = sinon.spy(); + + n._onData(encodeduint, isInbound, peerId); + n._deletePeer.calledOnce.should.equal(false); + }); + + it('should not reject data sent from a peer with a really big new nonce', function() { + var n = new Async(); + n.privkey = key2.private.toString('hex'); + n.networkNonces = {}; + n.networkNonces[(new bitcore.SIN(key1.public)).toString()] = new Buffer('5000000000000001', 'hex'); //previously used nonce + + var message = { + type: 'hello', + copayerId: key1.public.toString('hex') + }; + var messagestr = JSON.stringify(message); + var messagebuf = new Buffer(messagestr); + + var opts = {nonce: new Buffer('5000000000000002', 'hex')}; //message send with new nonce + var encoded = n._encode(key2.public, key1, messagebuf, opts); + var encodedstr = JSON.stringify(encoded); + var encodeduint = new Buffer(encodedstr); + + var isInbound = true; + var peerId = new bitcore.SIN(key1.public); + + n._deletePeer = sinon.spy(); + + n._onData(encodeduint, isInbound, peerId); + n._deletePeer.calledOnce.should.equal(false); + }); + + it('should reject data sent from a peer with an outdated nonce', function() { + var n = new Async(); + n.privkey = key2.private.toString('hex'); + n.networkNonces = {}; + n.networkNonces[(new bitcore.SIN(key1.public)).toString()] = new Buffer('0000000000000002', 'hex'); //previously used nonce + + var message = { + type: 'hello', + copayerId: key1.public.toString('hex') + }; + var messagestr = JSON.stringify(message); + var messagebuf = new Buffer(messagestr); + + var opts = {nonce: new Buffer('0000000000000001', 'hex')}; //message send with old nonce + var encoded = n._encode(key2.public, key1, messagebuf, opts); + var encodedstr = JSON.stringify(encoded); + var encodeduint = new Buffer(encodedstr); + + var isInbound = true; + var peerId = new bitcore.SIN(key1.public); + + n._deletePeer = sinon.spy(); + + n._onData(encodeduint, isInbound, peerId); + n._deletePeer.calledOnce.should.equal(true); + }); + + it('should reject data sent from a peer with a really big outdated nonce', function() { + var n = new Async(); + n.privkey = key2.private.toString('hex'); + n.networkNonces = {}; + n.networkNonces[(new bitcore.SIN(key1.public)).toString()] = new Buffer('5000000000000002', 'hex'); //previously used nonce + + var message = { + type: 'hello', + copayerId: key1.public.toString('hex') + }; + var messagestr = JSON.stringify(message); + var messagebuf = new Buffer(messagestr); + + var opts = {nonce: new Buffer('5000000000000001', 'hex')}; //message send with old nonce + var encoded = n._encode(key2.public, key1, messagebuf, opts); + var encodedstr = JSON.stringify(encoded); + var encodeduint = new Buffer(encodedstr); + + var isInbound = true; + var peerId = new bitcore.SIN(key1.public); + + n._deletePeer = sinon.spy(); + + n._onData(encodeduint, isInbound, peerId); + n._deletePeer.calledOnce.should.equal(true); + }); + + }); + + describe('#setHexNonce', function() { + + it('should set a nonce from a hex value', function() { + var hex = '0000000000000000'; + var n = new Async(); + n.setHexNonce(hex); + n.getHexNonce().should.equal(hex); + n.networkNonce.toString('hex').should.equal(hex); + }); + + }); + + describe('#setHexNonces', function() { + + it('should set a nonce from a hex value', function() { + var hex = '0000000000000000'; + var n = new Async(); + n.setHexNonces({fakeid: hex}); + n.getHexNonces().fakeid.should.equal(hex); + }); + + }); + + describe('#getHexNonce', function() { + + it('should get a nonce hex value', function() { + var hex = '0000000000000000'; + var n = new Async(); + n.setHexNonce(hex); + n.getHexNonce().should.equal(hex); + }); + + }); + + describe('#getHexNonces', function() { + + it('should get a nonce from a hex value', function() { + var hex = '0000000000000000'; + var n = new Async(); + n.setHexNonces({fakeid: hex}); + n.getHexNonces().fakeid.should.equal(hex); + }); + + }); + + describe('#iterateNonce', function() { + + it('should set a nonce not already set', function() { + var n = new Async(); + n.iterateNonce(); + n.networkNonce.slice(4, 8).toString('hex').should.equal('00000001'); + n.networkNonce.slice(0, 4).toString('hex').should.not.equal('00000000'); + }); + + it('called twice should increment', function() { + var n = new Async(); + n.iterateNonce(); + n.networkNonce.slice(4, 8).toString('hex').should.equal('00000001'); + n.iterateNonce(); + n.networkNonce.slice(4, 8).toString('hex').should.equal('00000002'); + }); + + it('should set the first byte to the most significant "now" digit', function() { + var n = new Async(); + n.iterateNonce(); + var buf = new Buffer(4); + buf.writeUInt32BE(Math.floor(Date.now()/1000), 0); + n.networkNonce[0].should.equal(buf[0]); + }); + + }); + +});