├── .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 |
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 | [](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 |
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 |
--------------------------------------------------------------------------------