├── .gitignore ├── bench.js ├── collaborators.md ├── contributing.md ├── index.js ├── package.json ├── readme.md └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /bench.js: -------------------------------------------------------------------------------- 1 | var multiplex = require('./') 2 | var time = Date.now() 3 | 4 | var plex = multiplex(function (stream, name) { 5 | stream.pipe(stream) 6 | }) 7 | 8 | plex.pipe(plex) 9 | 10 | var stream = plex.createStream() 11 | var hello = new Buffer(16 * 1024 - 1) 12 | var sent = 100000 13 | var rcvd = 0 14 | 15 | // the bench is just to test the actual parsing speed on multiplex 16 | // don't put too much weight into it - its mainly used to spot performance regressions between commits 17 | stream.on('data', function (data) { 18 | rcvd += data.length 19 | if (!--sent) { 20 | var delta = Date.now() - time 21 | console.log('%d b/s (%d ms)', Math.floor(100000 * rcvd / delta) / 100, delta) 22 | process.exit(0) 23 | } 24 | }) 25 | 26 | stream.pipe(stream) 27 | stream.write(hello) 28 | -------------------------------------------------------------------------------- /collaborators.md: -------------------------------------------------------------------------------- 1 | ## Collaborators 2 | 3 | multiplex is only possible due to the excellent work of the following collaborators: 4 | 5 | 6 | 7 | 8 | 9 | 10 |
substackGitHub/substack
maxogdenGitHub/maxogden
mafintoshGitHub/mafintosh
diasdavidGitHub/diasdavid
1N50MN14GitHub/1N50MN14
11 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # multiplex is an OPEN Open Source Project 2 | 3 | ----------------------------------------- 4 | 5 | ## What? 6 | 7 | Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project. 8 | 9 | ## Rules 10 | 11 | There are a few basic ground-rules for contributors: 12 | 13 | 1. **No `--force` pushes** or modifying the Git history in any way. 14 | 1. **Non-master branches** ought to be used for ongoing work. 15 | 1. **External API changes and significant modifications** ought to be subject to an **internal pull-request** to solicit feedback from other contributors. 16 | 1. Internal pull-requests to solicit feedback are *encouraged* for any other non-trivial contribution but left to the discretion of the contributor. 17 | 1. Contributors should attempt to adhere to the prevailing code-style. 18 | 19 | ## Releases 20 | 21 | Declaring formal releases remains the prerogative of the project maintainer. 22 | 23 | ## Changes to this arrangement 24 | 25 | This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change. 26 | 27 | ----------------------------------------- -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var stream = require('readable-stream') 2 | var varint = require('varint') 3 | var EventEmitter = require('events').EventEmitter 4 | var xtend = require('xtend') 5 | var inherits = require('inherits') 6 | var duplexify = require('duplexify') 7 | 8 | var SIGNAL_FLUSH = new Buffer([0]) 9 | 10 | var empty = new Buffer(0) 11 | var pool = new Buffer(10 * 1024) 12 | var used = 0 13 | 14 | var Channel = function (name, plex, opts) { 15 | if (!opts) opts = {} 16 | stream.Duplex.call(this) 17 | 18 | this.name = name 19 | this.channel = 0 20 | this.initiator = false 21 | this.chunked = !!opts.chunked 22 | this.halfOpen = !!opts.halfOpen 23 | this.destroyed = false 24 | this.finalized = false 25 | 26 | this._multiplex = plex 27 | this._dataHeader = 0 28 | this._opened = false 29 | this._awaitDrain = 0 30 | this._lazy = !!opts.lazy 31 | 32 | var finished = false 33 | var ended = false 34 | 35 | this.once('end', function () { 36 | this._read() // trigger drain 37 | if (this.destroyed) return 38 | ended = true 39 | if (finished) this._finalize() 40 | else if (!this.halfOpen) this.end() 41 | }) 42 | 43 | this.once('finish', function onfinish () { 44 | if (this.destroyed) return 45 | if (!this._opened) { 46 | this.once('open', onfinish) 47 | } else { 48 | if (this._lazy && this.initiator) this._open() 49 | this._multiplex._send(this.channel << 3 | (this.initiator ? 4 : 3), null) 50 | finished = true 51 | if (ended) this._finalize() 52 | } 53 | }) 54 | } 55 | 56 | inherits(Channel, stream.Duplex) 57 | 58 | Channel.prototype.destroy = function (err) { 59 | this._destroy(err, true) 60 | } 61 | 62 | Channel.prototype._destroy = function (err, local) { 63 | if (this.destroyed) return 64 | this.destroyed = true 65 | if (err && (!local || EventEmitter.listenerCount(this, 'error'))) this.emit('error', err) 66 | this.emit('close') 67 | if (local && this._opened) { 68 | if (this._lazy && this.initiator) this._open() 69 | try { 70 | this._multiplex._send(this.channel << 3 | (this.initiator ? 6 : 5), err ? new Buffer(err.message) : null) 71 | } catch (e) {} 72 | } 73 | this._finalize() 74 | } 75 | 76 | Channel.prototype._finalize = function () { 77 | if (this.finalized) return 78 | this.finalized = true 79 | this.emit('finalize') 80 | } 81 | 82 | Channel.prototype._write = function (data, enc, cb) { 83 | if (!this._opened) { 84 | this.once('open', this._write.bind(this, data, enc, cb)) 85 | return 86 | } 87 | if (this.destroyed) return cb() 88 | 89 | if (this._lazy && this.initiator) this._open() 90 | 91 | var drained = this._multiplex._send(this._dataHeader, data) 92 | if (drained) cb() 93 | else this._multiplex._ondrain.push(cb) 94 | } 95 | 96 | Channel.prototype._read = function () { 97 | if (this._awaitDrain) { 98 | var drained = this._awaitDrain 99 | this._awaitDrain = 0 100 | this._multiplex._onchanneldrain(drained) 101 | } 102 | } 103 | 104 | Channel.prototype._open = function () { 105 | var buf = null 106 | if (Buffer.isBuffer(this.name)) buf = this.name 107 | else if (this.name !== this.channel.toString()) buf = new Buffer(this.name) 108 | this._lazy = false 109 | this._multiplex._send(this.channel << 3 | 0, buf) 110 | } 111 | 112 | Channel.prototype.open = function (channel, initiator) { 113 | this.channel = channel 114 | this.initiator = initiator 115 | this._dataHeader = channel << 3 | (initiator ? 2 : 1) 116 | this._opened = true 117 | if (!this._lazy && this.initiator) this._open() 118 | this.emit('open') 119 | } 120 | 121 | var Multiplex = function (opts, onchannel) { 122 | if (!(this instanceof Multiplex)) return new Multiplex(opts, onchannel) 123 | stream.Duplex.call(this) 124 | 125 | if (typeof opts === 'function') { 126 | onchannel = opts 127 | opts = null 128 | } 129 | if (!opts) opts = {} 130 | if (onchannel) this.on('stream', onchannel) 131 | 132 | this.destroyed = false 133 | this.limit = opts.limit || 0 134 | 135 | this._corked = 0 136 | this._options = opts 137 | this._binaryName = !!opts.binaryName 138 | this._local = [] 139 | this._remote = [] 140 | this._list = this._local 141 | this._receiving = null 142 | this._chunked = false 143 | this._state = 0 144 | this._type = 0 145 | this._channel = 0 146 | this._missing = 0 147 | this._message = null 148 | this._buf = new Buffer(this.limit ? varint.encodingLength(this.limit) : 100) 149 | this._ptr = 0 150 | this._awaitChannelDrains = 0 151 | this._onwritedrain = null 152 | this._ondrain = [] 153 | this._finished = false 154 | 155 | this.on('finish', this._clear) 156 | } 157 | 158 | inherits(Multiplex, stream.Duplex) 159 | 160 | Multiplex.prototype.createStream = function (name, opts) { 161 | if (this.destroyed) throw new Error('Multiplexer is destroyed') 162 | var id = this._local.indexOf(null) 163 | if (id === -1) id = this._local.push(null) - 1 164 | var channel = new Channel(this._name(name || id.toString()), this, xtend(this._options, opts)) 165 | return this._addChannel(channel, id, this._local) 166 | } 167 | 168 | Multiplex.prototype.receiveStream = function (name, opts) { 169 | if (this.destroyed) throw new Error('Multiplexer is destroyed') 170 | if (name === undefined || name === null) throw new Error('Name is needed when receiving a stream') 171 | var channel = new Channel(this._name(name), this, xtend(this._options, opts)) 172 | if (!this._receiving) this._receiving = {} 173 | if (this._receiving[channel.name]) throw new Error('You are already receiving this stream') 174 | this._receiving[channel.name] = channel 175 | return channel 176 | } 177 | 178 | Multiplex.prototype.createSharedStream = function (name, opts) { 179 | return duplexify(this.createStream(name, xtend(opts, {lazy: true})), this.receiveStream(name, opts)) 180 | } 181 | 182 | Multiplex.prototype._name = function (name) { 183 | if (!this._binaryName) return name.toString() 184 | return Buffer.isBuffer(name) ? name : new Buffer(name) 185 | } 186 | 187 | Multiplex.prototype._send = function (header, data) { 188 | var len = data ? data.length : 0 189 | var oldUsed = used 190 | var drained = true 191 | 192 | varint.encode(header, pool, used) 193 | used += varint.encode.bytes 194 | varint.encode(len, pool, used) 195 | used += varint.encode.bytes 196 | 197 | drained = this.push(pool.slice(oldUsed, used)) 198 | 199 | if (pool.length - used < 100) { 200 | pool = new Buffer(10 * 1024) 201 | used = 0 202 | } 203 | 204 | if (data) drained = this.push(data) 205 | return drained 206 | } 207 | 208 | Multiplex.prototype._addChannel = function (channel, id, list) { 209 | while (list.length <= id) list.push(null) 210 | list[id] = channel 211 | channel.on('finalize', function () { 212 | list[id] = null 213 | }) 214 | 215 | channel.open(id, list === this._local) 216 | 217 | return channel 218 | } 219 | 220 | Multiplex.prototype._writeVarint = function (data, offset) { 221 | for (offset; offset < data.length; offset++) { 222 | if (this._ptr === this._buf.length) return this._lengthError(data) 223 | this._buf[this._ptr++] = data[offset] 224 | if (!(data[offset] & 0x80)) { 225 | if (this._state === 0) { 226 | var header = varint.decode(this._buf) 227 | this._type = header & 7 228 | this._channel = header >> 3 229 | this._list = this._type & 1 ? this._local : this._remote 230 | var chunked = this._list.length > this._channel && this._list[this._channel] && this._list[this._channel].chunked 231 | this._chunked = !!(this._type === 1 || this._type === 2) && chunked 232 | } else { 233 | this._missing = varint.decode(this._buf) 234 | if (this.limit && this._missing > this.limit) return this._lengthError(data) 235 | } 236 | this._state++ 237 | this._ptr = 0 238 | return offset + 1 239 | } 240 | } 241 | return data.length 242 | } 243 | 244 | Multiplex.prototype._lengthError = function (data) { 245 | this.destroy(new Error('Incoming message is too big')) 246 | return data.length 247 | } 248 | 249 | Multiplex.prototype._writeMessage = function (data, offset) { 250 | var free = data.length - offset 251 | var missing = this._missing 252 | 253 | if (!this._message) { 254 | if (missing <= free) { // fast track - no copy 255 | this._missing = 0 256 | this._push(data.slice(offset, offset + missing)) 257 | return offset + missing 258 | } 259 | if (this._chunked) { 260 | this._missing -= free 261 | this._push(data.slice(offset, data.length)) 262 | return data.length 263 | } 264 | this._message = new Buffer(missing) 265 | } 266 | 267 | data.copy(this._message, this._ptr, offset, offset + missing) 268 | 269 | if (missing <= free) { 270 | this._missing = 0 271 | this._push(this._message) 272 | return offset + missing 273 | } 274 | 275 | this._missing -= free 276 | this._ptr += free 277 | 278 | return data.length 279 | } 280 | 281 | Multiplex.prototype._push = function (data) { 282 | if (!this._missing) { 283 | this._ptr = 0 284 | this._state = 0 285 | this._message = null 286 | } 287 | 288 | if (this._type === 0) { // open 289 | if (this.destroyed || this._finished) return 290 | 291 | var name = this._binaryName ? data : (data.toString() || this._channel.toString()) 292 | var channel 293 | 294 | if (this._receiving && this._receiving[name]) { 295 | channel = this._receiving[name] 296 | delete this._receiving[name] 297 | this._addChannel(channel, this._channel, this._list) 298 | } else { 299 | channel = new Channel(name, this, this._options) 300 | this.emit('stream', this._addChannel(channel, this._channel, this._list), channel.name) 301 | } 302 | return 303 | } 304 | 305 | var stream = this._list[this._channel] 306 | if (!stream) return 307 | 308 | switch (this._type) { 309 | case 5: // local error 310 | case 6: // remote error 311 | stream._destroy(new Error(data.toString() || 'Channel destroyed'), false) 312 | return 313 | 314 | case 3: // local end 315 | case 4: // remote end 316 | stream.push(null) 317 | return 318 | 319 | case 1: // local packet 320 | case 2: // remote packet 321 | if (!stream.push(data)) { 322 | this._awaitChannelDrains++ 323 | stream._awaitDrain++ 324 | } 325 | return 326 | } 327 | } 328 | 329 | Multiplex.prototype._onchanneldrain = function (drained) { 330 | this._awaitChannelDrains -= drained 331 | if (this._awaitChannelDrains) return 332 | var ondrain = this._onwritedrain 333 | this._onwritedrain = null 334 | if (ondrain) ondrain() 335 | } 336 | 337 | Multiplex.prototype._write = function (data, enc, cb) { 338 | if (this._finished) return cb() 339 | if (this._corked) return this._onuncork(this._write.bind(this, data, enc, cb)) 340 | if (data === SIGNAL_FLUSH) return this._finish(cb) 341 | 342 | var offset = 0 343 | 344 | while (offset < data.length) { 345 | if (this._state === 2) offset = this._writeMessage(data, offset) 346 | else offset = this._writeVarint(data, offset) 347 | } 348 | if (this._state === 2 && !this._missing) this._push(empty) 349 | 350 | if (this._awaitChannelDrains) this._onwritedrain = cb 351 | else cb() 352 | } 353 | 354 | Multiplex.prototype._finish = function (cb) { 355 | var self = this 356 | this._onuncork(function () { 357 | if (self._writableState.prefinished === false) self._writableState.prefinished = true 358 | self.emit('prefinish') 359 | self._onuncork(cb) 360 | }) 361 | } 362 | 363 | Multiplex.prototype.cork = function () { 364 | if (++this._corked === 1) this.emit('cork') 365 | } 366 | 367 | Multiplex.prototype.uncork = function () { 368 | if (this._corked && --this._corked === 0) this.emit('uncork') 369 | } 370 | 371 | Multiplex.prototype.end = function (data, enc, cb) { 372 | if (typeof data === 'function') return this.end(null, null, data) 373 | if (typeof enc === 'function') return this.end(data, null, enc) 374 | if (data) this.write(data) 375 | if (!this._writableState.ending) this.write(SIGNAL_FLUSH) 376 | return stream.Writable.prototype.end.call(this, cb) 377 | } 378 | 379 | Multiplex.prototype._onuncork = function (fn) { 380 | if (this._corked) this.once('uncork', fn) 381 | else fn() 382 | } 383 | 384 | Multiplex.prototype._read = function () { 385 | while (this._ondrain.length) this._ondrain.shift()() 386 | } 387 | 388 | Multiplex.prototype._clear = function () { 389 | if (this._finished) return 390 | this._finished = true 391 | 392 | var list = this._local.concat(this._remote) 393 | 394 | this._local = [] 395 | this._remote = [] 396 | 397 | list.forEach(function (stream) { 398 | if (stream) stream._destroy(null, false) 399 | }) 400 | 401 | this.push(null) 402 | } 403 | 404 | Multiplex.prototype.finalize = function () { 405 | this._clear() 406 | } 407 | 408 | Multiplex.prototype.destroy = function (err) { 409 | if (this.destroyed) return 410 | this.destroyed = true 411 | this._clear() 412 | if (err) this.emit('error', err) 413 | this.emit('close') 414 | } 415 | 416 | module.exports = Multiplex 417 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multiplex", 3 | "version": "6.7.0", 4 | "description": "A binary stream multiplexer. Stream multiple streams of binary data over a single binary stream.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test.js" 8 | }, 9 | "contributors": [ 10 | "Max Ogden (https://github.com/maxogden)", 11 | "Ayman Mackouly (https://github.com/1N50MN14/)" 12 | ], 13 | "author": "max ogden", 14 | "license": "BSD 2-Clause", 15 | "dependencies": { 16 | "duplexify": "^3.4.2", 17 | "inherits": "^2.0.1", 18 | "readable-stream": "^2.0.2", 19 | "varint": "^4.0.0", 20 | "xtend": "^4.0.0" 21 | }, 22 | "devDependencies": { 23 | "chunky": "0.0.0", 24 | "concat-stream": "^1.4.8", 25 | "tape": "^4.0.0", 26 | "through2": "^0.6.5" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/maxogden/multiplex.git" 31 | }, 32 | "bugs": { 33 | "url": "https://github.com/maxogden/multiplex/issues" 34 | }, 35 | "homepage": "https://github.com/maxogden/multiplex" 36 | } 37 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # multiplex 2 | 3 | A binary stream multiplexer. Stream multiple streams of binary data over a single binary stream. Like [mux-demux](https://npmjs.org/package/mux-demux) but faster since it only works with binary streams. 4 | 5 | [![NPM](https://nodei.co/npm/multiplex.png)](https://nodei.co/npm/multiplex/) 6 | 7 | ## api 8 | 9 | ### `var multiplex = require('multiplex')([options], [onStream])` 10 | 11 | Returns a new multiplexer. You can use this to create sub-streams. All data written to sub-streams will be emitted through this. If you pipe a multiplex instance to another multiplex instance all substream data will be multiplexed and demultiplexed on the other end. 12 | 13 | `onStream` will be called with `(stream, id)` whenever a new remote sub-stream is created with an id that hasn't already been created with `.createStream`. 14 | 15 | Options include: 16 | 17 | * `opts.limit` - set the max allowed message size. default is no maximum 18 | 19 | Any other options set in `options` are used as defaults options when creating sub streams. 20 | 21 | ### `stream = multiplex.createStream([id], [options])` 22 | 23 | Creates a new sub-stream with an optional whole string `id` (default is the stream channel id). 24 | 25 | Sub-streams are duplex streams. 26 | 27 | Options include: 28 | 29 | * `opts.chunked` - enables chunked mode on all streams (message framing not guaranteed) 30 | * `opts.halfOpen` - make channels support half open mode meaning that they can be readable but not writable and vice versa 31 | 32 | ### `stream = multiplex.receiveStream(id, [options])` 33 | 34 | Explicitly receive an incoming stream. 35 | 36 | This is useful if you have a function that accepts an instance of multiplex 37 | and you want to receive a substream. 38 | 39 | ### `stream = multiplex.createSharedStream(id, [options])` 40 | 41 | Create a shared stream. If both ends create a shared stream with 42 | the same id, writing data on one end will emit the same data on the other end 43 | 44 | ## events 45 | 46 | ### `multiplex.on('error', function (err) {})` 47 | 48 | Emitted when the outer stream encounters invalid data 49 | 50 | ### `multiplex.on('stream', function (stream, id) {})` 51 | 52 | Emitted when a it a new stream arrives. 53 | 54 | ### `stream.on('error', function (err) {})` 55 | 56 | Emitted if the inner stream is destroyed with an error 57 | 58 | ### example 59 | 60 | ```js 61 | var multiplex = require('multiplex') 62 | var plex1 = multiplex() 63 | var stream1 = plex1.createStream() 64 | var stream2 = plex1.createStream() 65 | 66 | var plex2 = multiplex(function onStream(stream, id) { 67 | stream.on('data', function(c) { 68 | console.log('data', id, c.toString()) 69 | }) 70 | }) 71 | 72 | plex1.pipe(plex2) 73 | 74 | stream1.write(new Buffer('stream one!')) 75 | stream2.write(new Buffer('stream two!')) 76 | ``` 77 | 78 | ### contributing 79 | 80 | multiplex is an **OPEN Open Source Project**. This means that: 81 | 82 | > Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project. 83 | 84 | See the [CONTRIBUTING.md](contributing.md) file for more details. 85 | 86 | ### contributors 87 | 88 | multiplex is only possible due to the excellent work of the following contributors: 89 | 90 | 91 | 92 | 93 | 94 |
maxogdenGitHub/maxogden
1N50MN14GitHub/1N50MN14
substackGitHub/substack
mafintoshGitHub/mafintosh
95 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var concat = require('concat-stream') 3 | var through = require('through2') 4 | var multiplex = require('./') 5 | var net = require('net') 6 | var chunky = require('chunky') 7 | 8 | test('one way piping work with 2 sub-streams', function (t) { 9 | var plex1 = multiplex() 10 | var stream1 = plex1.createStream() 11 | var stream2 = plex1.createStream() 12 | 13 | var plex2 = multiplex(function onStream (stream, id) { 14 | stream.pipe(collect()) 15 | }) 16 | 17 | plex1.pipe(plex2) 18 | 19 | stream1.write(new Buffer('hello')) 20 | stream2.write(new Buffer('world')) 21 | stream1.end() 22 | stream2.end() 23 | 24 | var pending = 2 25 | var results = [] 26 | 27 | function collect () { 28 | return concat(function (data) { 29 | results.push(data.toString()) 30 | if (--pending === 0) { 31 | results.sort() 32 | t.equal(results[0].toString(), 'hello') 33 | t.equal(results[1].toString(), 'world') 34 | t.end() 35 | } 36 | }) 37 | } 38 | }) 39 | 40 | test('two way piping works with 2 sub-streams', function (t) { 41 | var plex1 = multiplex() 42 | 43 | var plex2 = multiplex(function onStream (stream, id) { 44 | var uppercaser = through(function (chunk, e, done) { 45 | this.push(new Buffer(chunk.toString().toUpperCase())) 46 | this.end() 47 | done() 48 | }) 49 | stream.pipe(uppercaser).pipe(stream) 50 | }) 51 | 52 | plex1.pipe(plex2).pipe(plex1) 53 | 54 | var stream1 = plex1.createStream() 55 | var stream2 = plex1.createStream() 56 | 57 | stream1.pipe(collect()) 58 | stream2.pipe(collect()) 59 | 60 | stream1.write(new Buffer('hello')) 61 | stream2.write(new Buffer('world')) 62 | 63 | var pending = 2 64 | var results = [] 65 | 66 | function collect () { 67 | return concat(function (data) { 68 | results.push(data.toString()) 69 | if (--pending === 0) { 70 | results.sort() 71 | t.equal(results[0].toString(), 'HELLO') 72 | t.equal(results[1].toString(), 'WORLD') 73 | t.end() 74 | } 75 | }) 76 | } 77 | }) 78 | 79 | test('stream id should be exposed as stream.name', function (t) { 80 | var plex1 = multiplex() 81 | var stream1 = plex1.createStream('5') 82 | t.equal(stream1.name, '5') 83 | 84 | var plex2 = multiplex(function onStream (stream, id) { 85 | t.equal(stream.name, '5') 86 | t.equal(id, '5') 87 | t.end() 88 | }) 89 | 90 | plex1.pipe(plex2) 91 | 92 | stream1.write(new Buffer('hello')) 93 | stream1.end() 94 | }) 95 | 96 | test('stream id can be a long string', function (t) { 97 | var plex1 = multiplex() 98 | var stream1 = plex1.createStream('hello-yes-this-is-dog') 99 | t.equal(stream1.name, 'hello-yes-this-is-dog') 100 | 101 | var plex2 = multiplex(function onStream (stream, id) { 102 | t.equal(stream.name, 'hello-yes-this-is-dog') 103 | t.equal(id, 'hello-yes-this-is-dog') 104 | t.end() 105 | }) 106 | 107 | plex1.pipe(plex2) 108 | 109 | stream1.write(new Buffer('hello')) 110 | stream1.end() 111 | }) 112 | 113 | test('destroy', function (t) { 114 | var plex1 = multiplex() 115 | var stream1 = plex1.createStream() 116 | 117 | var plex2 = multiplex(function onStream (stream, id) { 118 | stream.on('error', function (err) { 119 | t.equal(err.message, '0 had an error') 120 | t.end() 121 | }) 122 | }) 123 | 124 | plex1.pipe(plex2) 125 | 126 | stream1.write(new Buffer('hello')) 127 | stream1.destroy(new Error('0 had an error')) 128 | }) 129 | 130 | test('testing invalid data error', function (t) { 131 | var plex = multiplex() 132 | 133 | plex.on('error', function (err) { 134 | if (err) { 135 | t.equal(err.message, 'Incoming message is too big') 136 | t.end() 137 | } 138 | }) 139 | // a really stupid thing to do 140 | plex.write(Array(50000).join('\xff')) 141 | }) 142 | 143 | test('overflow', function (t) { 144 | var plex1 = multiplex() 145 | var plex2 = multiplex({limit: 10}) 146 | 147 | plex2.on('error', function (err) { 148 | if (err) { 149 | t.equal(err.message, 'Incoming message is too big') 150 | t.end() 151 | } 152 | }) 153 | 154 | plex1.pipe(plex2).pipe(plex1) 155 | plex1.createStream().write(new Buffer(11)) 156 | }) 157 | 158 | test('2 buffers packed into 1 chunk', function (t) { 159 | var plex1 = multiplex() 160 | var plex2 = multiplex(function (b) { 161 | b.pipe(concat(function (body) { 162 | t.equal(body.toString('utf8'), 'abc\n123\n') 163 | t.end() 164 | server.close() 165 | plex1.end() 166 | })) 167 | }) 168 | var a = plex1.createStream(1337) 169 | a.write('abc\n') 170 | a.write('123\n') 171 | a.end() 172 | 173 | var server = net.createServer(function (stream) { 174 | plex2.pipe(stream).pipe(plex2) 175 | }) 176 | server.listen(0, function () { 177 | var port = server.address().port 178 | plex1.pipe(net.connect(port)).pipe(plex1) 179 | }) 180 | }) 181 | 182 | test('chunks', function (t) { 183 | var times = 100 184 | ;(function chunk () { 185 | var collect = collector(function () { 186 | if (--times === 0) t.end() 187 | else chunk() 188 | }) 189 | var plex1 = multiplex() 190 | var stream1 = plex1.createStream() 191 | var stream2 = plex1.createStream() 192 | 193 | var plex2 = multiplex(function onStream (stream, id) { 194 | stream.pipe(collect()) 195 | }) 196 | 197 | plex1.pipe(through(function (buf, enc, next) { 198 | var bufs = chunky(buf) 199 | for (var i = 0; i < bufs.length; i++) this.push(bufs[i]) 200 | next() 201 | })).pipe(plex2) 202 | 203 | stream1.write(new Buffer('hello')) 204 | stream2.write(new Buffer('world')) 205 | stream1.end() 206 | stream2.end() 207 | })() 208 | 209 | function collector (cb) { 210 | var pending = 2 211 | var results = [] 212 | 213 | return function () { 214 | return concat(function (data) { 215 | results.push(data.toString()) 216 | if (--pending === 0) { 217 | results.sort() 218 | t.equal(results[0].toString(), 'hello') 219 | t.equal(results[1].toString(), 'world') 220 | cb() 221 | } 222 | }) 223 | } 224 | } 225 | }) 226 | 227 | test('prefinish + corking', function (t) { 228 | var plex = multiplex() 229 | var async = false 230 | 231 | plex.on('prefinish', function () { 232 | plex.cork() 233 | process.nextTick(function () { 234 | async = true 235 | plex.uncork() 236 | }) 237 | }) 238 | 239 | plex.on('finish', function () { 240 | t.ok(async, 'finished') 241 | t.end() 242 | }) 243 | 244 | plex.end() 245 | }) 246 | 247 | test('quick message', function (t) { 248 | var plex2 = multiplex() 249 | var plex1 = multiplex(function (stream) { 250 | stream.write('hello world') 251 | }) 252 | 253 | plex1.pipe(plex2).pipe(plex1) 254 | 255 | setTimeout(function () { 256 | var stream = plex2.createStream() 257 | stream.on('data', function (data) { 258 | t.same(data, new Buffer('hello world')) 259 | t.end() 260 | }) 261 | }, 100) 262 | }) 263 | 264 | test('if onstream is not passed, stream is emitted', function (t) { 265 | var plex1 = multiplex() 266 | var plex2 = multiplex() 267 | 268 | plex1.pipe(plex2).pipe(plex1) 269 | 270 | plex2.on('stream', function (stream, id) { 271 | t.ok(stream, 'received stream') 272 | t.ok(id, 'has id') 273 | stream.write('hello world') 274 | stream.end() 275 | }) 276 | 277 | var stream = plex1.createStream() 278 | stream.on('data', function (data) { 279 | t.same(data, new Buffer('hello world')) 280 | stream.end() 281 | t.end() 282 | }) 283 | }) 284 | --------------------------------------------------------------------------------