mirror of
https://github.com/fluencelabs/js-libp2p-interfaces
synced 2025-04-24 21:42:21 +00:00
Merge branch 'master' of https://github.com/libp2p/interface-stream-muxer into remote/connection
This commit is contained in:
commit
07d71de456
35
.gitignore
vendored
Normal file
35
.gitignore
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
**/node_modules/
|
||||||
|
**/*.log
|
||||||
|
test/repo-tests*
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
build
|
||||||
|
|
||||||
|
# Dependency directory
|
||||||
|
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
dist
|
||||||
|
package-lock.json
|
34
.npmignore
Normal file
34
.npmignore
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
**/node_modules/
|
||||||
|
**/*.log
|
||||||
|
test/repo-tests*
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
build
|
||||||
|
|
||||||
|
# Dependency directory
|
||||||
|
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
test
|
17
.travis.yml
Normal file
17
.travis.yml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
language: node_js
|
||||||
|
cache: npm
|
||||||
|
stages:
|
||||||
|
- check
|
||||||
|
|
||||||
|
node_js:
|
||||||
|
- '10'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
include:
|
||||||
|
- stage: check
|
||||||
|
script:
|
||||||
|
- npx aegir dep-check
|
||||||
|
- npm run lint
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email: false
|
173
CHANGELOG.md
Normal file
173
CHANGELOG.md
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
<a name="0.8.0"></a>
|
||||||
|
# [0.8.0](https://github.com/libp2p/interface-stream-muxer/compare/v0.7.0...v0.8.0) (2019-09-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add onStreamEnd, muxer.streams and timeline ([#56](https://github.com/libp2p/interface-stream-muxer/issues/56)) ([0f60832](https://github.com/libp2p/interface-stream-muxer/commit/0f60832))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* This adds new validations to the stream muxer, which will cause existing tests to fail.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.7.0"></a>
|
||||||
|
# [0.7.0](https://github.com/libp2p/interface-stream-muxer/compare/v0.6.0...v0.7.0) (2019-09-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Code Refactoring
|
||||||
|
|
||||||
|
* API changes and switch to async await ([#55](https://github.com/libp2p/interface-stream-muxer/issues/55)) ([dd837ba](https://github.com/libp2p/interface-stream-muxer/commit/dd837ba))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* the API is now async / await. See https://github.com/libp2p/interface-stream-muxer/pull/55#issue-275014779 for a summary of the changes.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.6.0"></a>
|
||||||
|
# [0.6.0](https://github.com/libp2p/interface-stream-muxer/compare/v0.5.9...v0.6.0) (2018-11-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* improve the close test ([d9c8681](https://github.com/libp2p/interface-stream-muxer/commit/d9c8681))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.5.9"></a>
|
||||||
|
## [0.5.9](https://github.com/libp2p/interface-stream-muxer/compare/v0.5.8...v0.5.9) (2017-12-14)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.5.8"></a>
|
||||||
|
## [0.5.8](https://github.com/libp2p/interface-stream-muxer/compare/v0.5.7...v0.5.8) (2017-12-14)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.5.7"></a>
|
||||||
|
## [0.5.7](https://github.com/libp2p/interface-stream-muxer/compare/v0.5.6...v0.5.7) (2017-03-16)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.5.6"></a>
|
||||||
|
## [0.5.6](https://github.com/libp2p/interface-stream-muxer/compare/v0.5.5...v0.5.6) (2017-02-09)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.5.5"></a>
|
||||||
|
## [0.5.5](https://github.com/libp2p/interface-stream-muxer/compare/v0.5.4...v0.5.5) (2017-01-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* when things are in the same process, there is a order to them :) ([1635977](https://github.com/libp2p/interface-stream-muxer/commit/1635977))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.5.4"></a>
|
||||||
|
## [0.5.4](https://github.com/libp2p/interface-stream-muxer/compare/v0.5.3...v0.5.4) (2017-01-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* avoid making webpacky funky by not trying to inject tcp ([6695b80](https://github.com/libp2p/interface-stream-muxer/commit/6695b80))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.5.3"></a>
|
||||||
|
## [0.5.3](https://github.com/libp2p/interface-stream-muxer/compare/v0.5.2...v0.5.3) (2017-01-19)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.5.2"></a>
|
||||||
|
## [0.5.2](https://github.com/libp2p/interface-stream-muxer/compare/v0.5.1...v0.5.2) (2017-01-19)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.5.1"></a>
|
||||||
|
## [0.5.1](https://github.com/libp2p/interface-stream-muxer/compare/v0.5.0...v0.5.1) (2017-01-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* some fixes for incorrect tests ([23a75d1](https://github.com/libp2p/interface-stream-muxer/commit/23a75d1))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.5.0"></a>
|
||||||
|
# [0.5.0](https://github.com/libp2p/interface-stream-muxer/compare/v0.4.0...v0.5.0) (2016-11-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* move to next aegir ([11980ac](https://github.com/libp2p/interface-stream-muxer/commit/11980ac))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.4.0"></a>
|
||||||
|
# [0.4.0](https://github.com/libp2p/interface-stream-muxer/compare/v0.3.1...v0.4.0) (2016-09-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **api:** update the interface usage from dial to dialer and listen to listener ([5069679](https://github.com/libp2p/interface-stream-muxer/commit/5069679))
|
||||||
|
* **pull:** migration to pull streams. Upgrade tests to use mocha as ([cc3130f](https://github.com/libp2p/interface-stream-muxer/commit/cc3130f))
|
||||||
|
* **tests:** add closing tests, make sure errors are propagated ([c06da3b](https://github.com/libp2p/interface-stream-muxer/commit/c06da3b))
|
||||||
|
* **tests:** stub test for aegir to verify ([949faf0](https://github.com/libp2p/interface-stream-muxer/commit/949faf0))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.3.1"></a>
|
||||||
|
## [0.3.1](https://github.com/libp2p/interface-stream-muxer/compare/v0.3.0...v0.3.1) (2016-03-06)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.3.0"></a>
|
||||||
|
# [0.3.0](https://github.com/libp2p/interface-stream-muxer/compare/v0.2.5...v0.3.0) (2016-03-06)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.2.5"></a>
|
||||||
|
## [0.2.5](https://github.com/libp2p/interface-stream-muxer/compare/v0.2.4...v0.2.5) (2015-12-12)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.2.4"></a>
|
||||||
|
## [0.2.4](https://github.com/libp2p/interface-stream-muxer/compare/v0.2.3...v0.2.4) (2015-07-22)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.2.3"></a>
|
||||||
|
## [0.2.3](https://github.com/libp2p/interface-stream-muxer/compare/v0.2.2...v0.2.3) (2015-07-15)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.2.2"></a>
|
||||||
|
## [0.2.2](https://github.com/libp2p/interface-stream-muxer/compare/v0.2.1...v0.2.2) (2015-07-15)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.2.1"></a>
|
||||||
|
## [0.2.1](https://github.com/libp2p/interface-stream-muxer/compare/v0.2.0...v0.2.1) (2015-07-14)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.2.0"></a>
|
||||||
|
# [0.2.0](https://github.com/libp2p/interface-stream-muxer/compare/v0.1.1...v0.2.0) (2015-07-14)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.1.1"></a>
|
||||||
|
## [0.1.1](https://github.com/libp2p/interface-stream-muxer/compare/v0.1.0...v0.1.1) (2015-07-14)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="0.1.0"></a>
|
||||||
|
# 0.1.0 (2015-07-13)
|
||||||
|
|
||||||
|
|
||||||
|
|
22
LICENSE
Normal file
22
LICENSE
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 David Dias
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
206
README.md
Normal file
206
README.md
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
# interface-stream-muxer
|
||||||
|
|
||||||
|
[](http://protocol.ai)
|
||||||
|
[](http://libp2p.io/)
|
||||||
|
[](http://webchat.freenode.net/?channels=%23libp2p)
|
||||||
|
[](https://discuss.libp2p.io)
|
||||||
|
[](https://travis-ci.com/libp2p/interface-stream-muxer)
|
||||||
|
[](https://david-dm.org/libp2p/interface-stream-muxer)
|
||||||
|
[](https://github.com/feross/standard)
|
||||||
|
|
||||||
|
> A test suite and interface you can use to implement a stream muxer. "A one stop shop for all your muxing needs"
|
||||||
|
|
||||||
|
The primary goal of this module is to enable developers to pick and swap their stream muxing module as they see fit for their application, without having to go through shims or compatibility issues. This module and test suite was heavily inspired by [abstract-blob-store](https://github.com/maxogden/abstract-blob-store).
|
||||||
|
|
||||||
|
Publishing a test suite as a module lets multiple modules all ensure compatibility since they use the same test suite.
|
||||||
|
|
||||||
|
The API is presented with both Node.js and Go primitives, however, there is no actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through different stacks.
|
||||||
|
|
||||||
|
## Lead Maintainer
|
||||||
|
|
||||||
|
[Jacob Heun](https://github.com/jacobheun/)
|
||||||
|
|
||||||
|
## Modules that implement the interface
|
||||||
|
|
||||||
|
- [JavaScript libp2p-spdy](https://github.com/libp2p/js-libp2p-spdy)
|
||||||
|
- [JavaScript libp2p-mplex](https://github.com/libp2p/js-libp2p-mplex)
|
||||||
|
- [Go spdy, muxado, yamux and multiplex](https://github.com/jbenet/go-stream-muxer)
|
||||||
|
|
||||||
|
Send a PR to add a new one if you happen to find or write one.
|
||||||
|
|
||||||
|
## Badge
|
||||||
|
|
||||||
|
Include this badge in your readme if you make a new module that uses interface-stream-muxer API.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### JS
|
||||||
|
|
||||||
|
Install `interface-stream-muxer` as one of the dependencies of your project and as a test file. Then, using `mocha` (for JavaScript) or a test runner with compatible API, do:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const test = require('interface-stream-muxer')
|
||||||
|
|
||||||
|
const common = {
|
||||||
|
async setup () {
|
||||||
|
return yourMuxer
|
||||||
|
},
|
||||||
|
async teardown () {
|
||||||
|
// cleanup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// use all of the test suits
|
||||||
|
test(common)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Go
|
||||||
|
|
||||||
|
> WIP
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### JS
|
||||||
|
|
||||||
|
A valid (one that follows this abstraction) stream muxer, must implement the following API:
|
||||||
|
|
||||||
|
#### `const muxer = new Muxer([options])`
|
||||||
|
|
||||||
|
Create a new _duplex_ stream that can be piped together with a connection in order to allow multiplexed communications.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const Muxer = require('your-muxer-module')
|
||||||
|
const pipe = require('it-pipe')
|
||||||
|
|
||||||
|
// Create a duplex muxer
|
||||||
|
const muxer = new Muxer()
|
||||||
|
|
||||||
|
// Use the muxer in a pipeline
|
||||||
|
pipe(conn, muxer, conn) // conn is duplex connection to another peer
|
||||||
|
```
|
||||||
|
|
||||||
|
`options` is an optional `Object` that may have the following properties:
|
||||||
|
|
||||||
|
* `onStream` - A function called when receiving a new stream from the remote. e.g.
|
||||||
|
```js
|
||||||
|
// Receive a new stream on the muxed connection
|
||||||
|
const onStream = stream => {
|
||||||
|
// Read from this stream and write back to it (echo server)
|
||||||
|
pipe(
|
||||||
|
stream,
|
||||||
|
source => (async function * () {
|
||||||
|
for await (const data of source) yield data
|
||||||
|
})()
|
||||||
|
stream
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const muxer = new Muxer({ onStream })
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
**Note:** The `onStream` function can be passed in place of the `options` object. i.e.
|
||||||
|
```js
|
||||||
|
new Mplex(stream => { /* ... */ })
|
||||||
|
```
|
||||||
|
* `onStreamEnd` - A function called when a stream ends.
|
||||||
|
```js
|
||||||
|
// Get notified when a stream has ended
|
||||||
|
const onStreamEnd = stream => {
|
||||||
|
// Manage any tracking changes, etc
|
||||||
|
}
|
||||||
|
const muxer = new Muxer({ onStreamEnd, ... })
|
||||||
|
```
|
||||||
|
* `signal` - An [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) which can be used to abort the muxer, _including_ all of it's multiplexed connections. e.g.
|
||||||
|
```js
|
||||||
|
const controller = new AbortController()
|
||||||
|
const muxer = new Muxer({ signal: controller.signal })
|
||||||
|
|
||||||
|
pipe(conn, muxer, conn)
|
||||||
|
|
||||||
|
controller.abort()
|
||||||
|
```
|
||||||
|
* `maxMsgSize` - The maximum size in bytes the data field of multiplexed messages may contain (default 1MB)
|
||||||
|
|
||||||
|
#### `muxer.onStream`
|
||||||
|
|
||||||
|
Use this property as an alternative to passing `onStream` as an option to the `Muxer` constructor.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const muxer = new Muxer()
|
||||||
|
// ...later
|
||||||
|
muxer.onStream = stream => { /* ... */ }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `muxer.onStreamEnd`
|
||||||
|
|
||||||
|
Use this property as an alternative to passing `onStreamEnd` as an option to the `Muxer` constructor.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const muxer = new Muxer()
|
||||||
|
// ...later
|
||||||
|
muxer.onStreamEnd = stream => { /* ... */ }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `const stream = muxer.newStream([options])`
|
||||||
|
|
||||||
|
Initiate a new stream with the remote. Returns a [duplex stream](https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#duplex-it).
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Create a new stream on the muxed connection
|
||||||
|
const stream = muxer.newStream()
|
||||||
|
|
||||||
|
// Use this new stream like any other duplex stream:
|
||||||
|
pipe([1, 2, 3], stream, consume)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `const streams = muxer.streams`
|
||||||
|
|
||||||
|
The streams property returns an array of streams the muxer currently has open. Closed streams will not be returned.
|
||||||
|
|
||||||
|
```js
|
||||||
|
muxer.streams.map(stream => {
|
||||||
|
// Log out the stream's id
|
||||||
|
console.log(stream.id)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Go
|
||||||
|
|
||||||
|
#### Attach muxer to a Connection
|
||||||
|
|
||||||
|
```go
|
||||||
|
muxedConn, err := muxer.Attach(conn, isListener)
|
||||||
|
```
|
||||||
|
|
||||||
|
This method attaches our stream muxer to an instance of [Connection](https://github.com/libp2p/interface-connection/blob/master/src/connection.js) defined by [interface-connection](https://github.com/libp2p/interface-connection).
|
||||||
|
|
||||||
|
If `err` is passed, no operation should be made in `conn`.
|
||||||
|
|
||||||
|
`isListener` is a bool that tells the side of the socket we are, `isListener = true` for listener/server and `isListener = false` for dialer/client side.
|
||||||
|
|
||||||
|
`muxedConn` interfaces our established Connection with the other endpoint, it must offer an interface to open a stream inside this connection and to receive incomming stream requests.
|
||||||
|
|
||||||
|
#### Dial(open/create) a new stream
|
||||||
|
|
||||||
|
```go
|
||||||
|
stream, err := muxedConn.newStream()
|
||||||
|
```
|
||||||
|
|
||||||
|
This method negotiates and opens a new stream with the other endpoint.
|
||||||
|
|
||||||
|
If `err` is passed, no operation should be made in `stream`.
|
||||||
|
|
||||||
|
`stream` interface our established Stream with the other endpoint, it must implement the [ReadWriteCloser](http://golang.org/pkg/io/#ReadWriteCloser).
|
||||||
|
|
||||||
|
#### Listen(wait/accept) a new incoming stream
|
||||||
|
|
||||||
|
```go
|
||||||
|
stream := muxedConn.Accept()
|
||||||
|
```
|
||||||
|
|
||||||
|
Each time a dialing peer initiates the new stream handshake, a new stream is created on the listening side.
|
BIN
img/badge.png
Normal file
BIN
img/badge.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.5 KiB |
BIN
img/badge.sketch
Normal file
BIN
img/badge.sketch
Normal file
Binary file not shown.
18
img/badge.svg
Normal file
18
img/badge.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 20 KiB |
63
package.json
Normal file
63
package.json
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"name": "interface-stream-muxer",
|
||||||
|
"version": "0.8.0",
|
||||||
|
"description": "A test suite and interface you can use to implement a stream muxer.",
|
||||||
|
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>",
|
||||||
|
"main": "src/index.js",
|
||||||
|
"browser": {
|
||||||
|
"libp2p-tcp": false
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "node -e 'process.exit()'",
|
||||||
|
"test:node": "node -e 'process.exit()'",
|
||||||
|
"build": "aegir build -t node",
|
||||||
|
"lint": "aegir lint",
|
||||||
|
"release": "aegir release -t node",
|
||||||
|
"release-minor": "aegir release -t node --type minor",
|
||||||
|
"release-major": "aegir release -t node --type major",
|
||||||
|
"coverage": "node -e 'process.exit()'",
|
||||||
|
"coverage-publish": "node -e 'process.exit()'"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/libp2p/interface-stream-muxer.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"Streams",
|
||||||
|
"Muxer",
|
||||||
|
"Interface"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/libp2p/interface-stream-muxer/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/libp2p/interface-stream-muxer",
|
||||||
|
"dependencies": {
|
||||||
|
"abort-controller": "^3.0.0",
|
||||||
|
"abortable-iterator": "^2.1.0",
|
||||||
|
"chai": "^4.2.0",
|
||||||
|
"chai-checkmark": "^1.0.1",
|
||||||
|
"detect-node": "^2.0.4",
|
||||||
|
"it-pair": "^1.0.0",
|
||||||
|
"it-pipe": "^1.0.1",
|
||||||
|
"libp2p-tcp": "^0.14.0",
|
||||||
|
"multiaddr": "^7.1.0",
|
||||||
|
"p-limit": "^2.2.0",
|
||||||
|
"streaming-iterables": "^4.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"aegir": "^20.0.0"
|
||||||
|
},
|
||||||
|
"contributors": [
|
||||||
|
"Alan Shaw <alan.shaw@protocol.ai>",
|
||||||
|
"David Dias <daviddias.p@gmail.com>",
|
||||||
|
"David Dias <mail@daviddias.me>",
|
||||||
|
"Dmitriy Ryajov <dryajov@gmail.com>",
|
||||||
|
"Friedel Ziegelmayer <dignifiedquire@gmail.com>",
|
||||||
|
"Jacob Heun <jacobheun@gmail.com>",
|
||||||
|
"Jeffrey Hulten <jhulten@gmail.com>",
|
||||||
|
"Vasco Santos <vasco.santos@moxy.studio>",
|
||||||
|
"greenkeeperio-bot <support@greenkeeper.io>",
|
||||||
|
"ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>"
|
||||||
|
]
|
||||||
|
}
|
153
src/base-test.js
Normal file
153
src/base-test.js
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/* eslint-env mocha */
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const chai = require('chai')
|
||||||
|
chai.use(require('chai-checkmark'))
|
||||||
|
const { expect } = chai
|
||||||
|
const pair = require('it-pair/duplex')
|
||||||
|
const pipe = require('it-pipe')
|
||||||
|
const { collect, map, consume } = require('streaming-iterables')
|
||||||
|
|
||||||
|
function close (stream) {
|
||||||
|
return pipe([], stream, consume)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function closeAndWait (stream) {
|
||||||
|
await close(stream)
|
||||||
|
expect(true).to.be.true.mark()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tick is considered valid if it happened between now
|
||||||
|
* and `ms` milliseconds ago
|
||||||
|
* @param {number} date Time in ticks
|
||||||
|
* @param {number} ms max milliseconds that should have expired
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function isValidTick (date, ms = 5000) {
|
||||||
|
const now = Date.now()
|
||||||
|
if (date > now - ms && date <= now) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = (common) => {
|
||||||
|
describe('base', () => {
|
||||||
|
let Muxer
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
Muxer = await common.setup()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Open a stream from the dialer', (done) => {
|
||||||
|
const p = pair()
|
||||||
|
const dialer = new Muxer()
|
||||||
|
|
||||||
|
const listener = new Muxer({
|
||||||
|
onStream: stream => {
|
||||||
|
expect(stream).to.exist.mark() // 1st check
|
||||||
|
expect(isValidTick(stream.timeline.open)).to.equal(true)
|
||||||
|
// Make sure the stream is being tracked
|
||||||
|
expect(listener.streams).to.include(stream)
|
||||||
|
close(stream)
|
||||||
|
},
|
||||||
|
onStreamEnd: stream => {
|
||||||
|
expect(stream).to.exist.mark() // 2nd check
|
||||||
|
expect(listener.streams).to.not.include(stream)
|
||||||
|
// Make sure the stream is removed from tracking
|
||||||
|
expect(isValidTick(stream.timeline.close)).to.equal(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
pipe(p[0], dialer, p[0])
|
||||||
|
pipe(p[1], listener, p[1])
|
||||||
|
|
||||||
|
expect(3).checks(() => {
|
||||||
|
// ensure we have no streams left
|
||||||
|
expect(dialer.streams).to.have.length(0)
|
||||||
|
expect(listener.streams).to.have.length(0)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
|
||||||
|
const conn = dialer.newStream()
|
||||||
|
expect(dialer.streams).to.include(conn)
|
||||||
|
expect(isValidTick(conn.timeline.open)).to.equal(true)
|
||||||
|
|
||||||
|
closeAndWait(conn) // 3rd check
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Open a stream from the listener', (done) => {
|
||||||
|
const p = pair()
|
||||||
|
const dialer = new Muxer(stream => {
|
||||||
|
expect(stream).to.exist.mark()
|
||||||
|
expect(isValidTick(stream.timeline.open)).to.equal(true)
|
||||||
|
closeAndWait(stream)
|
||||||
|
})
|
||||||
|
const listener = new Muxer()
|
||||||
|
|
||||||
|
pipe(p[0], dialer, p[0])
|
||||||
|
pipe(p[1], listener, p[1])
|
||||||
|
|
||||||
|
expect(3).check(done)
|
||||||
|
|
||||||
|
const conn = listener.newStream()
|
||||||
|
expect(listener.streams).to.include(conn)
|
||||||
|
expect(isValidTick(conn.timeline.open)).to.equal(true)
|
||||||
|
|
||||||
|
closeAndWait(conn)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Open a stream on both sides', (done) => {
|
||||||
|
const p = pair()
|
||||||
|
const dialer = new Muxer(stream => {
|
||||||
|
expect(stream).to.exist.mark()
|
||||||
|
closeAndWait(stream)
|
||||||
|
})
|
||||||
|
const listener = new Muxer(stream => {
|
||||||
|
expect(stream).to.exist.mark()
|
||||||
|
closeAndWait(stream)
|
||||||
|
})
|
||||||
|
|
||||||
|
pipe(p[0], dialer, p[0])
|
||||||
|
pipe(p[1], listener, p[1])
|
||||||
|
|
||||||
|
expect(6).check(done)
|
||||||
|
|
||||||
|
const listenerConn = listener.newStream()
|
||||||
|
const dialerConn = dialer.newStream()
|
||||||
|
|
||||||
|
closeAndWait(dialerConn)
|
||||||
|
closeAndWait(listenerConn)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Open a stream on one side, write, open a stream on the other side', (done) => {
|
||||||
|
const toString = map(c => c.slice().toString())
|
||||||
|
const p = pair()
|
||||||
|
const dialer = new Muxer()
|
||||||
|
const listener = new Muxer(stream => {
|
||||||
|
pipe(stream, toString, collect).then(chunks => {
|
||||||
|
expect(chunks).to.be.eql(['hey']).mark()
|
||||||
|
})
|
||||||
|
|
||||||
|
dialer.onStream = onDialerStream
|
||||||
|
|
||||||
|
const listenerConn = listener.newStream()
|
||||||
|
|
||||||
|
pipe(['hello'], listenerConn)
|
||||||
|
|
||||||
|
async function onDialerStream (stream) {
|
||||||
|
const chunks = await pipe(stream, toString, collect)
|
||||||
|
expect(chunks).to.be.eql(['hello']).mark()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
pipe(p[0], dialer, p[0])
|
||||||
|
pipe(p[1], listener, p[1])
|
||||||
|
|
||||||
|
expect(2).check(done)
|
||||||
|
|
||||||
|
const dialerConn = dialer.newStream()
|
||||||
|
|
||||||
|
pipe(['hey'], dialerConn)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
118
src/close-test.js
Normal file
118
src/close-test.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/* eslint-env mocha */
|
||||||
|
/* eslint max-nested-callbacks: ["error", 8] */
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const pair = require('it-pair/duplex')
|
||||||
|
const pipe = require('it-pipe')
|
||||||
|
const { consume } = require('streaming-iterables')
|
||||||
|
const Tcp = require('libp2p-tcp')
|
||||||
|
const multiaddr = require('multiaddr')
|
||||||
|
const abortable = require('abortable-iterator')
|
||||||
|
const AbortController = require('abort-controller')
|
||||||
|
|
||||||
|
const mh = multiaddr('/ip4/127.0.0.1/tcp/0')
|
||||||
|
|
||||||
|
function pause (ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomBuffer () {
|
||||||
|
return Buffer.from(Math.random().toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
const infiniteRandom = {
|
||||||
|
[Symbol.asyncIterator]: async function * () {
|
||||||
|
while (true) {
|
||||||
|
yield randomBuffer()
|
||||||
|
await pause(10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = (common) => {
|
||||||
|
describe('close', () => {
|
||||||
|
let Muxer
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
Muxer = await common.setup()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('closing underlying socket closes streams (tcp)', async () => {
|
||||||
|
const mockConn = muxer => ({
|
||||||
|
newStream: (...args) => muxer.newStream(...args)
|
||||||
|
})
|
||||||
|
|
||||||
|
const mockUpgrade = () => maConn => {
|
||||||
|
const muxer = new Muxer(stream => pipe(stream, stream))
|
||||||
|
pipe(maConn, muxer, maConn)
|
||||||
|
return mockConn(muxer)
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockUpgrader = () => ({
|
||||||
|
upgradeInbound: mockUpgrade(),
|
||||||
|
upgradeOutbound: mockUpgrade()
|
||||||
|
})
|
||||||
|
|
||||||
|
const tcp = new Tcp({ upgrader: mockUpgrader() })
|
||||||
|
const tcpListener = tcp.createListener()
|
||||||
|
|
||||||
|
await tcpListener.listen(mh)
|
||||||
|
const dialerConn = await tcp.dial(tcpListener.getAddrs()[0])
|
||||||
|
|
||||||
|
const s1 = await dialerConn.newStream()
|
||||||
|
const s2 = await dialerConn.newStream()
|
||||||
|
|
||||||
|
// close the listener in a bit
|
||||||
|
setTimeout(() => tcpListener.close(), 50)
|
||||||
|
|
||||||
|
const s1Result = pipe(infiniteRandom, s1, consume)
|
||||||
|
const s2Result = pipe(infiniteRandom, s2, consume)
|
||||||
|
|
||||||
|
// test is complete when all muxed streams have closed
|
||||||
|
await s1Result
|
||||||
|
await s2Result
|
||||||
|
})
|
||||||
|
|
||||||
|
it('closing one of the muxed streams doesn\'t close others', async () => {
|
||||||
|
const p = pair()
|
||||||
|
const dialer = new Muxer()
|
||||||
|
|
||||||
|
// Listener is echo server :)
|
||||||
|
const listener = new Muxer(stream => pipe(stream, stream))
|
||||||
|
|
||||||
|
pipe(p[0], dialer, p[0])
|
||||||
|
pipe(p[1], listener, p[1])
|
||||||
|
|
||||||
|
const stream = dialer.newStream()
|
||||||
|
const streams = Array.from(Array(5), () => dialer.newStream())
|
||||||
|
let closed = false
|
||||||
|
const controllers = []
|
||||||
|
|
||||||
|
const streamResults = streams.map(async stream => {
|
||||||
|
const controller = new AbortController()
|
||||||
|
controllers.push(controller)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const abortableRand = abortable(infiniteRandom, controller.signal, { abortCode: 'ERR_TEST_ABORT' })
|
||||||
|
await pipe(abortableRand, stream, consume)
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code !== 'ERR_TEST_ABORT') throw err
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!closed) throw new Error('stream should not have ended yet!')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Pause, and then send some data and close the first stream
|
||||||
|
await pause(50)
|
||||||
|
await pipe([randomBuffer()], stream, consume)
|
||||||
|
closed = true
|
||||||
|
|
||||||
|
// Abort all the other streams later
|
||||||
|
await pause(50)
|
||||||
|
controllers.forEach(c => c.abort())
|
||||||
|
|
||||||
|
// These should now all resolve without error
|
||||||
|
await Promise.all(streamResults)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
19
src/index.js
Normal file
19
src/index.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/* eslint-env mocha */
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const baseTest = require('./base-test')
|
||||||
|
const stressTest = require('./stress-test')
|
||||||
|
const megaStressTest = require('./mega-stress-test')
|
||||||
|
const isNode = require('detect-node')
|
||||||
|
|
||||||
|
module.exports = (common) => {
|
||||||
|
describe('interface-stream-muxer', () => {
|
||||||
|
baseTest(common)
|
||||||
|
if (isNode) {
|
||||||
|
const closeTest = require('./close-test')
|
||||||
|
closeTest(common)
|
||||||
|
}
|
||||||
|
stressTest(common)
|
||||||
|
megaStressTest(common)
|
||||||
|
})
|
||||||
|
}
|
17
src/mega-stress-test.js
Normal file
17
src/mega-stress-test.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/* eslint-env mocha */
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const spawn = require('./spawner')
|
||||||
|
|
||||||
|
module.exports = (common) => {
|
||||||
|
describe.skip('mega stress test', function () {
|
||||||
|
this.timeout(100 * 200 * 1000)
|
||||||
|
let Muxer
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
Muxer = await common.setup()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('10,000 streams with 10,000 msg', () => spawn(Muxer, 10000, 10000, 5000))
|
||||||
|
})
|
||||||
|
}
|
82
src/spawner.js
Normal file
82
src/spawner.js
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const { expect } = require('chai')
|
||||||
|
const pair = require('it-pair/duplex')
|
||||||
|
const pipe = require('it-pipe')
|
||||||
|
const pLimit = require('p-limit')
|
||||||
|
const { collect, tap, consume } = require('streaming-iterables')
|
||||||
|
|
||||||
|
module.exports = async (Muxer, nStreams, nMsg, limit) => {
|
||||||
|
const [dialerSocket, listenerSocket] = pair()
|
||||||
|
const { check, done } = marker((4 * nStreams) + (nStreams * nMsg))
|
||||||
|
|
||||||
|
const msg = 'simple msg'
|
||||||
|
|
||||||
|
const listener = new Muxer(async stream => {
|
||||||
|
expect(stream).to.exist // eslint-disable-line
|
||||||
|
check()
|
||||||
|
|
||||||
|
await pipe(
|
||||||
|
stream,
|
||||||
|
tap(chunk => check()),
|
||||||
|
consume
|
||||||
|
)
|
||||||
|
|
||||||
|
check()
|
||||||
|
pipe([], stream)
|
||||||
|
})
|
||||||
|
|
||||||
|
const dialer = new Muxer()
|
||||||
|
|
||||||
|
pipe(listenerSocket, listener, listenerSocket)
|
||||||
|
pipe(dialerSocket, dialer, dialerSocket)
|
||||||
|
|
||||||
|
const spawnStream = async n => {
|
||||||
|
const stream = dialer.newStream()
|
||||||
|
expect(stream).to.exist // eslint-disable-line
|
||||||
|
check()
|
||||||
|
|
||||||
|
const res = await pipe(
|
||||||
|
(function * () {
|
||||||
|
for (let i = 0; i < nMsg; i++) {
|
||||||
|
// console.log('n', n, 'msg', i)
|
||||||
|
yield new Promise(resolve => resolve(msg))
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
stream,
|
||||||
|
collect
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(res).to.be.eql([])
|
||||||
|
check()
|
||||||
|
}
|
||||||
|
|
||||||
|
const limiter = pLimit(limit || Infinity)
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
Array.from(Array(nStreams), (_, i) => limiter(() => spawnStream(i)))
|
||||||
|
)
|
||||||
|
|
||||||
|
return done
|
||||||
|
}
|
||||||
|
|
||||||
|
function marker (n) {
|
||||||
|
let check
|
||||||
|
let i = 0
|
||||||
|
const done = new Promise((resolve, reject) => {
|
||||||
|
check = err => {
|
||||||
|
i++
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
console.error('Failed after %s iterations', i)
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i === n) {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return { check, done }
|
||||||
|
}
|
30
src/stress-test.js
Normal file
30
src/stress-test.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/* eslint-env mocha */
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const spawn = require('./spawner')
|
||||||
|
|
||||||
|
module.exports = (common) => {
|
||||||
|
describe('stress test', () => {
|
||||||
|
let Muxer
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
Muxer = await common.setup()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('1 stream with 1 msg', () => spawn(Muxer, 1, 1))
|
||||||
|
it('1 stream with 10 msg', () => spawn(Muxer, 1, 10))
|
||||||
|
it('1 stream with 100 msg', () => spawn(Muxer, 1, 100))
|
||||||
|
it('10 streams with 1 msg', () => spawn(Muxer, 10, 1))
|
||||||
|
it('10 streams with 10 msg', () => spawn(Muxer, 10, 10))
|
||||||
|
it('10 streams with 100 msg', () => spawn(Muxer, 10, 100))
|
||||||
|
it('100 streams with 1 msg', () => spawn(Muxer, 100, 1))
|
||||||
|
it('100 streams with 10 msg', () => spawn(Muxer, 100, 10))
|
||||||
|
it('100 streams with 100 msg', () => spawn(Muxer, 100, 100))
|
||||||
|
it('1000 streams with 1 msg', () => spawn(Muxer, 1000, 1))
|
||||||
|
it('1000 streams with 10 msg', () => spawn(Muxer, 1000, 10))
|
||||||
|
it('1000 streams with 100 msg', function () {
|
||||||
|
this.timeout(30 * 1000)
|
||||||
|
return spawn(Muxer, 1000, 100)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
1
test/stream-muxer.spec.js
Normal file
1
test/stream-muxer.spec.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
'use strict'
|
Loading…
x
Reference in New Issue
Block a user