├── examples ├── server.sh ├── client.sh ├── env.sh ├── reference-test-client.js └── js-test.js ├── circle.yml ├── src ├── index.js ├── message-block.js ├── constants.js ├── utils.js ├── clock.js ├── message.js ├── chicago.js ├── simple-message-stream.js ├── message-stream.js └── packet-stream.js ├── .gitignore ├── package.json ├── LICENSE ├── README.md └── .jshintrc /examples/server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | curvecpserver -v $SERVER_HOSTNAME $SERVER_KEYDIR $SERVER_IP $SERVER_PORT $SERVER_EXTENSION curvecpmessage -vs date 3 | -------------------------------------------------------------------------------- /examples/client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | curvecpclient $SERVER_HOSTNAME $SERVER_KEY $SERVER_IP $SERVER_PORT $SERVER_EXTENSION curvecpmessage -cv sh -c "exec cat<&6" 3 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 4.4.5 4 | deployment: 5 | npm: 6 | branch: master 7 | commands: 8 | - echo -e "$NPM_USER\n$NPM_PASS\n$NPM_EMAIL" | npm login 9 | - npm run 2npm 10 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | PacketStream: require('./packet-stream.js'), 5 | MessageStream: require('./message-stream.js'), 6 | SimpleMessageStream: require('./simple-message-stream.js'), 7 | Message: require('./message.js') 8 | } 9 | -------------------------------------------------------------------------------- /examples/env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export SERVER_KEYDIR='server' 4 | 5 | if [ ! -d $SERVER_KEYDIR ] 6 | then 7 | curvecpmakekey $SERVER_KEYDIR 8 | fi 9 | 10 | export SERVER_HOSTNAME="thomas-XPS" 11 | export SERVER_IP="127.0.0.1" 12 | export SERVER_PORT="9013" 13 | export SERVER_EXTENSION="00000000000000000000000000000000" 14 | export SERVER_KEY=$(curvecpprintkey $SERVER_KEYDIR) 15 | -------------------------------------------------------------------------------- /src/message-block.js: -------------------------------------------------------------------------------- 1 | var Block = function () { 2 | /* Start byte in stream */ 3 | this.startByte = null 4 | /* Last transmission time of block */ 5 | this.transmissionTime = 0 6 | /* Number of transmission attempts of this block */ 7 | this.transmissions = 0 8 | /* ID of last message sending this block */ 9 | this.id = null 10 | /* Actual block data (buffer) */ 11 | this.data = null 12 | /* Flags */ 13 | this.stop_success = false 14 | this.stop_failure = false 15 | } 16 | 17 | Block.prototype.includedIn = function (size) { 18 | return this.startByte + this.data.length <= size 19 | } 20 | 21 | module.exports = Block 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | var MAX_MESSAGE_SIZE = 1088 2 | var MINIMAL_PADDING = 16 3 | var HEADER_SIZE = 48 4 | var MESSAGE_BODY = MAX_MESSAGE_SIZE - HEADER_SIZE - MINIMAL_PADDING 5 | 6 | var MAX_OUTGOING = 128 7 | var MAX_INCOMING = 64 8 | 9 | var MAXIMUM_UNPROCESSED_SEND_BYTES = 1024 * 1024 10 | 11 | var MILLISECOND = 1000000 12 | var SECOND = MILLISECOND * 1000 13 | 14 | var MAX_SECONDS_IN_DURATION = (Number.MAX_SAFE_INTEGER / SECOND) - 1 15 | 16 | var MAX_RETRANSMISSIONS = 15 17 | 18 | module.exports = { 19 | MAX_MESSAGE_SIZE: MAX_MESSAGE_SIZE, 20 | MINIMAL_PADDING: MINIMAL_PADDING, 21 | HEADER_SIZE: HEADER_SIZE, 22 | MESSAGE_BODY: MESSAGE_BODY, 23 | MAX_OUTGOING: MAX_OUTGOING, 24 | MAX_INCOMING: MAX_INCOMING, 25 | MAXIMUM_UNPROCESSED_SEND_BYTES: MAXIMUM_UNPROCESSED_SEND_BYTES, 26 | MILLISECOND: MILLISECOND, 27 | SECOND: SECOND, 28 | MAX_SECONDS_IN_DURATION: MAX_SECONDS_IN_DURATION, 29 | MAX_RETRANSMISSIONS: MAX_RETRANSMISSIONS 30 | } 31 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | var nacl = require('tweetnacl') 2 | 3 | var safeIntegerAddition = function (original, addition) { 4 | if (Number.MAX_SAFE_INTEGER - addition < original) { 5 | return Number.MAX_SAFE_INTEGER 6 | } else { 7 | return original + addition 8 | } 9 | } 10 | 11 | var safeIntegerMultiplication = function (original, multiplier) { 12 | if (Number.MAX_SAFE_INTEGER / 4 < original) { 13 | return Number.MAX_SAFE_INTEGER 14 | } else { 15 | return original * multiplier 16 | } 17 | } 18 | 19 | var randommod = function (n) { 20 | var result = 0 21 | if (n <= 1) { 22 | return 0 23 | } 24 | var randomBytes = nacl.randomBytes(32) 25 | for (var j = 0; j < 32; ++j) { 26 | result = safeIntegerAddition(safeIntegerMultiplication(result, 256), Number(randomBytes[j])) 27 | result = result % n 28 | } 29 | return result 30 | } 31 | 32 | module.exports = { 33 | safeIntegerAddition: safeIntegerAddition, 34 | safeIntegerMultiplication: safeIntegerMultiplication, 35 | randommod: randommod 36 | } 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "curvecp", 3 | "version": "1.2.1", 4 | "description": "Pure javascript CurveCP library", 5 | "main": "src/index.js", 6 | "repository": "git://github.com/thomasdelaet/curvecp.git", 7 | "directories": { 8 | "example": "examples" 9 | }, 10 | "scripts": { 11 | "test": "node ./examples/js-test.js", 12 | "2npm": "publish" 13 | }, 14 | "keywords": [ 15 | "curvecp" 16 | ], 17 | "author": "Thomas Delaet ", 18 | "license": "MIT", 19 | "dependencies": { 20 | "async.util.setimmediate": "0.5.2", 21 | "browser-process-hrtime": "0.1.2", 22 | "extend.js": "0.0.2", 23 | "inherits": "2.0.3", 24 | "int64-buffer": "0.1.9", 25 | "is-buffer": "1.1.4", 26 | "lodash": "4.15.0", 27 | "nanotimer": "0.3.14", 28 | "readable-stream-no-buffering": "2.0.7", 29 | "tweetnacl": "0.14.3", 30 | "tweetnacl-util": "0.13.3", 31 | "winston": "2.2.0", 32 | "winston-meta-wrapper": "1.2.0" 33 | }, 34 | "devDependencies": { 35 | "net-udp": "0.0.5", 36 | "publish": "0.6.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Thomas Delaet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /examples/reference-test-client.js: -------------------------------------------------------------------------------- 1 | var net = require('net-udp') 2 | var PacketStream = require('../src/packet-stream.js') 3 | var MessageStream = require('../src/message-stream.js') 4 | var nacl = require('tweetnacl') 5 | nacl.util = require('tweetnacl-util') 6 | var winston = require('winston') 7 | var winstonWrapper = require('winston-meta-wrapper') 8 | 9 | var logger = new winston.Logger({ 10 | transports: [ 11 | new winston.transports.Console({ 12 | level: 'debug', 13 | timestamp: true, 14 | logstash: false 15 | }) 16 | ] 17 | }) 18 | logger = winstonWrapper(logger) 19 | 20 | var keypair = nacl.box.keyPair() 21 | var connection = new net.Socket() 22 | 23 | var packetStream = new PacketStream({ 24 | stream: connection, 25 | logger: logger, 26 | is_server: false, 27 | serverName: process.env.SERVER_HOSTNAME, 28 | clientPublicKey: keypair.publicKey, 29 | clientPrivateKey: keypair.secretKey 30 | }) 31 | 32 | var messageStream = new MessageStream({ 33 | stream: packetStream, 34 | logger: logger 35 | }) 36 | 37 | messageStream.on('connect', function () { 38 | console.log('messagestream connected') 39 | }) 40 | messageStream.on('data', function (data) { 41 | console.log('data') 42 | console.log(data.toString()) 43 | }) 44 | 45 | messageStream.on('error', function (error) { 46 | console.log('error') 47 | console.log(error) 48 | }) 49 | 50 | messageStream.on('close', function () { 51 | console.log('close') 52 | }) 53 | 54 | var boxId = nacl.util.encodeBase64(new Uint8Array(new Buffer(process.env.SERVER_KEY, 'hex'))) 55 | messageStream.connect(boxId, { 56 | addresses: [process.env.SERVER_IP], 57 | port: parseInt(process.env.SERVER_PORT, 10) 58 | }) 59 | -------------------------------------------------------------------------------- /src/clock.js: -------------------------------------------------------------------------------- 1 | var constants = require('./constants.js') 2 | 3 | var Clock = function (input) { 4 | this.seconds = input[0] 5 | /* always less than 10^9 */ 6 | this.nanoseconds = input[1] 7 | } 8 | 9 | Clock.prototype.clone = function () { 10 | return new Clock([this.seconds, this.nanoseconds]) 11 | } 12 | 13 | Clock.prototype.add = function (nanoseconds) { 14 | var secondsToAdd = Number(Number(nanoseconds / constants.SECOND).toString().split('.')[0]) 15 | var nanosecondsToAdd = nanoseconds % constants.SECOND 16 | if (nanosecondsToAdd > constants.SECOND) { 17 | secondsToAdd += 1 18 | nanosecondsToAdd -= constants.SECOND 19 | } 20 | this.seconds += secondsToAdd 21 | this.nanoseconds += nanosecondsToAdd 22 | } 23 | 24 | Clock.prototype.subtract = function (clock) { 25 | var seconds = this.seconds - clock.seconds 26 | if (seconds > constants.MAX_SECONDS_IN_DURATION) { 27 | seconds = constants.MAX_SECONDS_IN_DURATION 28 | } 29 | var nanoseconds = this.nanoseconds - clock.nanoseconds 30 | var result = (seconds * constants.SECOND) + nanoseconds 31 | if (result < 0) { 32 | throw new Error('Clock subtraction should not be negative') 33 | } 34 | return result 35 | } 36 | 37 | /* 38 | * @return Number 1 if we are larger than clock, -1 if smaller, 0 if equal 39 | */ 40 | Clock.prototype.compare = function (clock) { 41 | if (this.seconds > clock.seconds) { 42 | return 1 43 | } else if (this.seconds === clock.seconds) { 44 | if (this.nanoseconds > clock.nanoseconds) { 45 | return 1 46 | } else if (this.nanoseconds === clock.nanoseconds) { 47 | return 0 48 | } else { 49 | return -1 50 | } 51 | } else { 52 | return -1 53 | } 54 | } 55 | 56 | module.exports = Clock 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CurveCP 2 | 3 | Pure Javascript CurveCP protocol 4 | 5 | The aim of this project is to implement the CurveCP protocol in pure javascript following the specification on [http://www.curvecp.org](http://www.curvecp.org) and interopable with the reference implementation. 6 | 7 | This project currently implements the messaging format, encryption layer and congestion control algorithm but still needs to be tested, fine-tuned and improved upon. I don't consider this library to be usable yet. 8 | 9 | ## Quick start 10 | 11 | See examples directory for an example client and server implementation. 12 | 13 | You should create a PacketStream to start with (supplying public/private keys and other required info) and then wrap this stream in a MessageStream() object. 14 | 15 | Clients should then execute the connect() method on MessageStream object. 16 | 17 | ## Design 18 | 19 | This implementation makes a clean split between: 20 | 21 | * The congestion control algorithm (chicago.js) 22 | * The crypto layer (packet-stream.js) 23 | * The messaging layer (message-stream.js) 24 | * The actual networking transport. To follow the reference implementation, you should use UDP as a networking transport (like illustrated in examples section) but in principle, the packet-stream can be connected to any networking transport. I'm also using it with different types of transports in my own projects. 25 | 26 | ## Inspiration 27 | 28 | * The reference implementation/specification at [http://www.curvecp.org](http://www.curvecp.org) 29 | * The description of the Chicago algorithm by Matthew Dempsky at [http://shinobi.dempsky.org/~matthew/curvecp/chicago.html](http://shinobi.dempsky.org/~matthew/curvecp/chicago.html) 30 | * The libcurvecpr project at [https://github.com/impl/libcurvecpr](https://github.com/impl/libcurvecpr) for inspiration on a clean split between the messaging layer and congestion control aglorithm. 31 | -------------------------------------------------------------------------------- /examples/js-test.js: -------------------------------------------------------------------------------- 1 | var net = require('net-udp') 2 | var nacl = require('tweetnacl') 3 | var PacketStream = require('../src/packet-stream.js') 4 | var MessageStream = require('../src/message-stream.js') 5 | nacl.util = require('tweetnacl-util') 6 | 7 | var NB_BLOCKS = 100 8 | var BLOCK_LENGTH = 1024 9 | 10 | var server = net.createServer() 11 | var client = new net.Socket() 12 | 13 | var serverKeyPair = nacl.box.keyPair() 14 | var clientKeyPair = nacl.box.keyPair() 15 | 16 | var source = Buffer(NB_BLOCKS * BLOCK_LENGTH) 17 | var sourceServer = Buffer(NB_BLOCKS * BLOCK_LENGTH) 18 | 19 | for (var i = 0; i < NB_BLOCKS; i++) { 20 | var buffer = new Buffer(nacl.randomBytes(BLOCK_LENGTH)) 21 | buffer.copy(source, i * BLOCK_LENGTH) 22 | } 23 | 24 | for (i = 0; i < NB_BLOCKS; i++) { 25 | buffer = new Buffer(nacl.randomBytes(BLOCK_LENGTH)) 26 | buffer.copy(sourceServer, i * BLOCK_LENGTH) 27 | } 28 | 29 | var currentBlock = 0 30 | var currentBlockServer = 0 31 | var messageStream 32 | var messageStreamServer 33 | 34 | var destination = Buffer(0) 35 | var destinationClient = Buffer(0) 36 | 37 | server.on('connection', function (socket) { 38 | console.log('new connection to server') 39 | var packetStream = new PacketStream({ 40 | stream: socket, 41 | isServer: true, 42 | serverPublicKey: serverKeyPair.publicKey, 43 | serverPrivateKey: serverKeyPair.secretKey 44 | }) 45 | messageStreamServer = new MessageStream({ 46 | stream: packetStream 47 | }) 48 | messageStreamServer.on('data', function (data) { 49 | console.log('DATA RECEIVED ON SERVER') 50 | destination = Buffer.concat([destination, data]) 51 | }) 52 | messageStreamServer.on('end', function () { 53 | console.log('END RECEIVED ON SERVER') 54 | // console.log(nacl.util.encodeBase64(source)) 55 | // console.log(nacl.util.encodeBase64(destination)) 56 | if (!Buffer.compare(source, destination)) { 57 | console.log('IDENTICAL BUFFER MATCH FOUND') 58 | } else { 59 | console.log('BUFFERS DO NOT MATCH') 60 | } 61 | }) 62 | messageStreamServer.on('finish', function () { 63 | console.log('FINISH RECEIVED ON SERVER') 64 | process.exit(0) 65 | }) 66 | writeServer() 67 | }) 68 | 69 | var write = function () { 70 | var canContinue = currentBlock < NB_BLOCKS 71 | while (canContinue) { 72 | // console.log(currentBlock) 73 | var buffer = new Buffer(BLOCK_LENGTH) 74 | source.copy(buffer, 0, currentBlock * BLOCK_LENGTH, (currentBlock + 1) * BLOCK_LENGTH) 75 | var result = messageStream.write(buffer, 'buffer', function (err) { 76 | if (err) { 77 | console.log('ERROR SENDING BLOCK: ' + err) 78 | } else { 79 | // console.log('SUCCESS SENDING BLOCK') 80 | } 81 | }) 82 | currentBlock += 1 83 | canContinue = result && currentBlock < NB_BLOCKS 84 | if (currentBlock === NB_BLOCKS) { 85 | messageStream.end() 86 | } 87 | } 88 | } 89 | 90 | var writeServer = function () { 91 | var canContinue = currentBlockServer < NB_BLOCKS 92 | while (canContinue) { 93 | // console.log(currentBlock) 94 | var buffer = new Buffer(BLOCK_LENGTH) 95 | sourceServer.copy(buffer, 0, currentBlockServer * BLOCK_LENGTH, (currentBlockServer + 1) * BLOCK_LENGTH) 96 | var result = messageStreamServer.write(buffer, 'buffer', function (err) { 97 | if (err) { 98 | console.log('ERROR SENDING BLOCK: ' + err) 99 | } else { 100 | // console.log('SUCCESS SENDING BLOCK') 101 | } 102 | }) 103 | currentBlockServer += 1 104 | canContinue = result && currentBlockServer < NB_BLOCKS 105 | if (currentBlockServer === NB_BLOCKS) { 106 | messageStreamServer.end() 107 | } 108 | } 109 | } 110 | 111 | server.on('listening', function () { 112 | console.log('server listening') 113 | var packetStream = new PacketStream({ 114 | stream: client, 115 | isServer: false, 116 | serverPublicKey: serverKeyPair.publicKey, 117 | clientPublicKey: clientKeyPair.publicKey, 118 | clientPrivateKey: clientKeyPair.secretKey 119 | }) 120 | messageStream = new MessageStream({ 121 | stream: packetStream 122 | }) 123 | client.on('data', function (msg) { 124 | console.log('message received on client') 125 | console.log('length ' + msg.length) 126 | }) 127 | messageStream.on('drain', function () { 128 | console.log('drain event') 129 | write() 130 | }) 131 | messageStream.on('connect', function () { 132 | console.log('connect event') 133 | // messageStream.write(new Buffer('test')) 134 | write() 135 | }) 136 | messageStream.on('finish', function () { 137 | console.log('FINISH RECEIVED ON CLIENT') 138 | }) 139 | messageStream.on('data', function (data) { 140 | console.log('DATA RECEIVED ON CLIENT') 141 | destinationClient = Buffer.concat([destinationClient, data]) 142 | }) 143 | messageStream.on('end', function () { 144 | console.log('END RECEIVED ON CLIENT') 145 | console.log('sourceServer length' + sourceServer.length) 146 | console.log('destinationClient length' + destinationClient.length) 147 | // console.log(nacl.util.encodeBase64(sourceServer)) 148 | // console.log(nacl.util.encodeBase64(destinationClient)) 149 | if (!Buffer.compare(sourceServer, destinationClient)) { 150 | console.log('IDENTICAL BUFFER MATCH FOUND ON CLIENT') 151 | } else { 152 | console.log('BUFFERS DO NOT MATCH ON CLIENT') 153 | } 154 | }) 155 | messageStream.connect( 156 | nacl.util.encodeBase64(serverKeyPair.publicKey), { 157 | addresses: ['127.0.0.1'], 158 | port: server.address().port 159 | } 160 | ) 161 | setTimeout(function () { 162 | console.log('remotePort ' + client.remotePort) 163 | }, 500) 164 | }) 165 | 166 | server.listen() 167 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // JSHint Configuration File 3 | "maxerr" : 10, // {int} Maximum error before stopping 4 | 5 | // Enforcing 6 | "bitwise" : false, // true: Prohibit bitwise operators (&, |, ^, etc.) 7 | "camelcase" : false, // true: Identifiers must be in camelCase 8 | "curly" : false, // true: Require {} for every new block or scope 9 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 10 | "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() 11 | "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc. 12 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 13 | "indent" : 4, // {int} Number of spaces to use for indentation 14 | "latedef" : false, // true: Require variables/functions to be defined before being used 15 | "newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()` 16 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 17 | "noempty" : true, // true: Prohibit use of empty blocks 18 | "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters. 19 | "nonew" : true, // true: Prohibit use of constructors for side-effects (without assignment) 20 | "plusplus" : false, // true: Prohibit use of `++` & `--` 21 | "quotmark" : false, // Quotation mark consistency: 22 | // false : do nothing (default) 23 | // true : ensure whatever is used is consistent 24 | // "single" : require single quotes 25 | // "double" : require double quotes 26 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 27 | "unused" : true, // true: Require all defined variables be used 28 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode 29 | "maxparams" : 10, // {int} Max number of formal params allowed per function 30 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 31 | "maxstatements" : false, // {int} Max number statements per function 32 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 33 | "maxlen" : 100, // {int} Max number of characters per line 34 | 35 | // Relaxing 36 | "asi" : true, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 37 | "boss" : false, // true: Tolerate assignments where comparisons would be expected 38 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 39 | "eqnull" : false, // true: Tolerate use of `== null` 40 | //"es5" : true, // true: Allow ES5 syntax (ex: getters and setters) (on by default) 41 | "esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`) 42 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 43 | // (ex: `for each`, multiple try/catch, function expression…) 44 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 45 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs 46 | "funcscope" : false, // true: Tolerate defining variables inside control statements 47 | "globalstrict" : true, // true: Allow global "use strict" (also enables 'strict') 48 | "iterator" : false, // true: Tolerate using the `__iterator__` property 49 | "lastsemic" : true, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 50 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 51 | "laxcomma" : false, // true: Tolerate comma-first style coding 52 | "loopfunc" : true, // true: Tolerate functions being defined in loops 53 | "multistr" : false, // true: Tolerate multi-line strings 54 | "noyield" : false, // true: Tolerate generator functions with no yield statement in them. 55 | "notypeof" : false, // true: Tolerate invalid typeof operator values 56 | "proto" : false, // true: Tolerate using the `__proto__` property 57 | "scripturl" : false, // true: Tolerate script-targeted URLs 58 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 59 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 60 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 61 | "validthis" : false, // true: Tolerate using this in a non-constructor function 62 | 63 | // Environments 64 | "browser" : true, // Web Browser (window, document, etc) 65 | "browserify" : false, // Browserify (node.js code in the browser) 66 | "couch" : false, // CouchDB 67 | "devel" : true, // Development/debugging (alert, confirm, etc) 68 | "dojo" : false, // Dojo Toolkit 69 | "jasmine" : false, // Jasmine 70 | "jquery" : false, // jQuery 71 | "mocha" : true, // Mocha 72 | "mootools" : false, // MooTools 73 | "node" : true, // Node.js 74 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 75 | "prototypejs" : false, // Prototype and Scriptaculous 76 | "qunit" : false, // QUnit 77 | "rhino" : false, // Rhino 78 | "shelljs" : false, // ShellJS 79 | "worker" : false, // Web Workers 80 | "wsh" : false, // Windows Scripting Host 81 | "yui" : false, // Yahoo User Interface 82 | 83 | // Custom Globals 84 | "globals" : {} // additional predefined global variables 85 | } 86 | -------------------------------------------------------------------------------- /src/message.js: -------------------------------------------------------------------------------- 1 | var Uint64BE = require('int64-buffer').Uint64BE 2 | var assert = require('assert') 3 | 4 | var MINIMAL_PADDING = 16 5 | var HEADER_SIZE = 48 6 | 7 | var MIN_MESSAGE_SIZE = MINIMAL_PADDING + HEADER_SIZE 8 | var MAX_MESSAGE_SIZE = 1088 9 | var MAX_BODY_SIZE = MAX_MESSAGE_SIZE - MIN_MESSAGE_SIZE 10 | 11 | var STOP_SUCCESS = 2048 12 | var STOP_FAILURE = 4096 13 | var STOP = STOP_SUCCESS + STOP_FAILURE 14 | 15 | var Message = function () { 16 | this.id = 0 17 | this.acknowledging_id = 0 18 | this.acknowledging_range_1_size = 0 19 | this.acknowledging_range_12_gap = 0 20 | this.acknowledging_range_2_size = 0 21 | this.acknowledging_range_23_gap = 0 22 | this.acknowledging_range_3_size = 0 23 | this.acknowledging_range_34_gap = 0 24 | this.acknowledging_range_4_size = 0 25 | this.acknowledging_range_45_gap = 0 26 | this.acknowledging_range_5_size = 0 27 | this.acknowledging_range_56_gap = 0 28 | this.acknowledging_range_6_size = 0 29 | this.success = false 30 | this.failure = false 31 | } 32 | 33 | Message.prototype.fromBuffer = function (buf) { 34 | if (buf.length < MIN_MESSAGE_SIZE || buf.length > MAX_MESSAGE_SIZE) { 35 | throw new Error('Invalid message size') 36 | } 37 | this.id = buf.readUInt32LE(0) 38 | this.acknowledging_id = buf.readUInt32LE(4) 39 | this.acknowledging_range_1_size = new Buffer(8) 40 | buf.copy(this.acknowledging_range_1_size, 0, 8) 41 | this.acknowledging_range_1_size.reverse() 42 | this.acknowledging_range_1_size = Number(new Uint64BE(this.acknowledging_range_1_size)) 43 | if (this.acknowledging_range_1_size > Number.MAX_SAFE_INTEGER) { 44 | throw new Error('Acknowledging range exceeds maximum safe integer') 45 | } 46 | this.acknowledging_range_12_gap = buf.readUInt32LE(16) 47 | this.acknowledging_range_2_size = buf.readUInt16LE(20) 48 | this.acknowledging_range_23_gap = buf.readUInt16LE(22) 49 | this.acknowledging_range_3_size = buf.readUInt16LE(24) 50 | this.acknowledging_range_34_gap = buf.readUInt16LE(26) 51 | this.acknowledging_range_4_size = buf.readUInt16LE(28) 52 | this.acknowledging_range_45_gap = buf.readUInt16LE(30) 53 | this.acknowledging_range_5_size = buf.readUInt16LE(32) 54 | this.acknowledging_range_56_gap = buf.readUInt16LE(34) 55 | this.acknowledging_range_6_size = buf.readUInt16LE(36) 56 | this.flags = buf.readUInt16LE(38) 57 | if (!this._validFlags(this.flags)) { 58 | throw new Error('Invalid flags') 59 | } 60 | this.offset = new Buffer(8) 61 | buf.copy(this.offset, 0, 40) 62 | this.offset.reverse() 63 | this.offset = Number(new Uint64BE(this.offset)) 64 | if (this.offset > Number.MAX_SAFE_INTEGER) { 65 | throw new Error('Offset exceeds maximum safe integer') 66 | } 67 | this.data_length = this.flags - (this.flags & STOP) 68 | this.success = Boolean((this.flags - this.data_length) & STOP_SUCCESS) 69 | this.failure = Boolean((this.flags - this.data_length) & STOP_FAILURE) 70 | if (buf.length < MIN_MESSAGE_SIZE + this._data_length) { 71 | throw new Error('Advertised data not included in message') 72 | } 73 | this.data = buf.slice(buf.length - this.data_length) 74 | } 75 | 76 | Message.prototype._validFlags = function (flags) { 77 | return (flags >= 0 && flags <= MAX_BODY_SIZE) || 78 | (flags >= STOP_SUCCESS && flags <= STOP_SUCCESS + MAX_BODY_SIZE) || 79 | (flags >= STOP_FAILURE && flags <= STOP_FAILURE + MAX_BODY_SIZE) 80 | } 81 | 82 | Message.prototype.isAcknowledged = function (startByte, length) { 83 | return this._inRange1(startByte, length) || 84 | this._inRange2(startByte, length) || 85 | this._inRange3(startByte, length) || 86 | this._inRange4(startByte, length) || 87 | this._inRange5(startByte, length) || 88 | this._inRange6(startByte, length) 89 | } 90 | 91 | Message.prototype._inRange1 = function (startByte, length) { 92 | return startByte + length <= this.acknowledging_range_1_size 93 | } 94 | 95 | Message.prototype._inRange2 = function (startByte, length) { 96 | return startByte >= this._range2Start() && length <= this.acknowledging_range_2_size 97 | } 98 | 99 | Message.prototype._inRange3 = function (startByte, length) { 100 | return startByte >= this._range3Start() && length <= this.acknowledging_range_3_size 101 | } 102 | 103 | Message.prototype._inRange4 = function (startByte, length) { 104 | return startByte >= this._range4Start() && length <= this.acknowledging_range_4_size 105 | } 106 | 107 | Message.prototype._inRange5 = function (startByte, length) { 108 | return startByte >= this._range5Start() && length <= this.acknowledging_range_5_size 109 | } 110 | 111 | Message.prototype._inRange6 = function (startByte, length) { 112 | return startByte >= this._range6Start() && length <= this.acknowledging_range_6_size 113 | } 114 | 115 | Message.prototype._range2Start = function () { 116 | return this.acknowledging_range_1_size + this.acknowledging_range_12_gap 117 | } 118 | 119 | Message.prototype._range3Start = function () { 120 | return this._range2Start() + this.acknowledging_range_2_size + this.acknowledging_range_23_gap 121 | } 122 | 123 | Message.prototype._range4Start = function () { 124 | return this._range3Start() + this.acknowledging_range_3_size + this.acknowledging_range_34_gap 125 | } 126 | 127 | Message.prototype._range5Start = function () { 128 | return this._range4Start() + this.acknowledging_range_4_size + this.acknowledging_range_45_gap 129 | } 130 | 131 | Message.prototype._range6Start = function () { 132 | return this._range5Start() + this.acknowledging_range_5_size + this.acknowledging_range_56_gap 133 | } 134 | 135 | Message.prototype.toBuffer = function () { 136 | var messageSize = HEADER_SIZE + MINIMAL_PADDING 137 | if (this.data !== undefined && this.data.length > 0) { 138 | if (this.data.length % 16) { 139 | messageSize += this.data.length + 16 - (this.data.length % 16) 140 | } else { 141 | messageSize += this.data.length 142 | } 143 | } 144 | assert(messageSize <= MAX_MESSAGE_SIZE) 145 | var message = new Buffer(messageSize) 146 | message.fill(0) 147 | message.writeUInt32LE(this.id) 148 | message.writeUInt32LE(this.acknowledging_id, 4) 149 | new Uint64BE(this.acknowledging_range_1_size).toBuffer().reverse().copy(message, 8) 150 | message.writeUInt32LE(this.acknowledging_range_12_gap, 16) 151 | message.writeUInt16LE(this.acknowledging_range_2_size, 20) 152 | message.writeUInt16LE(this.acknowledging_range_23_gap, 22) 153 | message.writeUInt16LE(this.acknowledging_range_3_size, 24) 154 | message.writeUInt16LE(this.acknowledging_range_34_gap, 26) 155 | message.writeUInt16LE(this.acknowledging_range_4_size, 28) 156 | message.writeUInt16LE(this.acknowledging_range_45_gap, 30) 157 | message.writeUInt16LE(this.acknowledging_range_5_size, 32) 158 | message.writeUInt16LE(this.acknowledging_range_56_gap, 34) 159 | message.writeUInt16LE(this.acknowledging_range_6_size, 36) 160 | if (this.data !== undefined) { 161 | this.flags = this.data.length 162 | } else { 163 | this.flags = 0 164 | } 165 | if (this.success) { 166 | this.flags += STOP_SUCCESS 167 | } else if (this.failure) { 168 | this.flags += STOP_FAILURE 169 | } 170 | message.writeUInt16LE(this.flags, 38) 171 | if (this.data && this.data.length > 0) { 172 | var offset = new Uint64BE(this.offset).toBuffer().reverse() 173 | offset.copy(message, 40, 0) 174 | this.data.copy(message, messageSize - this.data.length) 175 | } 176 | return message 177 | } 178 | 179 | module.exports = Message 180 | -------------------------------------------------------------------------------- /src/chicago.js: -------------------------------------------------------------------------------- 1 | var hrtime = require('browser-process-hrtime') 2 | process.hrtime = hrtime 3 | global.setImmediate = require('async.util.setimmediate') 4 | var winston = require('winston') 5 | var winstonWrapper = require('winston-meta-wrapper') 6 | var NanoTimer = require('nanotimer') 7 | var Clock = require('./clock.js') 8 | var constants = require('./constants.js') 9 | var utils = require('./utils.js') 10 | 11 | var Chicago = function (options) { 12 | if (!options) { 13 | options = {} 14 | } 15 | if (!options.logger) { 16 | options.logger = winston 17 | } 18 | this._log = winstonWrapper(options.logger) 19 | this._log.addMeta({ 20 | module: 'curvecp-chicago' 21 | }) 22 | /* Clock instance */ 23 | this.clock = new Clock([0, 0]) 24 | this._refresh_clock() 25 | /* Smoothed Round Trip Time (SRTT) in nanoseconds */ 26 | this.rtt_average = 0 27 | /* RTTVAR in nanoseconds */ 28 | this.rtt_deviation = 0 29 | this.rtt_highwater = 0 30 | this.rtt_lowwater = 0 31 | /* RTO in nanoseconds */ 32 | this.rtt_timeout = constants.SECOND 33 | this.seen_recent_high = false 34 | this.seen_recent_low = false 35 | this.seen_older_high = false 36 | this.seen_older_low = false 37 | this.rtt_phase = 0 38 | /* Sending rate (nanosecond interval between blocks) */ 39 | this.nsecperblock = constants.SECOND 40 | /* Clock instance */ 41 | this.lastblocktime 42 | /* Clock instance */ 43 | this.lastspeedadjustment = this.clock 44 | /* Clock instance */ 45 | this.lastedge = new Clock([0, 0]) 46 | /* Clock instance */ 47 | this.lastdoubling = new Clock([0, 0]) 48 | /* Clock instance */ 49 | this.lastpanic = new Clock([0, 0]) 50 | this.timer = new NanoTimer() 51 | this._set_timeout() 52 | this.timer_disabled = false 53 | } 54 | 55 | /* TIMER */ 56 | 57 | Chicago.prototype.set_timeout = function (func) { 58 | this.timeout_callback = func 59 | } 60 | 61 | Chicago.prototype.disable_timer = function () { 62 | this.timer_disabled = true 63 | } 64 | 65 | Chicago.prototype.enable_timer = function () { 66 | if (this.timer_disabled) { 67 | this.timer_disabled = false 68 | this._set_timeout() 69 | } 70 | } 71 | 72 | Chicago.prototype._timeout_callback = function () { 73 | if (this.timeout_callback) { 74 | this.timeout_callback() 75 | } 76 | if (!this.timer_disabled) { 77 | this._set_timeout() 78 | } 79 | } 80 | 81 | Chicago.prototype._set_timeout = function () { 82 | this._log.debug('_set_timeout ' + this.nsecperblock) 83 | this.timer.setTimeout(this._timeout_callback.bind(this), [], this.nsecperblock.toString() + 'n') 84 | } 85 | 86 | /* CLOCK */ 87 | 88 | Chicago.prototype._refresh_clock = function () { 89 | this.clock = new Clock(hrtime()) 90 | } 91 | 92 | Chicago.prototype.get_clock = function () { 93 | this._refresh_clock() 94 | return this.clock.clone() 95 | } 96 | 97 | /* SEND BLOCK */ 98 | 99 | Chicago.prototype.send_block = function () { 100 | this._refresh_clock() 101 | this.lastblocktime = this.clock.clone() 102 | } 103 | 104 | /* RETRANSMISSION */ 105 | 106 | Chicago.prototype.block_is_timed_out = function (transmissionTime) { 107 | this._refresh_clock() 108 | var clock = transmissionTime.clone() 109 | clock.add(this.rtt_timeout) 110 | var compareResult = clock.compare(this.clock) 111 | if (compareResult === -1) { 112 | return true 113 | } else { 114 | return false 115 | } 116 | } 117 | 118 | Chicago.prototype.retransmission = function () { 119 | this._refresh_clock() 120 | var clock = this.lastpanic.clone() 121 | clock.add(4 * this.rtt_timeout) 122 | var compareResult = this.clock.compare(clock) 123 | if (compareResult === 1) { 124 | this._halve_transmission_rate() 125 | } 126 | } 127 | 128 | Chicago.prototype._halve_transmission_rate = function () { 129 | this.nsecperblock = utils.safeIntegerMultiplication(this.nsecperblock, 2) 130 | this.lastpanic = this.clock.clone() 131 | this.lastedge = this.clock.clone() 132 | } 133 | 134 | /* MESSAGE ACKNOWLEDGEMENT */ 135 | 136 | Chicago.prototype.acknowledgement = function (originalBlocktime) { 137 | this._refresh_clock() 138 | var rtt = this._initialize_rtt(originalBlocktime) 139 | this._jacobson_retransmission_timeout(rtt) 140 | this._compensate_delayed_acks() 141 | this._track_watermarks(rtt) 142 | this._check_for_watermarks() 143 | if (this._adjustment_cycle_has_completed()) { 144 | this._reinitialize_lastspeedadjustment() 145 | this._apply_additive_increase() 146 | this._phase_events() 147 | this._update_seen_watermarks() 148 | } 149 | this._apply_rate_doubling() 150 | } 151 | 152 | Chicago.prototype._initialize_rtt = function (originalBlocktime) { 153 | var rtt = this.clock.subtract(originalBlocktime) 154 | if (!this.rtt_average) { 155 | this.nsecperblock = rtt 156 | this.rtt_average = rtt 157 | this.rtt_deviation = rtt / 2 158 | this.rtt_highwater = rtt 159 | this.rtt_lowwater = rtt 160 | } 161 | return rtt 162 | } 163 | 164 | Chicago.prototype._jacobson_retransmission_timeout = function (rtt) { 165 | /* Jacobon's retransmission timeout calculation */ 166 | var rttDelta = rtt - this.rtt_average 167 | this.rtt_average = utils.safeIntegerAddition(this.rtt_average, rttDelta / 8) 168 | if (rttDelta < 0) { 169 | rttDelta = -rttDelta 170 | } 171 | rttDelta -= this.rtt_deviation 172 | this.rtt_deviation = utils.safeIntegerAddition(this.rtt_deviation, rttDelta / 4) 173 | this.rtt_timeout = utils.safeIntegerAddition(this.rtt_average, utils.safeIntegerMultiplication(this.rtt_deviation, 4)) 174 | } 175 | 176 | Chicago.prototype._compensate_delayed_acks = function () { 177 | /* adjust for delayed acks with anti-spiking */ 178 | this.rtt_timeout = utils.safeIntegerAddition(this.rtt_timeout, 8 * this.nsecperblock) 179 | } 180 | 181 | Chicago.prototype._track_watermarks = function (rtt) { 182 | /* Adjust high watermark of congestion cycle */ 183 | var rttDelta = rtt - this.rtt_highwater 184 | this.rtt_highwater = utils.safeIntegerAddition(this.rtt_highwater, rttDelta / 1024) 185 | /* Adjust low watermark of congestion cycle */ 186 | rttDelta = rtt - this.rtt_lowwater 187 | if (rttDelta > 0) { 188 | this.rtt_lowwater = utils.safeIntegerAddition(this.rtt_lowwater, rttDelta / 8192) 189 | } else { 190 | this.rtt_lowwater = utils.safeIntegerAddition(this.rtt_lowwater, rttDelta / 256) 191 | } 192 | } 193 | 194 | Chicago.prototype._check_for_watermarks = function () { 195 | if (this.rtt_average > utils.safeIntegerAddition(this.rtt_highwater, 5 * constants.MILLISECOND)) { 196 | this.rtt_seenrecenthigh = true 197 | } else if (this.rtt_average < this.rtt_lowwater) { 198 | this.rtt_seenrecentlow = true 199 | } 200 | } 201 | 202 | // Start new adjustment cycle (at least 16 tranmsission periods have elapsed) 203 | Chicago.prototype._adjustment_cycle_has_completed = function () { 204 | var cycle = utils.safeIntegerMultiplication(this.nsecperblock, 16) 205 | var endOfCycle = new Clock([this.lastspeedadjustment.seconds, this.lastspeedadjustment.nanoseconds]) 206 | endOfCycle.add(cycle) 207 | var result = this.clock.compare(endOfCycle) 208 | return result >= 0 209 | } 210 | 211 | Chicago.prototype._reinitialize_lastspeedadjustment = function () { 212 | this._log.debug('_reinitialize_lastspeedadjustment ' + this.nsecperblock) 213 | if (this.clock.subtract(this.lastspeedadjustment) > 10 * constants.SECOND) { 214 | this.nsecperblock = constants.SECOND /* slow restart */ 215 | this.nsecperblock = utils.safeIntegerAddition(this.nsecperblock, utils.randommod(this.nsecperblock / 8)) 216 | } 217 | this.lastspeedadjustment = this.clock.clone() 218 | } 219 | 220 | Chicago.prototype._apply_additive_increase = function () { 221 | this._log.debug('_apply_additive_increase ' + this.nsecperblock) 222 | if (this.nsecperblock >= 131072) { 223 | /* additive increase: adjust 1/N by a constant c */ 224 | /* rtt-fair additive increase: adjust 1/N by a constant c every nanosecond */ 225 | /* approximation: adjust 1/N by cN every N nanoseconds */ 226 | /* i.e., N <- 1/(1/N + cN) = N/(1 + cN^2) every N nanoseconds */ 227 | if (this.nsecperblock < 16777216) { 228 | /* N/(1+cN^2) approx N - cN^3 */ 229 | var u = this.nsecperblock / 131072 230 | var u3 = utils.safeIntegerMultiplication(utils.safeIntegerMultiplication(u, u), u) 231 | this.nsecperblock = this.nsecperblock - u3 232 | } else { 233 | var d = this.nsecperblock 234 | this.nsecperblock = d / (1 + ((d * d) / 2251799813685248.0)) 235 | } 236 | } 237 | } 238 | 239 | Chicago.prototype._phase_events = function () { 240 | this._log.debug('_phase_events ' + this.nsecperblock) 241 | if (this.rtt_phase === 0) { 242 | if (this.rtt_seenolderhigh) { 243 | this.rtt_phase = 1 244 | this.lastedge = this.clock 245 | this.nsecperblock = utils.safeIntegerAddition(this.nsecperblock, utils.randommod(this.nsecperblock / 4)) 246 | } 247 | } else { 248 | if (this.rtt_seenolderlow) { 249 | this.rtt_phase = 0 250 | } 251 | } 252 | } 253 | 254 | Chicago.prototype._update_seen_watermarks = function () { 255 | this.rtt_seenolderhigh = this.rtt_seenrecenthigh 256 | this.rtt_seenolderlow = this.rtt_seenrecentlow 257 | this.rtt_seenrecenthigh = false 258 | this.rtt_seenrecentlow = false 259 | } 260 | 261 | Chicago.prototype._apply_rate_doubling = function () { 262 | this._log.debug('_apply_rate_doubling ' + this.nsecperblock) 263 | var compareClock 264 | var result 265 | while (true) { 266 | var n4 = utils.safeIntegerMultiplication(this.nsecperblock, 4) 267 | if (this.clock.subtract(this.lastedge) < 60 * constants.SECOND) { 268 | var rto64 = utils.safeIntegerMultiplication(this.rtt_timeout, 64) 269 | compareClock = this.lastdoubling.clone() 270 | compareClock.add(n4) 271 | compareClock.add(rto64) 272 | compareClock.add(5 * constants.SECOND) 273 | result = this.clock.compare(compareClock) 274 | if (result === -1) { 275 | break 276 | } 277 | } else { 278 | var rto2 = utils.safeIntegerMultiplication(this.rtt_timeout, 2) 279 | compareClock = this.lastdoubling.clone() 280 | compareClock.add(n4) 281 | compareClock.add(rto2) 282 | result = this.clock.compare(compareClock) 283 | if (result === -1) { 284 | break 285 | } 286 | } 287 | if (this.nsecperblock <= 65535) { 288 | break 289 | } 290 | this.nsecperblock = this.nsecperblock / 2 291 | this.lastdoubling = this.clock.clone() 292 | if (this.lastedge) { 293 | this.lastedge = this.clock.clone() 294 | } 295 | } 296 | } 297 | 298 | module.exports = Chicago 299 | -------------------------------------------------------------------------------- /src/simple-message-stream.js: -------------------------------------------------------------------------------- 1 | var Chicago = require('./chicago.js') 2 | var Message = require('./message.js') 3 | var assert = require('assert') 4 | var EventEmitter = require('events').EventEmitter 5 | var inherits = require('inherits') 6 | var Block = require('./message-block.js') 7 | var _ = require('lodash') 8 | var constants = require('./constants.js') 9 | var isBuffer = require('is-buffer') 10 | var winston = require('winston') 11 | var winstonWrapper = require('winston-meta-wrapper') 12 | 13 | var MessageStream = function (options) { 14 | if (!options) { 15 | options = {} 16 | } 17 | if (!options.logger) { 18 | options.logger = winston 19 | } 20 | this._log = winstonWrapper(options.logger) 21 | this._log.addMeta({ 22 | module: 'curvecp-messagestream' 23 | }) 24 | EventEmitter.call(this) 25 | this._maxBlockLength = constants.MESSAGE_BODY 26 | this._stream = options.stream 27 | var self = this 28 | this._stream.on('data', this._receiveData.bind(this)) 29 | this._stream.on('error', function (error) { 30 | self.emit('error', error) 31 | }) 32 | this._stream.on('close', function () { 33 | self._cleanup() 34 | self.emit('close') 35 | }) 36 | this.__streamReady = true 37 | /* Bytes that still need to be processed */ 38 | this._sendBytes = new Buffer(0) 39 | this._stopSuccess = false 40 | this._stopFailure = false 41 | this._writeRequests = [] 42 | /* Bytes that have been processed / send to peer */ 43 | this._sendProcessed = 0 44 | /* Blocks that have been send but not yet acknowledged by other party */ 45 | this._outgoing = [] 46 | /* Messages that have been received but not yet processed */ 47 | this._incoming = [] 48 | /* Number of bytes that have been received and send upstream */ 49 | this._receivedBytes = 0 50 | /* Chicago congestion control algorithm */ 51 | this._chicago = new Chicago() 52 | /* nanosecond precision timer */ 53 | this._chicago.set_timeout(this._process.bind(this)) 54 | this.__nextMessageId = 1 55 | } 56 | 57 | inherits(MessageStream, EventEmitter) 58 | 59 | MessageStream.prototype._cleanup = function () { 60 | var writeRequests = this._writeRequests 61 | this._writeRequests = [] 62 | this._sendBytes = new Buffer(0) 63 | _.forEach(writeRequests, function (request) { 64 | request.callback(new Error('Underlying stream does not respond anymore')) 65 | }) 66 | this._outgoing = [] 67 | } 68 | 69 | MessageStream.prototype._nextMessageId = function () { 70 | var result = this.__nextMessageId 71 | this.__nextMessageId += 1 72 | return result 73 | } 74 | 75 | MessageStream.prototype._receiveData = function (data) { 76 | assert(isBuffer(data)) 77 | this._log.debug('_receiveData') 78 | if (_.size(this._incoming) < constants.MAX_INCOMING) { 79 | var message = new Message() 80 | try { 81 | message.fromBuffer(data) 82 | } catch (e) { 83 | this._log.debug(e) 84 | this._log.warn('Invalid message received') 85 | return 86 | } 87 | this._incoming.push(message) 88 | } 89 | var self = this 90 | process.nextTick(function () { 91 | if (self.canProcessMessage()) { 92 | self.processMessage() 93 | } 94 | }) 95 | } 96 | 97 | MessageStream.prototype.destroy = function () { 98 | this._stream.destroy() 99 | } 100 | 101 | MessageStream.prototype._process = function () { 102 | this._log.debug('_process') 103 | var maxReached = _.some(this._outgoing, function (block) { 104 | return block.transmissions > constants.MAX_RETRANSMISSIONS 105 | }) 106 | if (maxReached) { 107 | this._log.warn('maximum retransmissions reached') 108 | this._cleanup() 109 | this.emit('error', new Error('Maximum retransmissions reached - remote host down')) 110 | } 111 | if (this.canResend()) { 112 | this.resendBlock() 113 | } else if (this.canSend()) { 114 | this.sendBlock() 115 | } 116 | if (_.isEmpty(this._incoming) && _.isEmpty(this._outgoing) && this._sendBytes.length === 0) { 117 | this._chicago.disable_timer() 118 | } 119 | } 120 | 121 | MessageStream.prototype.write = function (chunk, done) { 122 | this._log.debug('write') 123 | assert(isBuffer(chunk)) 124 | assert(_.isFunction(done)) 125 | if (this._sendBytes.length > constants.MAXIMUM_UNPROCESSED_SEND_BYTES) { 126 | this._log.warn('Buffer is full') 127 | done(new Error('Buffer is full')) 128 | return 129 | } 130 | this._writeRequests.push({ 131 | startByte: this._sendProcessed, 132 | length: chunk.length, 133 | callback: done 134 | }) 135 | this._sendBytes = Buffer.concat([this._sendBytes, chunk]) 136 | this._log.debug('_sendBytes length: ' + this._sendBytes.length) 137 | this._log.debug('_sendProcessed length: ' + this._sendProcessed) 138 | this._chicago.enable_timer() 139 | return this._sendBytes.length < constants.MAXIMUM_UNPROCESSED_SEND_BYTES 140 | } 141 | 142 | MessageStream.prototype.canResend = function () { 143 | var self = this 144 | if (this.__streamReady && !_.isEmpty(this._outgoing)) { 145 | var some = _.some(this._outgoing, function (block) { 146 | return self._chicago.block_is_timed_out(block.transmissionTime) 147 | }) 148 | return some 149 | } 150 | return false 151 | } 152 | 153 | MessageStream.prototype.resendBlock = function () { 154 | this._log.debug('resendBlock') 155 | var block = this._outgoing[0] 156 | _.forEach(this._outgoing, function (compareBlock) { 157 | if (block.transmissionTime.compare(compareBlock) > 0) { 158 | block = compareBlock 159 | } 160 | }) 161 | block.transmissionTime = this._chicago.get_clock() 162 | block.transmissions = block.transmissions + 1 163 | block.id = this._nextMessageId() 164 | this._chicago.retransmission() 165 | this._sendBlock(block) 166 | } 167 | 168 | MessageStream.prototype.canSend = function () { 169 | return this.__streamReady && this._sendBytes.length > 0 && _.size(this._outgoing) < constants.MAX_OUTGOING 170 | } 171 | 172 | MessageStream.prototype.sendBlock = function () { 173 | this._log.debug('sendBlock') 174 | this._log.debug('sendBytes start: ' + this._sendBytes.length) 175 | this._log.debug('sendProcessed start: ' + this._sendProcessed) 176 | var blockSize = this._sendBytes.length 177 | if (blockSize > this._maxBlockLength) { 178 | blockSize = this._maxBlockLength 179 | } 180 | var block = new Block() 181 | block.startByte = this._sendProcessed 182 | block.transmissionTime = this._chicago.get_clock() 183 | block.id = this._nextMessageId() 184 | block.data = this._sendBytes.slice(0, blockSize) 185 | if (this._sendBytes.length === blockSize && (this._stopSuccess || this._stopFailure)) { 186 | block.stop_success = this._stopSuccess 187 | block.stop_failure = this._stopFailure 188 | } 189 | this._sendBytes = this._sendBytes.slice(blockSize) 190 | this._sendProcessed = this._sendProcessed + block.data.length 191 | this._log.debug('sendBytes stop: ' + this._sendBytes.length) 192 | this._log.debug('sendProcessed stop: ' + this._sendProcessed) 193 | this._outgoing.push(block) 194 | this._sendBlock(block) 195 | if (this._sendBytes.length + block.data.length > constants.MAXIMUM_UNPROCESSED_SEND_BYTES * 0.5 && 196 | this._sendBytes.length < constants.MAXIMUM_UNPROCESSED_SEND_BYTES * 0.5) { 197 | this.emit('drain') 198 | } 199 | } 200 | 201 | MessageStream.prototype._sendBlock = function (block) { 202 | this._log.debug('_sendBlock ' + block.startByte + ' - ' + block.data.length) 203 | var message = new Message() 204 | message.id = block.id 205 | message.acknowledging_range_1_size = this._receivedBytes 206 | message.data = block.data 207 | message.success = block.stop_success 208 | message.failure = block.stop_failure 209 | message.offset = block.startByte 210 | this._chicago.send_block() 211 | this._writeToStream(message) 212 | } 213 | 214 | MessageStream.prototype.canProcessMessage = function () { 215 | return this._incoming.length > 0 216 | } 217 | 218 | MessageStream.prototype.processMessage = function () { 219 | this._log.debug('processMessage') 220 | var message = this._incoming.shift() 221 | this.processAcknowledgments(message) 222 | this._processMessage(message) 223 | } 224 | 225 | MessageStream.prototype.processAcknowledgments = function (message) { 226 | var self = this 227 | this._log.debug('processAcknowledgements') 228 | var removedList 229 | removedList = _.remove(this._outgoing, function (block) { 230 | return message.isAcknowledged(block.startByte, block.data.length) 231 | }) 232 | _.forEach(removedList, function (block) { 233 | self._log.debug('block acknowledged: ' + block.startByte + ' - ' + block.data.length) 234 | self._chicago.acknowledgement(block.transmissionTime) 235 | }) 236 | removedList = _.remove(this._writeRequests, function (writeRequest) { 237 | return message.isAcknowledged(writeRequest.startByte, writeRequest.length) 238 | }) 239 | _.forEach(removedList, function (writeRequest) { 240 | self._log.debug('write request acknowledged: ' + writeRequest.startByte + ' - ' + writeRequest.length) 241 | writeRequest.callback() 242 | }) 243 | if ((this._stopSuccess || this._stopFailure) && this._sendBytes.length === 0 && this._outgoing.length === 0) { 244 | this.emit('close') 245 | } 246 | } 247 | 248 | MessageStream.prototype.sendAcknowledgment = function (message) { 249 | this._log.debug('sendAcknowledgment ' + this._receivedBytes) 250 | var reply = new Message() 251 | reply.id = this._nextMessageId() 252 | reply.acknowledging_id = message.id 253 | reply.acknowledging_range_1_size = this._receivedBytes 254 | this._writeToStream(reply) 255 | } 256 | 257 | MessageStream.prototype._processMessage = function (message) { 258 | this._log.debug('_processMessage') 259 | if (message.offset <= this._receivedBytes) { 260 | if (message.data_length > 1) { 261 | var ignoreBytes = this._receivedBytes - message.offset 262 | var data = message.data.slice(ignoreBytes) 263 | this._receivedBytes += data.length 264 | // debug(data.toString()) 265 | this.emit('data', data) 266 | if (message.success || message.failure) { 267 | this.emit('close') 268 | } 269 | this.sendAcknowledgment(message) 270 | } 271 | } 272 | } 273 | 274 | MessageStream.prototype._writeToStream = function (message) { 275 | this.__streamReady = false 276 | this._stream.write(message.toBuffer(), this._processReady.bind(this)) 277 | } 278 | 279 | MessageStream.prototype._processReady = function (err) { 280 | if (!err) { 281 | this.__streamReady = true 282 | } else { 283 | this._log.warn('error while sending CurveCP message') 284 | this.emit('error', err) 285 | } 286 | } 287 | 288 | Object.defineProperty(MessageStream.prototype, 'remoteAddress', { 289 | get: function () { 290 | return this._stream.remoteAddress 291 | } 292 | }) 293 | 294 | module.exports = MessageStream 295 | -------------------------------------------------------------------------------- /src/message-stream.js: -------------------------------------------------------------------------------- 1 | var Chicago = require('./chicago.js') 2 | var Message = require('./message.js') 3 | var assert = require('assert') 4 | var Duplex = require('readable-stream-no-buffering').Duplex 5 | var inherits = require('inherits') 6 | var Block = require('./message-block.js') 7 | var _ = require('lodash') 8 | var constants = require('./constants.js') 9 | var isBuffer = require('is-buffer') 10 | var winston = require('winston') 11 | var winstonWrapper = require('winston-meta-wrapper') 12 | 13 | var MessageStream = function (options) { 14 | if (!options) { 15 | options = {} 16 | } 17 | if (!options.logger) { 18 | options.logger = winston 19 | } 20 | this._log = winstonWrapper(options.logger) 21 | this._log.addMeta({ 22 | module: 'curvecp-messagestream' 23 | }) 24 | var opts = {} 25 | opts.objectMode = false 26 | opts.decodeStrings = true 27 | opts.allowHalfOpen = false 28 | opts.highWaterMark = 0 29 | Duplex.call(this, opts) 30 | this._maxBlockLength = 640 - constants.HEADER_SIZE - constants.MINIMAL_PADDING 31 | this._stream = options.stream 32 | this.__streamReady = false 33 | var self = this 34 | this._stream.on('data', this._receiveData.bind(this)) 35 | this._stream.on('error', function (error) { 36 | self.emit('error', error) 37 | }) 38 | this._stream.on('close', function () { 39 | self.push(null) 40 | self._cleanup() 41 | self.emit('close') 42 | }) 43 | this._stream.on('end', function () { 44 | self.push(null) 45 | }) 46 | this._stream.on('connect', function () { 47 | var message = new Message() 48 | self._stream.write(message.toBuffer(), function (err) { 49 | if (!err) { 50 | self.__streamReady = true 51 | self.emit('connect') 52 | } else { 53 | self.emit('error', err) 54 | } 55 | }) 56 | }) 57 | this._stream.on('lookup', function (err, address, family) { 58 | self.emit('lookup', err, address, family) 59 | }) 60 | this._stream.on('timeout', function () { 61 | self.emit('timeout') 62 | }) 63 | if (this._stream.is_server) { 64 | this._maxBlockLength = constants.MESSAGE_BODY 65 | } 66 | /* Bytes that still need to be processed */ 67 | this._sendBytes = new Buffer(0) 68 | this._stopSuccess = false 69 | this._stopFailure = false 70 | this._writeRequests = [] 71 | /* Bytes that have been processed / send to peer */ 72 | this._sendProcessed = 0 73 | /* Blocks that have been send but not yet acknowledged by other party */ 74 | this._outgoing = [] 75 | /* Messages that have been received but not yet processed */ 76 | this._incoming = [] 77 | /* Number of bytes that have been received and send upstream */ 78 | this._receivedBytes = 0 79 | /* Chicago congestion control algorithm */ 80 | this._chicago = new Chicago() 81 | /* nanosecond precision timer */ 82 | this._chicago.set_timeout(this._process.bind(this)) 83 | this.__nextMessageId = 1 84 | } 85 | 86 | inherits(MessageStream, Duplex) 87 | 88 | MessageStream.prototype._cleanup = function () { 89 | var writeRequests = this._writeRequests 90 | this._writeRequests = [] 91 | this._sendBytes = new Buffer(0) 92 | _.forEach(writeRequests, function (request) { 93 | request.callback(new Error('Underlying stream does not respond anymore')) 94 | }) 95 | this._outgoing = [] 96 | } 97 | 98 | MessageStream.prototype._nextMessageId = function () { 99 | var result = this.__nextMessageId 100 | this.__nextMessageId += 1 101 | return result 102 | } 103 | 104 | MessageStream.prototype._receiveData = function (data) { 105 | this._log.debug('_receiveData') 106 | if (_.size(this._incoming) < constants.MAX_INCOMING) { 107 | var message = new Message() 108 | try { 109 | message.fromBuffer(data) 110 | } catch (e) { 111 | this._log.warn('Invalid message received') 112 | return 113 | } 114 | this._incoming.push(message) 115 | } 116 | var self = this 117 | process.nextTick(function () { 118 | if (self.canProcessMessage()) { 119 | self.processMessage() 120 | } 121 | }) 122 | } 123 | 124 | MessageStream.prototype.connect = function (boxId, connectionInfo) { 125 | this._stream.connect(boxId, connectionInfo) 126 | } 127 | 128 | MessageStream.prototype.isConnected = function () { 129 | return this._stream.isConnected() 130 | } 131 | 132 | MessageStream.prototype.destroy = function () { 133 | this._stream.destroy() 134 | } 135 | 136 | MessageStream.prototype._read = function (size) {} 137 | 138 | MessageStream.prototype._process = function () { 139 | this._log.debug('_process') 140 | var maxReached = _.some(this._outgoing, function (block) { 141 | return block.transmissions > constants.MAX_RETRANSMISSIONS 142 | }) 143 | if (maxReached) { 144 | this._log.warn('maximum retransmissions reached') 145 | this._cleanup() 146 | this.emit('error', new Error('Maximum retransmissions reached - remote host down')) 147 | } 148 | if (this.canResend()) { 149 | this.resendBlock() 150 | } else if (this.canSend()) { 151 | this.sendBlock() 152 | } 153 | if (_.isEmpty(this._incoming) && _.isEmpty(this._outgoing) && this._sendBytes.length === 0) { 154 | this._chicago.disable_timer() 155 | } 156 | } 157 | 158 | MessageStream.prototype._write = function (chunk, encoding, done) { 159 | this._log.debug('_write') 160 | assert(isBuffer(chunk)) 161 | if (this._sendBytes.length > constants.MAXIMUM_UNPROCESSED_SEND_BYTES) { 162 | this._log.warn('Buffer is full') 163 | done(new Error('Buffer is full')) 164 | return 165 | } 166 | this._writeRequests.push({ 167 | startByte: this._sendProcessed, 168 | length: chunk.length, 169 | callback: done 170 | }) 171 | this._sendBytes = Buffer.concat([this._sendBytes, chunk]) 172 | this._log.debug('_sendBytes length: ' + this._sendBytes.length) 173 | this._log.debug('_sendProcessed length: ' + this._sendProcessed) 174 | this._chicago.enable_timer() 175 | return this._sendBytes.length < constants.MAXIMUM_UNPROCESSED_SEND_BYTES 176 | } 177 | 178 | MessageStream.prototype._end = function () { 179 | this._stopSuccess = true 180 | this._writableState.ended = true 181 | } 182 | 183 | MessageStream.prototype.canResend = function () { 184 | var self = this 185 | if (this.__streamReady && !_.isEmpty(this._outgoing)) { 186 | var some = _.some(this._outgoing, function (block) { 187 | return self._chicago.block_is_timed_out(block.transmissionTime) 188 | }) 189 | return some 190 | } 191 | return false 192 | } 193 | 194 | MessageStream.prototype.resendBlock = function () { 195 | this._log.debug('resendBlock') 196 | var block = this._outgoing[0] 197 | _.forEach(this._outgoing, function (compareBlock) { 198 | if (block.transmissionTime.compare(compareBlock) > 0) { 199 | block = compareBlock 200 | } 201 | }) 202 | block.transmissionTime = this._chicago.get_clock() 203 | block.transmissions = block.transmissions + 1 204 | block.id = this._nextMessageId() 205 | this._chicago.retransmission() 206 | this._sendBlock(block) 207 | } 208 | 209 | MessageStream.prototype.canSend = function () { 210 | return this.__streamReady && this._sendBytes.length > 0 && _.size(this._outgoing) < constants.MAX_OUTGOING 211 | } 212 | 213 | MessageStream.prototype.sendBlock = function () { 214 | this._log.debug('sendBlock') 215 | this._log.debug('sendBytes start: ' + this._sendBytes.length) 216 | this._log.debug('sendProcessed start: ' + this._sendProcessed) 217 | var blockSize = this._sendBytes.length 218 | if (blockSize > this._maxBlockLength) { 219 | blockSize = this._maxBlockLength 220 | } 221 | var block = new Block() 222 | block.startByte = this._sendProcessed 223 | block.transmissionTime = this._chicago.get_clock() 224 | block.id = this._nextMessageId() 225 | block.data = this._sendBytes.slice(0, blockSize) 226 | if (this._sendBytes.length === blockSize && (this._stopSuccess || this._stopFailure)) { 227 | block.stop_success = this._stopSuccess 228 | block.stop_failure = this._stopFailure 229 | } 230 | this._sendBytes = this._sendBytes.slice(blockSize) 231 | this._sendProcessed = this._sendProcessed + block.data.length 232 | this._log.debug('sendBytes stop: ' + this._sendBytes.length) 233 | this._log.debug('sendProcessed stop: ' + this._sendProcessed) 234 | this._outgoing.push(block) 235 | this._sendBlock(block) 236 | if (this._sendBytes.length + block.data.length > constants.MAXIMUM_UNPROCESSED_SEND_BYTES * 0.5 && 237 | this._sendBytes.length < constants.MAXIMUM_UNPROCESSED_SEND_BYTES * 0.5) { 238 | this.emit('drain') 239 | } 240 | } 241 | 242 | MessageStream.prototype._sendBlock = function (block) { 243 | this._log.debug('_sendBlock ' + block.startByte + ' - ' + block.data.length) 244 | var message = new Message() 245 | message.id = block.id 246 | message.acknowledging_range_1_size = this._receivedBytes 247 | message.data = block.data 248 | message.success = block.stop_success 249 | message.failure = block.stop_failure 250 | message.offset = block.startByte 251 | this._chicago.send_block() 252 | this._writeToStream(message) 253 | this._maxBlockLength = constants.MESSAGE_BODY 254 | } 255 | 256 | MessageStream.prototype.canProcessMessage = function () { 257 | return this._incoming.length > 0 258 | } 259 | 260 | MessageStream.prototype.processMessage = function () { 261 | this._log.debug('processMessage') 262 | var message = this._incoming.shift() 263 | this.processAcknowledgments(message) 264 | this._processMessage(message) 265 | } 266 | 267 | MessageStream.prototype.processAcknowledgments = function (message) { 268 | var self = this 269 | this._log.debug('processAcknowledgements') 270 | var removedList 271 | removedList = _.remove(this._outgoing, function (block) { 272 | return message.isAcknowledged(block.startByte, block.data.length) 273 | }) 274 | _.forEach(removedList, function (block) { 275 | self._log.debug('block acknowledged: ' + block.startByte + ' - ' + block.data.length) 276 | self._chicago.acknowledgement(block.transmissionTime) 277 | }) 278 | removedList = _.remove(this._writeRequests, function (writeRequest) { 279 | return message.isAcknowledged(writeRequest.startByte, writeRequest.length) 280 | }) 281 | _.forEach(removedList, function (writeRequest) { 282 | self._log.debug('write request acknowledged: ' + writeRequest.startByte + ' - ' + writeRequest.length) 283 | writeRequest.callback() 284 | }) 285 | if ((this._stopSuccess || this._stopFailure) && this._sendBytes.length === 0 && this._outgoing.length === 0) { 286 | this.emit('finish') 287 | } 288 | } 289 | 290 | MessageStream.prototype.sendAcknowledgment = function (message) { 291 | this._log.debug('sendAcknowledgment ' + this._receivedBytes) 292 | var reply = new Message() 293 | reply.id = this._nextMessageId() 294 | reply.acknowledging_id = message.id 295 | reply.acknowledging_range_1_size = this._receivedBytes 296 | this._writeToStream(reply) 297 | } 298 | 299 | MessageStream.prototype._processMessage = function (message) { 300 | this._log.debug('_processMessage') 301 | if (message.offset <= this._receivedBytes) { 302 | if (message.data_length > 1) { 303 | var ignoreBytes = this._receivedBytes - message.offset 304 | var data = message.data.slice(ignoreBytes) 305 | this._receivedBytes += data.length 306 | // debug(data.toString()) 307 | this.push(data) 308 | if (message.success || message.failure) { 309 | this.push(null) 310 | } 311 | this.sendAcknowledgment(message) 312 | } 313 | } 314 | } 315 | 316 | MessageStream.prototype._writeToStream = function (message) { 317 | this.__streamReady = false 318 | this._stream.write(message.toBuffer(), this._processReady.bind(this)) 319 | } 320 | 321 | MessageStream.prototype._processReady = function (err) { 322 | if (!err) { 323 | this.__streamReady = true 324 | } else { 325 | this._log.warn('error while sending CurveCP message') 326 | this.emit('error', err) 327 | } 328 | } 329 | 330 | Object.defineProperty(MessageStream.prototype, 'remoteAddress', { 331 | get: function () { 332 | return this._stream.remoteAddress 333 | } 334 | }) 335 | 336 | module.exports = MessageStream 337 | -------------------------------------------------------------------------------- /src/packet-stream.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var Duplex = require('stream').Duplex 3 | var inherits = require('inherits') 4 | var extend = require('extend.js') 5 | var Uint64BE = require('int64-buffer').Uint64BE 6 | var nacl = require('tweetnacl') 7 | var crypto = require('crypto') 8 | var _ = require('lodash') 9 | nacl.util = require('tweetnacl-util') 10 | var utils = require('./utils.js') 11 | var winston = require('winston') 12 | var winstonWrapper = require('winston-meta-wrapper') 13 | 14 | var HELLO_MSG = nacl.util.decodeUTF8('QvnQ5XlH') 15 | var COOKIE_MSG = nacl.util.decodeUTF8('RL3aNMXK') 16 | var INITIATE_MSG = nacl.util.decodeUTF8('QvnQ5XlI') 17 | var SERVER_MSG = nacl.util.decodeUTF8('RL3aNMXM') 18 | var CLIENT_MSG = nacl.util.decodeUTF8('QvnQ5XlM') 19 | 20 | var HELLO_WAIT = [1000000000, 1500000000, 2250000000, 3375000000, 5062500000, 7593750000, 11390625000, 17085937500] 21 | 22 | var MINUTE_KEY_TIMEOUT = 1000 * 60 * 2 23 | 24 | nacl.setPRNG(function (x, n) { 25 | var i 26 | var v = crypto.randomBytes(n) 27 | for (i = 0; i < n; i++) x[i] = v[i] 28 | for (i = 0; i < v.length; i++) v[i] = 0 29 | }) 30 | 31 | var PacketStream = function (opts) { 32 | if (!opts) opts = {} 33 | if (!opts.logger) { 34 | opts.logger = winston 35 | } 36 | this._log = winstonWrapper(opts.logger) 37 | this._log.addMeta({ 38 | module: 'curvecp-packetstream' 39 | }) 40 | opts.objectMode = false 41 | opts.decodeStrings = true 42 | opts.allowHalfOpen = false 43 | this.__ourNonceCounter = 0 44 | this.__remoteNonceCounter = 0 45 | this.__helloCounter = 0 46 | this.__state = null 47 | this.__sharedKey = null 48 | Duplex.call(this, opts) 49 | extend(this, { 50 | __canSend: false, 51 | __pendingWrite: null, 52 | __initiateSend: false, 53 | stream: null, 54 | isServer: false, 55 | serverName: new Uint8Array(256), 56 | clientExtension: new Uint8Array(16), 57 | serverExtension: new Uint8Array(16), 58 | serverPublicKey: null, 59 | serverPrivateKey: null, 60 | serverConnectionPublicKey: null, 61 | serverConnectionPrivateKey: null, 62 | clientPublicKey: null, 63 | clientPrivateKey: null, 64 | clientConnectionPublicKey: null, 65 | clientConnectionPrivateKey: null, 66 | __serverCookie: null 67 | }, opts) 68 | if (this.serverName.length !== 256) { 69 | var buffer = new Buffer(256) 70 | buffer.fill(0) 71 | buffer.write('0A', 'hex') 72 | buffer.write(this.serverName, 1) 73 | this.serverName = new Uint8Array(buffer) 74 | } 75 | if (!this.isServer) { 76 | var keyPair = nacl.box.keyPair() 77 | this.clientConnectionPublicKey = keyPair.publicKey 78 | this.clientConnectionPrivateKey = keyPair.secretKey 79 | } 80 | this._connectStream(this.stream) 81 | } 82 | 83 | inherits(PacketStream, Duplex) 84 | 85 | PacketStream.prototype._startCookieKeyTimeout = function () { 86 | var self = this 87 | this.__cookieKey = nacl.randomBytes(nacl.secretbox.keyLength) 88 | setTimeout(function () { 89 | delete self.__cookieKey 90 | }, MINUTE_KEY_TIMEOUT) 91 | } 92 | 93 | PacketStream.prototype.toMetadata = function () { 94 | return { 95 | isServer: this.isServer, 96 | serverPublicKey: this._encode(this.serverPublicKey), 97 | clientPublicKey: this._encode(this.clientPublicKey), 98 | serverConnectionPublicKey: this._encode(this.serverConnectionPublicKey), 99 | clientConnectionPublicKey: this._encode(this.clientConnectionPublicKey) 100 | } 101 | } 102 | 103 | PacketStream.prototype._encode = function (array) { 104 | if (array !== undefined && array !== null) { 105 | return nacl.util.encodeBase64(array) 106 | } else { 107 | return 108 | } 109 | } 110 | 111 | PacketStream.prototype._canSend = function () { 112 | return this.__canSend 113 | } 114 | 115 | PacketStream.prototype._setCanSend = function (canSend) { 116 | if (canSend === this.__canSend) { 117 | return 118 | } 119 | this.__canSend = canSend 120 | if (canSend && this.__pendingWrite) { 121 | this._write(this.__pendingWrite.chunk, this.__pendingWrite.encoding, this.__pendingWrite.done) 122 | this.__pendingWrite = null 123 | } 124 | } 125 | 126 | PacketStream.prototype._connectStream = function (stream) { 127 | var curveStream = this 128 | var functions = { 129 | data: function (data) { 130 | if (data.length < 30) { 131 | return 132 | } 133 | curveStream._onMessage(new Uint8Array(data)) 134 | }, 135 | error: function (err) { 136 | if (!_.isError(err)) { 137 | err = new Error(err) 138 | } 139 | curveStream.emit('error', err) 140 | }, 141 | close: function () { 142 | curveStream.emit('close') 143 | }, 144 | end: function () { 145 | curveStream.emit('end') 146 | }, 147 | drain: function () { 148 | curveStream.emit('drain') 149 | }, 150 | lookup: function (err, address, family) { 151 | curveStream.emit('lookup', err, address, family) 152 | }, 153 | timeout: function () { 154 | if (curveStream.isConnected()) { 155 | curveStream.emit('timeout') 156 | } else { 157 | curveStream.emit('error', new Error('Timeout expired to establish connection')) 158 | } 159 | } 160 | } 161 | stream.on('data', functions.data) 162 | stream.on('error', functions.error) 163 | stream.on('close', functions.close) 164 | stream.on('end', functions.end) 165 | stream.on('drain', functions.drain) 166 | stream.on('lookup', functions.lookup) 167 | stream.on('timeout', functions.timeout) 168 | } 169 | 170 | PacketStream.prototype._onMessage = function (message) { 171 | this._log.debug('_onMessage') 172 | if (this.isServer) { 173 | this._onMessageServer(message) 174 | } else { 175 | this._onMessageClient(message) 176 | } 177 | } 178 | 179 | PacketStream.prototype._onMessageClient = function (message) { 180 | this._log.debug('_onMessage@Client') 181 | if (message.length < 64 || message.length > 1152) { 182 | return 183 | } 184 | var messageType = message.subarray(0, 8) 185 | if (this._isEqual(messageType, COOKIE_MSG)) { 186 | this._onCookie(message) 187 | this.__state = COOKIE_MSG 188 | } else if (this._isEqual(messageType, SERVER_MSG) && 189 | (this.__state === COOKIE_MSG || this.__state === SERVER_MSG)) { 190 | this._onServerMessage(message) 191 | this.__state = SERVER_MSG 192 | } else { 193 | this._log.warn('invalid packet received') 194 | } 195 | } 196 | 197 | PacketStream.prototype._onMessageServer = function (message) { 198 | this._log.debug('_onMessage@Server') 199 | if (message.length < 96 || message.length > 1184) { 200 | return 201 | } 202 | var messageType = message.subarray(0, 8) 203 | if (this._isEqual(messageType, HELLO_MSG)) { 204 | this._onHello(message) 205 | this.__state = HELLO_MSG 206 | } else if (this._isEqual(messageType, INITIATE_MSG) && this.__state === HELLO_MSG) { 207 | this._onInitiate(message) 208 | this.__state = INITIATE_MSG 209 | } else if (this._isEqual(messageType, CLIENT_MSG) && 210 | (this.__state === INITIATE_MSG || this.__state === CLIENT_MSG)) { 211 | this._onClientMessage(message) 212 | this.__state = CLIENT_MSG 213 | } else { 214 | this._log.warn('invalid packet received') 215 | } 216 | } 217 | 218 | PacketStream.prototype.connect = function (boxId, connectionInfo) { 219 | this._log.debug('connect', { 220 | connectionInfo: connectionInfo, 221 | boxId: boxId 222 | }) 223 | var self = this 224 | if (!this.isServer) { 225 | this.serverPublicKey = nacl.util.decodeBase64(boxId) 226 | } 227 | if (this.stream.isConnected()) { 228 | if (!this.isServer) { 229 | this._sendHello() 230 | } 231 | } else { 232 | this.stream.once('connect', function () { 233 | self._log.debug('underlying stream connected') 234 | self.connect(boxId, connectionInfo) 235 | }) 236 | this.stream.connect(connectionInfo) 237 | } 238 | } 239 | 240 | PacketStream.prototype.setDestination = function (destination) { 241 | this.serverPublicKey = nacl.util.decodeBase64(destination) 242 | } 243 | 244 | PacketStream.prototype.isConnected = function () { 245 | return this.stream.isConnected() && this.__sharedKey 246 | } 247 | 248 | PacketStream.prototype.destroy = function () { 249 | this.stream.destroy() 250 | } 251 | 252 | PacketStream.prototype._read = function (size) { 253 | this._log.debug('_read') 254 | } 255 | 256 | PacketStream.prototype._write = function (chunk, encoding, done) { 257 | this._log.debug('_write') 258 | if (this._canSend()) { 259 | if (this.isServer) { 260 | this._sendServerMessage(chunk, done) 261 | } else { 262 | if (this.__initiateSend) { 263 | this._sendClientMessage(chunk, done) 264 | } else { 265 | this._sendInitiate(chunk, done) 266 | } 267 | } 268 | } else { 269 | if (this.__pendingWrite) { 270 | done(new Error('Error: You can not write to stream while previous write did not yet return')) 271 | return 272 | } 273 | this.__pendingWrite = { 274 | chunk: chunk, 275 | encoding: encoding, 276 | done: done 277 | } 278 | } 279 | } 280 | 281 | // utility functions 282 | 283 | PacketStream.prototype._isEqual = function (a, b) { 284 | // debug('isEqual') 285 | if (a.length !== b.length) { 286 | return false 287 | } 288 | for (var i = 0; i < a.length; i++) { 289 | if (a[i] !== b[i]) { 290 | return false 291 | } 292 | } 293 | return true 294 | } 295 | 296 | PacketStream.prototype._decrypt = function (source, prefix, from, to) { 297 | // debug('decrypt') 298 | try { 299 | prefix = nacl.util.decodeUTF8(prefix) 300 | var nonceLength = 24 - prefix.length 301 | var shortNonce = source.subarray(0, nonceLength) 302 | var nonce = new Uint8Array(24) 303 | nonce.set(prefix) 304 | nonce.set(shortNonce, prefix.length) 305 | var result = nacl.box.open(source.subarray(nonceLength), nonce, from, to) 306 | } catch (err) { 307 | this._log.warn('Decrypt failed with error ' + err) 308 | } 309 | return result 310 | } 311 | 312 | PacketStream.prototype._decryptShared = function (source, prefix) { 313 | try { 314 | prefix = nacl.util.decodeUTF8(prefix) 315 | var nonceLength = 24 - prefix.length 316 | var shortNonce = source.subarray(0, nonceLength) 317 | var nonce = new Uint8Array(24) 318 | nonce.set(prefix) 319 | nonce.set(shortNonce, prefix.length) 320 | var result = nacl.box.open.after(source.subarray(nonceLength), nonce, this.__sharedKey) 321 | } catch (err) { 322 | this._log.warn('Decrypt failed with error ' + err) 323 | } 324 | return result 325 | } 326 | 327 | PacketStream.prototype._encrypt = function (data, nonce, prefixLength, from, to) { 328 | // debug('encrypt') 329 | var box = nacl.box(data, nonce, to, from) 330 | var result = new Uint8Array(24 - prefixLength + box.length) 331 | var shortNonce = nonce.subarray(prefixLength) 332 | result.set(shortNonce) 333 | result.set(box, 24 - prefixLength) 334 | return result 335 | } 336 | 337 | PacketStream.prototype._encryptShared = function (data, nonce, prefixLength) { 338 | var box = nacl.box.after(data, nonce, this.__sharedKey) 339 | var result = new Uint8Array(24 - prefixLength + box.length) 340 | var shortNonce = nonce.subarray(prefixLength) 341 | result.set(shortNonce) 342 | result.set(box, 24 - prefixLength) 343 | return result 344 | } 345 | 346 | PacketStream.prototype._encryptSymmetric = function (data, prefix, key) { 347 | prefix = nacl.util.decodeUTF8(prefix) 348 | var nonceLength = 24 - prefix.length 349 | var randomNonce = new Uint8Array(nacl.randomBytes(nacl.secretbox.nonceLength)) 350 | var shortNonce = randomNonce.subarray(0, nonceLength) 351 | var nonce = new Uint8Array(24) 352 | nonce.set(prefix) 353 | nonce.set(shortNonce, prefix.length) 354 | var box = nacl.secretbox(data, nonce, key) 355 | var result = new Uint8Array(nonceLength + box.length) 356 | result.set(shortNonce) 357 | result.set(box, nonceLength) 358 | return result 359 | } 360 | 361 | PacketStream.prototype._decryptSymmetric = function (data, prefix, key) { 362 | try { 363 | prefix = nacl.util.decodeUTF8(prefix) 364 | var nonceLength = 24 - prefix.length 365 | var shortNonce = data.subarray(0, nonceLength) 366 | var nonce = new Uint8Array(24) 367 | nonce.set(prefix) 368 | nonce.set(shortNonce, prefix.length) 369 | var result = nacl.secretbox.open(data.subarray(nonceLength), nonce, key) 370 | } catch (err) { 371 | this._log.warn('Decrypt failed with error ' + err) 372 | } 373 | return result 374 | } 375 | 376 | PacketStream.prototype._setExtensions = function (array) { 377 | if (this.isServer) { 378 | array.set(this.clientExtension, 8) 379 | array.set(this.serverExtension, 24) 380 | } else { 381 | array.set(this.serverExtension, 8) 382 | array.set(this.clientExtension, 24) 383 | } 384 | return array 385 | } 386 | 387 | PacketStream.prototype._validExtensions = function (array) { 388 | if (this.isServer) { 389 | return this._validServerExtension(array.subarray(8, 8 + 16)) && 390 | this._validClientExtension(array.subarray(8 + 16, 8 + 16 + 16)) 391 | } else { 392 | return this._validClientExtension(array.subarray(8, 8 + 16)) && 393 | this._validServerExtension(array.subarray(8 + 16, 8 + 16 + 16)) 394 | } 395 | } 396 | 397 | PacketStream.prototype._validServerExtension = function (extension) { 398 | return this._isEqual(extension, this.serverExtension) 399 | } 400 | 401 | PacketStream.prototype._validClientExtension = function (extension) { 402 | return this._isEqual(extension, this.clientExtension) 403 | } 404 | 405 | PacketStream.prototype._createNonceFromCounter = function (prefix) { 406 | this._increaseCounter() 407 | var nonce = new Uint8Array(24) 408 | nonce.set(nacl.util.decodeUTF8(prefix)) 409 | var counter = new Uint8Array(new Uint64BE(this.__ourNonceCounter).toBuffer()).reverse() 410 | nonce.set(counter, 16) 411 | return nonce 412 | } 413 | 414 | PacketStream.prototype._createRandomNonce = function (prefix) { 415 | var nonce = new Uint8Array(24) 416 | nonce.set(nacl.util.decodeUTF8(prefix)) 417 | nonce.set(nacl.randomBytes(16), 8) 418 | return nonce 419 | } 420 | 421 | PacketStream.prototype._increaseCounter = function () { 422 | this.__ourNonceCounter += 1 423 | } 424 | 425 | PacketStream.prototype.__validNonce = function (message, offset) { 426 | var remoteNonce = new Uint64BE(new Buffer(message.subarray(offset, 8).reverse())).toNumber() 427 | if (remoteNonce > this.__remoteNonceCounter || (this.__remoteNonceCounter === 0 && remoteNonce === 0)) { 428 | this.__remoteNonceCounter = remoteNonce 429 | return true 430 | } else { 431 | return false 432 | } 433 | } 434 | 435 | // Hello command 436 | 437 | PacketStream.prototype._sendHello = function () { 438 | this._log.debug('sendHello') 439 | var self = this 440 | this._setCanSend(false) 441 | this.__initiateSend = false 442 | var result = new Uint8Array(224) 443 | result.set(HELLO_MSG, 0) 444 | result.set(this.clientConnectionPublicKey, 40) 445 | var nonce = this._createNonceFromCounter('CurveCP-client-H') 446 | var box = this._encrypt(new Uint8Array(64), nonce, 16, this.clientConnectionPrivateKey, this.serverPublicKey) 447 | result.set(box, 136) 448 | result = this._setExtensions(result) 449 | this.stream.write(new Buffer(result)) 450 | var wait = HELLO_WAIT[this.__helloCounter] 451 | setTimeout(function () { 452 | if (self.__state === COOKIE_MSG || self.__state === SERVER_MSG) {} else if (self.__helloCounter < HELLO_WAIT.length + 1) { 453 | self.__helloCounter += 1 454 | self._sendHello() 455 | } else { 456 | self.emit('error', new Error('Maximum resends of HELLO packet reached, aborting')) 457 | } 458 | }, (wait + utils.randommod(wait)) / 1000000) 459 | } 460 | 461 | PacketStream.prototype._onHello = function (helloMessage) { 462 | this._log.debug('onHello') 463 | this._setCanSend(false) 464 | if (helloMessage.length !== 224) { 465 | this._log.warn('Hello message has incorrect length') 466 | return 467 | } 468 | this.clientExtension = helloMessage.subarray(8 + 16, 8 + 16 + 16) 469 | if (!this._validServerExtension(helloMessage.subarray(8, 8 + 16))) { 470 | this._log.warn('Invalid server extension in hello message') 471 | return 472 | } 473 | this.clientConnectionPublicKey = helloMessage.subarray(40, 40 + 32) 474 | if (!this.__validNonce(helloMessage, 40 + 32 + 64)) { 475 | this._log.warn('Invalid nonce received') 476 | return 477 | } 478 | var boxData = this._decrypt(helloMessage.subarray(40 + 32 + 64, 224), 'CurveCP-client-H', this.clientConnectionPublicKey, this.serverPrivateKey) 479 | if (boxData === undefined) { 480 | this._log.warn('Hello: not able to decrypt box data') 481 | return 482 | } 483 | if (!this._isEqual(boxData, new Uint8Array(64))) { 484 | this._log.warn('Hello: invalid data in signature box') 485 | return 486 | } 487 | this._sendCookie() 488 | } 489 | 490 | // Cookie command 491 | 492 | PacketStream.prototype._sendCookie = function () { 493 | this._log.debug('sendCookie') 494 | this._setCanSend(false) 495 | var keyPair = nacl.box.keyPair() 496 | this.serverConnectionPublicKey = keyPair.publicKey 497 | this.serverConnectionPrivateKey = keyPair.secretKey 498 | this.__sharedKey = nacl.box.before(this.clientConnectionPublicKey, this.serverConnectionPrivateKey) 499 | var result = new Uint8Array(200) 500 | result.set(COOKIE_MSG) 501 | var boxData = new Uint8Array(128) 502 | boxData.set(this.serverConnectionPublicKey) 503 | var cookieData = new Uint8Array(64) 504 | cookieData.set(this.clientConnectionPublicKey) 505 | cookieData.set(this.serverConnectionPrivateKey, 32) 506 | this._startCookieKeyTimeout() 507 | var serverCookie = this._encryptSymmetric(cookieData, 'minute-k', this.__cookieKey) 508 | boxData.set(serverCookie, 32) 509 | var nonce = this._createRandomNonce('CurveCPK') 510 | var encryptedBoxData = this._encrypt(boxData, nonce, 8, this.serverPrivateKey, this.clientConnectionPublicKey) 511 | result.set(encryptedBoxData, 40) 512 | result = this._setExtensions(result) 513 | this.stream.write(new Buffer(result)) 514 | } 515 | 516 | PacketStream.prototype._isValidCookie = function (cookie) { 517 | var cookieData = this._decryptSymmetric(cookie, 'minute-k', this.__cookieKey) 518 | if (!cookieData) { 519 | return false 520 | } 521 | return this._isEqual(cookieData.subarray(0, 32), this.clientConnectionPublicKey) && 522 | this._isEqual(cookieData.subarray(32), this.serverConnectionPrivateKey) 523 | } 524 | 525 | PacketStream.prototype._onCookie = function (cookieMessage) { 526 | this._log.debug('onCookie') 527 | this._setCanSend(false) 528 | if (cookieMessage.length !== 200) { 529 | this._log.warn('Cookie message has incorrect length') 530 | return 531 | } 532 | if (!this._validExtensions(cookieMessage)) { 533 | this._log.warn('Invalid extensions') 534 | return 535 | } 536 | var boxData = this._decrypt(cookieMessage.subarray(40, 200), 'CurveCPK', this.serverPublicKey, this.clientConnectionPrivateKey) 537 | if (boxData === undefined || !boxData) { 538 | this._log.warn('Not able to decrypt cookie box data') 539 | return 540 | } 541 | this.serverConnectionPublicKey = boxData.subarray(0, 32) 542 | this.__sharedKey = nacl.box.before(this.serverConnectionPublicKey, this.clientConnectionPrivateKey) 543 | this.__serverCookie = boxData.subarray(32) 544 | if (this.__serverCookie.length !== 96) { 545 | this._log.warn('Server cookie invalid') 546 | return 547 | } 548 | this._setCanSend(true) 549 | this.emit('connect') 550 | } 551 | 552 | // Initiate command 553 | 554 | PacketStream.prototype._sendInitiate = function (message, done) { 555 | this._log.debug('sendInitiate ' + nacl.util.encodeBase64(this.clientPublicKey) + ' > ' + nacl.util.encodeBase64(this.serverPublicKey)) 556 | if (message.length & 15) { 557 | this._log.warn('message is of incorrect length, needs to be multiple of 16') 558 | return 559 | } 560 | var result = new Uint8Array(544 + message.length) 561 | result.set(INITIATE_MSG) 562 | result.set(this.clientConnectionPublicKey, 40) 563 | result.set(this.__serverCookie, 72) 564 | var initiateBoxData = new Uint8Array(352 + message.length) 565 | initiateBoxData.set(this.clientPublicKey) 566 | initiateBoxData.set(this._createVouch(), 32) 567 | initiateBoxData.set(this.serverName, 96) 568 | initiateBoxData.set(message, 352) 569 | var nonce = this._createNonceFromCounter('CurveCP-client-I') 570 | result.set(this._encryptShared(initiateBoxData, nonce, 16), 168) 571 | result = this._setExtensions(result) 572 | this.stream.write(new Buffer(result), done) 573 | this.__initiateSend = true 574 | this._setCanSend(false) 575 | } 576 | 577 | PacketStream.prototype._createVouch = function () { 578 | var nonce = this._createRandomNonce('CurveCPV') 579 | return this._encrypt(this.clientConnectionPublicKey, nonce, 8, this.clientPrivateKey, this.serverPublicKey) 580 | } 581 | 582 | PacketStream.prototype._onInitiate = function (initiateMessage) { 583 | this._log.debug('onInitiate') 584 | this._setCanSend(false) 585 | if (initiateMessage.length < 544) { 586 | this._log.warn('Initiate command has incorrect length') 587 | return 588 | } 589 | if (!this._isEqual(initiateMessage.subarray(40, 40 + 32), this.clientConnectionPublicKey)) { 590 | this._log.warn('Invalid client connection key') 591 | return 592 | } 593 | if (!this._validExtensions(initiateMessage)) { 594 | this._log.warn('Invalid extensions') 595 | return 596 | } 597 | if (!this.__validNonce(initiateMessage, 72 + 96)) { 598 | this._log.warn('Invalid nonce received') 599 | return 600 | } 601 | if (!this._isValidCookie(initiateMessage.subarray(72, 72 + 96))) { 602 | this._log.warn('Initiate command server cookie not recognized') 603 | return 604 | } 605 | var initiateBoxData = this._decryptShared(initiateMessage.subarray(72 + 96), 'CurveCP-client-I') 606 | if (initiateBoxData === undefined) { 607 | this._log.warn('Not able to decrypt initiate box data') 608 | return 609 | } 610 | this.clientPublicKey = initiateBoxData.subarray(0, 32) 611 | var vouch = this._decrypt(initiateBoxData.subarray(32, 96), 'CurveCPV', this.clientPublicKey, this.serverPrivateKey) 612 | if (vouch === undefined) { 613 | this._log.warn('not able to decrypt vouch data') 614 | return 615 | } 616 | if (!this._isEqual(vouch, this.clientConnectionPublicKey)) { 617 | this._log.warn('Initiate command vouch contains different client connection public key than previously received') 618 | return 619 | } 620 | if (!this._isEqual(initiateBoxData.subarray(32 + 16 + 48, 32 + 16 + 48 + 256), this.serverName)) { 621 | this._log.warn('Invalid server name') 622 | return 623 | } 624 | this._setCanSend(true) 625 | this.emit('connect') 626 | this.push(new Buffer(initiateBoxData.subarray(32 + 16 + 48 + 256))) 627 | } 628 | 629 | // Message command - Server 630 | 631 | PacketStream.prototype._sendServerMessage = function (message, done) { 632 | this._log.debug('sendServerMessage') 633 | var result = new Uint8Array(64 + message.length) 634 | result.set(SERVER_MSG) 635 | var nonce = this._createNonceFromCounter('CurveCP-server-M') 636 | var messageBox = this._encryptShared(message, nonce, 16) 637 | result.set(messageBox, 8 + 16 + 16) 638 | result = this._setExtensions(result) 639 | this.stream.write(new Buffer(result), done) 640 | } 641 | 642 | PacketStream.prototype._onServerMessage = function (message) { 643 | this._log.debug('onServerMessage@Client') 644 | if (message.length < 64 || message.length > 1152) { 645 | this._log.warn('Message command has incorrect length') 646 | return 647 | } 648 | if (!this._validExtensions(message)) { 649 | this._log.warn('Invalid extensions') 650 | return 651 | } 652 | if (!this.__validNonce(message, 40)) { 653 | this._log.warn('Invalid nonce received') 654 | return 655 | } 656 | var boxData = this._decryptShared(message.subarray(40), 'CurveCP-server-M') 657 | if (boxData === undefined || !boxData) { 658 | this._log.warn('not able to decrypt box data') 659 | return 660 | } 661 | this._setCanSend(true) 662 | var buffer = new Buffer(boxData) 663 | this.push(buffer) 664 | } 665 | 666 | // Message command - Client 667 | 668 | PacketStream.prototype._sendClientMessage = function (message, done) { 669 | this._log.debug('sendClientMessage ' + nacl.util.encodeBase64(this.clientPublicKey) + ' > ' + nacl.util.encodeBase64(this.serverPublicKey)) 670 | var result = new Uint8Array(96 + message.length) 671 | result.set(CLIENT_MSG) 672 | result.set(this.clientConnectionPublicKey, 40) 673 | var nonce = this._createNonceFromCounter('CurveCP-client-M') 674 | var messageBox = this._encryptShared(message, nonce, 16) 675 | result.set(messageBox, 8 + 16 + 16 + 32) 676 | result = this._setExtensions(result) 677 | this.stream.write(new Buffer(result), done) 678 | } 679 | 680 | PacketStream.prototype._onClientMessage = function (message) { 681 | this._log.debug('onClientMessage@Server ' + nacl.util.encodeBase64(this.clientPublicKey) + ' > ' + nacl.util.encodeBase64(this.serverPublicKey)) 682 | if (message.length < 96 || message.length > 1184) { 683 | this._log.warn('Message command has incorrect length') 684 | return 685 | } 686 | if (!this._validExtensions(message)) { 687 | this._log.warn('Invalid extensions') 688 | return 689 | } 690 | if (!this._isEqual(message.subarray(40, 40 + 32), this.clientConnectionPublicKey)) { 691 | this._log.warn('Invalid client connection key') 692 | return 693 | } 694 | if (!this.__validNonce(message, 40 + 32)) { 695 | this._log.warn('Invalid nonce received') 696 | return 697 | } 698 | var boxData = this._decryptShared(message.subarray(40 + 32), 'CurveCP-client-M') 699 | if (boxData === undefined || !boxData) { 700 | this._log.warn('not able to decrypt box data') 701 | return 702 | } 703 | var buffer = new Buffer(boxData) 704 | this._setCanSend(true) 705 | this.push(buffer) 706 | } 707 | 708 | Object.defineProperty(PacketStream.prototype, 'remoteAddress', { 709 | get: function () { 710 | if (this.isServer) { 711 | if (!this.clientPublicKey) { 712 | return 713 | } else { 714 | return nacl.util.encodeBase64(this.clientPublicKey) 715 | } 716 | } else { 717 | return nacl.util.encodeBase64(this.serverPublicKey) 718 | } 719 | } 720 | }) 721 | 722 | module.exports = PacketStream 723 | --------------------------------------------------------------------------------