refactor: move all files to the right place
30
src/connection/.gitignore
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# 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
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# 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
|
29
src/connection/.npmignore
Normal file
@ -0,0 +1,29 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# 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
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
node_modules
|
||||
|
||||
test
|
40
src/connection/.travis.yml
Normal file
@ -0,0 +1,40 @@
|
||||
language: node_js
|
||||
cache: npm
|
||||
stages:
|
||||
- check
|
||||
- test
|
||||
- cov
|
||||
|
||||
node_js:
|
||||
- '10'
|
||||
- '12'
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
- windows
|
||||
|
||||
script: npx nyc -s npm run test:node -- --bail
|
||||
after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: check
|
||||
script:
|
||||
- npx aegir dep-check
|
||||
- npm run lint
|
||||
|
||||
- stage: test
|
||||
name: chrome
|
||||
addons:
|
||||
chrome: stable
|
||||
script: npx aegir test -t browser -t webworker
|
||||
|
||||
- stage: test
|
||||
name: firefox
|
||||
addons:
|
||||
firefox: latest
|
||||
script: npx aegir test -t browser -t webworker -- --browsers FirefoxHeadless
|
||||
|
||||
notifications:
|
||||
email: false
|
115
src/connection/CHANGELOG.md
Normal file
@ -0,0 +1,115 @@
|
||||
<a name="0.4.1"></a>
|
||||
## [0.4.1](https://github.com/libp2p/interface-connection/compare/v0.4.0...v0.4.1) (2019-10-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for timeline proxying ([#31](https://github.com/libp2p/interface-connection/issues/31)) ([541bf83](https://github.com/libp2p/interface-connection/commit/541bf83))
|
||||
|
||||
|
||||
|
||||
<a name="0.4.0"></a>
|
||||
# [0.4.0](https://github.com/libp2p/interface-connection/compare/v0.3.3...v0.4.0) (2019-09-27)
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* API changes and switch to async iterators ([#29](https://github.com/libp2p/interface-connection/issues/29)) ([bf5c646](https://github.com/libp2p/interface-connection/commit/bf5c646))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* all the callbacks in the provided API were removed and each function uses async/await. Additionally, pull-streams are no longer being used. See the README for new usage.
|
||||
|
||||
|
||||
|
||||
<a name="0.3.3"></a>
|
||||
## [0.3.3](https://github.com/libp2p/interface-connection/compare/v0.3.1...v0.3.3) (2018-11-29)
|
||||
|
||||
|
||||
|
||||
<a name="0.3.1"></a>
|
||||
## [0.3.1](https://github.com/libp2p/interface-connection/compare/v0.3.0...v0.3.1) (2017-02-09)
|
||||
|
||||
|
||||
|
||||
<a name="0.3.0"></a>
|
||||
# [0.3.0](https://github.com/libp2p/interface-connection/compare/v0.2.1...v0.3.0) (2016-11-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* async crypto + sauce labs + aegir 9 ([b40114c](https://github.com/libp2p/interface-connection/commit/b40114c))
|
||||
|
||||
|
||||
|
||||
<a name="0.2.1"></a>
|
||||
## [0.2.1](https://github.com/libp2p/interface-connection/compare/v0.2.0...v0.2.1) (2016-09-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **package.json:** point to right main ([84cd2ca](https://github.com/libp2p/interface-connection/commit/84cd2ca))
|
||||
|
||||
|
||||
|
||||
<a name="0.2.0"></a>
|
||||
# [0.2.0](https://github.com/libp2p/interface-connection/compare/v0.1.8...v0.2.0) (2016-09-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** fix package.json ([e0f7db3](https://github.com/libp2p/interface-connection/commit/e0f7db3))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **connection:** migrate to pull-streams ([ed5727a](https://github.com/libp2p/interface-connection/commit/ed5727a))
|
||||
|
||||
|
||||
|
||||
<a name="0.1.8"></a>
|
||||
## [0.1.8](https://github.com/libp2p/interface-connection/compare/v0.1.7...v0.1.8) (2016-08-03)
|
||||
|
||||
|
||||
|
||||
<a name="0.1.7"></a>
|
||||
## [0.1.7](https://github.com/libp2p/interface-connection/compare/v0.1.6...v0.1.7) (2016-06-27)
|
||||
|
||||
|
||||
|
||||
<a name="0.1.6"></a>
|
||||
## [0.1.6](https://github.com/libp2p/interface-connection/compare/v0.1.5...v0.1.6) (2016-06-23)
|
||||
|
||||
|
||||
|
||||
<a name="0.1.5"></a>
|
||||
## [0.1.5](https://github.com/libp2p/interface-connection/compare/v0.1.4...v0.1.5) (2016-06-23)
|
||||
|
||||
|
||||
|
||||
<a name="0.1.4"></a>
|
||||
## [0.1.4](https://github.com/libp2p/interface-connection/compare/v0.1.3...v0.1.4) (2016-06-23)
|
||||
|
||||
|
||||
|
||||
<a name="0.1.3"></a>
|
||||
## [0.1.3](https://github.com/libp2p/interface-connection/compare/v0.1.0...v0.1.3) (2016-06-16)
|
||||
|
||||
|
||||
|
||||
<a name="0.1.0"></a>
|
||||
# [0.1.0](https://github.com/libp2p/interface-connection/compare/v0.0.3...v0.1.0) (2016-06-16)
|
||||
|
||||
|
||||
|
||||
<a name="0.0.3"></a>
|
||||
## [0.0.3](https://github.com/libp2p/interface-connection/compare/v0.0.1...v0.0.3) (2015-12-12)
|
||||
|
||||
|
||||
|
||||
<a name="0.0.1"></a>
|
||||
## 0.0.1 (2015-09-17)
|
||||
|
||||
|
||||
|
22
src/connection/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.
|
||||
|
262
src/connection/README.md
Normal file
@ -0,0 +1,262 @@
|
||||
interface-connection
|
||||
==================
|
||||
|
||||
[](http://protocol.ai)
|
||||
[](http://libp2p.io/)
|
||||
[](http://webchat.freenode.net/?channels=%23libp2p)
|
||||
[](https://discuss.libp2p.io)
|
||||
[](https://codecov.io/gh/libp2p/interface-connection)
|
||||
[](https://travis-ci.com/libp2p/interface-connection)
|
||||
[](https://david-dm.org/libp2p/interface-connection)
|
||||
[](https://github.com/feross/standard)
|
||||
|
||||
This is a test suite and interface you can use to implement a connection. The connection interface contains all the metadata associated with it, as well as an array of the streams opened through this connection. In the same way as the connection, a stream contains properties with its metadata, plus an iterable duplex object that offers a mechanism for writing and reading data, with back pressure. This module and test suite were heavily inspired by abstract-blob-store and interface-stream-muxer.
|
||||
|
||||
The primary goal of this module is to enable developers to pick, swap or upgrade their connection without losing the same API expectations and mechanisms such as back pressure and the ability to half close a connection.
|
||||
|
||||
Publishing a test suite as a module lets multiple modules ensure compatibility since they use the same test suite.
|
||||
|
||||
## Lead Maintainer
|
||||
|
||||
[Jacob Heun](https://github.com/jacobheun/)
|
||||
|
||||
## Usage
|
||||
|
||||
### Connection
|
||||
|
||||
Before creating a connection from a transport compatible with `libp2p` it is important to understand some concepts:
|
||||
|
||||
- **socket**: the underlying raw duplex connection between two nodes. It is created by the transports during a dial/listen.
|
||||
- **[multiaddr connection](https://github.com/libp2p/interface-transport#multiaddrconnection)**: an abstraction over the socket to allow it to work with multiaddr addresses. It is a duplex connection that transports create to wrap the socket before passing to an upgrader that turns it into a standard connection (see below).
|
||||
- **connection**: a connection between two _peers_ that has built in multiplexing and info about the connected peer. It is created from a [multiaddr connection](https://github.com/libp2p/interface-transport#multiaddrconnection) by an upgrader. The upgrader uses multistream-select to add secio and multiplexing and returns this object.
|
||||
- **stream**: a muxed duplex channel of the `connection`. Each connection may have many streams.
|
||||
|
||||
A connection stands for the libp2p communication duplex layer between two nodes. It is **not** the underlying raw transport duplex layer (socket), such as a TCP socket, but an abstracted layer that sits on top of the raw socket.
|
||||
|
||||
This helps ensuring that the transport is responsible for socket management, while also allowing the application layer to handle the connection management.
|
||||
|
||||
### Test suite
|
||||
|
||||
#### JS
|
||||
|
||||
```js
|
||||
describe('your connection', () => {
|
||||
require('interface-connection/src/tests')({
|
||||
async setup () {
|
||||
return YourConnection
|
||||
},
|
||||
async teardown () {
|
||||
// cleanup resources created by setup()
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### Go
|
||||
|
||||
> WIP
|
||||
|
||||
## API
|
||||
|
||||
### Connection
|
||||
|
||||
A valid connection (one that follows this abstraction), must implement the following API:
|
||||
|
||||
- type: `Connection`
|
||||
```js
|
||||
new Connection({
|
||||
localAddr,
|
||||
remoteAddr,
|
||||
localPeer,
|
||||
remotePeer,
|
||||
newStream,
|
||||
close,
|
||||
getStreams,
|
||||
stat: {
|
||||
direction,
|
||||
timeline: {
|
||||
open,
|
||||
upgraded
|
||||
},
|
||||
multiplexer,
|
||||
encryption
|
||||
}
|
||||
})
|
||||
```
|
||||
- `<Multiaddr> conn.localAddr`
|
||||
- `<Multiaddr> conn.remoteAddr`
|
||||
- `<PeerId> conn.localPeer`
|
||||
- `<PeerId> conn.remotePeer`
|
||||
- `<Object> conn.stat`
|
||||
- `<Map> conn.registry`
|
||||
- `Array<Stream> conn.streams`
|
||||
- `Promise<object> conn.newStream(Array<protocols>)`
|
||||
- `<void> conn.removeStream(id)`
|
||||
- `<Stream> conn.addStream(stream, protocol, metadata)`
|
||||
- `Promise<> conn.close()`
|
||||
|
||||
It can be obtained as follows:
|
||||
|
||||
```js
|
||||
const { Connection } = require('interface-connection')
|
||||
|
||||
const conn = new Connection({
|
||||
localAddr: maConn.localAddr,
|
||||
remoteAddr: maConn.remoteAddr,
|
||||
localPeer: this._peerId,
|
||||
remotePeer,
|
||||
newStream,
|
||||
close: err => maConn.close(err),
|
||||
getStreams,
|
||||
stats: {
|
||||
direction: 'outbound',
|
||||
timeline: {
|
||||
open: maConn.timeline.open,
|
||||
upgraded: Date.now()
|
||||
},
|
||||
multiplexer,
|
||||
encryption
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### Creating a connection instance
|
||||
|
||||
- `JavaScript` - `const conn = new Connection({localAddr, remoteAddr, localPeer, remotePeer, newStream, close, getStreams, direction, multiplexer, encryption})`
|
||||
|
||||
Creates a new Connection instance.
|
||||
|
||||
`localAddr` is the [multiaddr](https://github.com/multiformats/multiaddr) address used by the local peer to reach the remote.
|
||||
`remoteAddr` is the [multiaddr](https://github.com/multiformats/multiaddr) address used to communicate with the remote peer.
|
||||
`localPeer` is the [PeerId](https://github.com/libp2p/js-peer-id) of the local peer.
|
||||
`remotePeer` is the [PeerId](https://github.com/libp2p/js-peer-id) of the remote peer.
|
||||
`newStream` is the `function` responsible for getting a new muxed+multistream-selected stream.
|
||||
`close` is the `function` responsible for closing the raw connection.
|
||||
`getStreams` is the `function` responsible for getting the streams muxed within the connection.
|
||||
`stats` is an `object` with the metadata of the connection. It contains:
|
||||
- `direction` is a `string` indicating whether the connection is `inbound` or `outbound`.
|
||||
- `timeline` is an `object` with the relevant events timestamps of the connection (`open`, `upgraded` and `closed`; the `closed` will be added when the connection is closed).
|
||||
- `multiplexer` is a `string` with the connection multiplexing codec (optional).
|
||||
- `encryption` is a `string` with the connection encryption method identifier (optional).
|
||||
|
||||
#### Create a new stream
|
||||
|
||||
- `JavaScript` - `conn.newStream(protocols)`
|
||||
|
||||
Create a new stream within the connection.
|
||||
|
||||
`protocols` is an array of the intended protocol to use (by order of preference). Example: `[/echo/1.0.0]`
|
||||
|
||||
It returns a `Promise` with an object with the following properties:
|
||||
|
||||
```js
|
||||
{
|
||||
stream,
|
||||
protocol
|
||||
}
|
||||
```
|
||||
|
||||
The stream property contains the muxed stream, while the protocol contains the protocol codec used by the stream.
|
||||
|
||||
#### Add stream metadata
|
||||
|
||||
- `JavaScript` - `conn.addStream(stream, { protocol, ...metadata })`
|
||||
|
||||
Add a new stream to the connection registry.
|
||||
|
||||
`stream` is a muxed stream.
|
||||
`protocol` is the string codec for the protocol used by the stream. Example: `/echo/1.0.0`
|
||||
`metadata` is an object containing any additional, optional, stream metadata that you wish to track (such as its `tags`).
|
||||
|
||||
#### Remove a from the registry
|
||||
|
||||
- `JavaScript` - `conn.removeStream(id)`
|
||||
|
||||
Removes the stream with the given id from the connection registry.
|
||||
|
||||
`id` is the unique id of the stream for this connection.
|
||||
|
||||
|
||||
#### Close connection
|
||||
|
||||
- `JavaScript` - `conn.close()`
|
||||
|
||||
This method closes the connection to the remote peer, as well as all the streams muxed within the connection.
|
||||
|
||||
It returns a `Promise`.
|
||||
|
||||
#### Connection identifier
|
||||
|
||||
- `JavaScript` - `conn.id`
|
||||
|
||||
This property contains the identifier of the connection.
|
||||
|
||||
#### Connection streams registry
|
||||
|
||||
- `JavaScript` - `conn.registry`
|
||||
|
||||
This property contains a map with the muxed streams indexed by their id. This registry contains the protocol used by the stream, as well as its metadata.
|
||||
|
||||
#### Remote peer
|
||||
|
||||
- `JavaScript` - `conn.remotePeer`
|
||||
|
||||
This property contains the remote `peer-id` of this connection.
|
||||
|
||||
#### Local peer
|
||||
|
||||
- `JavaScript` - `conn.localPeer`
|
||||
|
||||
This property contains the local `peer-id` of this connection.
|
||||
|
||||
#### Get the connection Streams
|
||||
|
||||
- `JavaScript` - `conn.streams`
|
||||
|
||||
This getter returns all the muxed streams within the connection.
|
||||
|
||||
It returns an `Array`.
|
||||
|
||||
#### Remote address
|
||||
|
||||
- `JavaScript` - `conn.remoteAddr`
|
||||
|
||||
This getter returns the `remote` [multiaddr](https://github.com/multiformats/multiaddr) address.
|
||||
|
||||
#### Local address
|
||||
|
||||
- `JavaScript` - `conn.localAddr`
|
||||
|
||||
This getter returns the `local` [multiaddr](https://github.com/multiformats/multiaddr) address.
|
||||
|
||||
#### Stat
|
||||
|
||||
- `JavaScript` - `conn.stat`
|
||||
|
||||
This getter returns an `Object` with the metadata of the connection, as follows:
|
||||
|
||||
- `status`:
|
||||
|
||||
This property contains the status of the connection. It can be either `open`, `closing` or `closed`. Once the connection is created it is in an `open` status. When a `conn.close()` happens, the status will change to `closing` and finally, after all the connection streams are properly closed, the status will be `closed`.
|
||||
|
||||
- `timeline`:
|
||||
|
||||
This property contains an object with the `open`, `upgraded` and `close` timestamps of the connection. Note that, the `close` timestamp is `undefined` until the connection is closed.
|
||||
|
||||
- `direction`:
|
||||
|
||||
This property contains the direction of the peer in the connection. It can be `inbound` or `outbound`.
|
||||
|
||||
- `multiplexer`:
|
||||
|
||||
This property contains the `multiplexing` codec being used in the connection.
|
||||
|
||||
- `encryption`:
|
||||
|
||||
This property contains the encryption method being used in the connection. It is `undefined` if the connection is not encrypted.
|
||||
|
||||
#### Tags
|
||||
|
||||
- `JavaScript` - `conn.tags`
|
||||
|
||||
This property contains an array of tags associated with the connection. New tags can be pushed to this array during the connection's lifetime.
|
196
src/connection/connection.js
Normal file
@ -0,0 +1,196 @@
|
||||
'use strict'
|
||||
|
||||
const PeerId = require('peer-id')
|
||||
const multiaddr = require('multiaddr')
|
||||
|
||||
const withIs = require('class-is')
|
||||
|
||||
const assert = require('assert')
|
||||
const errCode = require('err-code')
|
||||
|
||||
/**
|
||||
* An implementation of the js-libp2p connection.
|
||||
* Any libp2p transport should use an upgrader to return this connection.
|
||||
*/
|
||||
class Connection {
|
||||
/**
|
||||
* Creates an instance of Connection.
|
||||
* @param {object} properties properties of the connection.
|
||||
* @param {multiaddr} properties.localAddr local multiaddr of the connection.
|
||||
* @param {multiaddr} properties.remoteAddr remote multiaddr of the connection.
|
||||
* @param {PeerId} properties.localPeer local peer-id.
|
||||
* @param {PeerId} properties.remotePeer remote peer-id.
|
||||
* @param {function} properties.newStream new stream muxer function.
|
||||
* @param {function} properties.close close raw connection function.
|
||||
* @param {function} properties.getStreams get streams from muxer function.
|
||||
* @param {object} properties.stat metadata of the connection.
|
||||
* @param {string} properties.stat.direction connection establishment direction ("inbound" or "outbound").
|
||||
* @param {object} properties.stat.timeline connection relevant events timestamp.
|
||||
* @param {string} properties.stat.timeline.open connection opening timestamp.
|
||||
* @param {string} properties.stat.timeline.upgraded connection upgraded timestamp.
|
||||
* @param {string} [properties.stat.multiplexer] connection multiplexing identifier.
|
||||
* @param {string} [properties.stat.encryption] connection encryption method identifier.
|
||||
*/
|
||||
constructor ({ localAddr, remoteAddr, localPeer, remotePeer, newStream, close, getStreams, stat }) {
|
||||
assert(multiaddr.isMultiaddr(localAddr), 'localAddr must be an instance of multiaddr')
|
||||
assert(multiaddr.isMultiaddr(remoteAddr), 'remoteAddr must be an instance of multiaddr')
|
||||
assert(PeerId.isPeerId(localPeer), 'localPeer must be an instance of peer-id')
|
||||
assert(PeerId.isPeerId(remotePeer), 'remotePeer must be an instance of peer-id')
|
||||
assert(typeof newStream === 'function', 'new stream must be a function')
|
||||
assert(typeof close === 'function', 'close must be a function')
|
||||
assert(typeof getStreams === 'function', 'getStreams must be a function')
|
||||
assert(stat, 'connection metadata object must be provided')
|
||||
assert(stat.direction === 'inbound' || stat.direction === 'outbound', 'direction must be "inbound" or "outbound"')
|
||||
assert(stat.timeline, 'connection timeline object must be provided in the stat object')
|
||||
assert(stat.timeline.open, 'connection open timestamp must be provided')
|
||||
assert(stat.timeline.upgraded, 'connection upgraded timestamp must be provided')
|
||||
|
||||
/**
|
||||
* Connection identifier.
|
||||
*/
|
||||
this.id = (parseInt(Math.random() * 1e9)).toString(36) + Date.now()
|
||||
|
||||
/**
|
||||
* Observed multiaddr of the local peer
|
||||
*/
|
||||
this.localAddr = localAddr
|
||||
|
||||
/**
|
||||
* Observed multiaddr of the remote peer
|
||||
*/
|
||||
this.remoteAddr = remoteAddr
|
||||
|
||||
/**
|
||||
* Local peer id.
|
||||
*/
|
||||
this.localPeer = localPeer
|
||||
|
||||
/**
|
||||
* Remote peer id.
|
||||
*/
|
||||
this.remotePeer = remotePeer
|
||||
|
||||
/**
|
||||
* Connection metadata.
|
||||
*/
|
||||
this._stat = {
|
||||
...stat,
|
||||
status: 'open'
|
||||
}
|
||||
|
||||
/**
|
||||
* Reference to the new stream function of the multiplexer
|
||||
*/
|
||||
this._newStream = newStream
|
||||
|
||||
/**
|
||||
* Reference to the close function of the raw connection
|
||||
*/
|
||||
this._close = close
|
||||
|
||||
/**
|
||||
* Reference to the getStreams function of the muxer
|
||||
*/
|
||||
this._getStreams = getStreams
|
||||
|
||||
/**
|
||||
* Connection streams registry
|
||||
*/
|
||||
this.registry = new Map()
|
||||
|
||||
/**
|
||||
* User provided tags
|
||||
*/
|
||||
this.tags = []
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection metadata
|
||||
* @return {Object}
|
||||
*/
|
||||
get stat () {
|
||||
return this._stat
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the streams of the muxer.
|
||||
* @return {Array<*>}
|
||||
*/
|
||||
get streams () {
|
||||
return this._getStreams()
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new stream from this connection
|
||||
* @param {string[]} protocols intended protocol for the stream
|
||||
* @return {Promise<object>} with muxed+multistream-selected stream and selected protocol
|
||||
*/
|
||||
async newStream (protocols) {
|
||||
if (this.stat.status === 'closing') {
|
||||
throw errCode(new Error('the connection is being closed'), 'ERR_CONNECTION_BEING_CLOSED')
|
||||
}
|
||||
|
||||
if (this.stat.status === 'closed') {
|
||||
throw errCode(new Error('the connection is closed'), 'ERR_CONNECTION_CLOSED')
|
||||
}
|
||||
|
||||
if (!Array.isArray(protocols)) protocols = [protocols]
|
||||
|
||||
const { stream, protocol } = await this._newStream(protocols)
|
||||
|
||||
this.addStream(stream, protocol)
|
||||
|
||||
return {
|
||||
stream,
|
||||
protocol
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a stream when it is opened to the registry.
|
||||
* @param {*} muxedStream a muxed stream
|
||||
* @param {object} properties the stream properties to be registered
|
||||
* @param {string} properties.protocol the protocol used by the stream
|
||||
* @param {object} properties.metadata metadata of the stream
|
||||
* @return {void}
|
||||
*/
|
||||
addStream (muxedStream, { protocol, metadata = {} }) {
|
||||
// Add metadata for the stream
|
||||
this.registry.set(muxedStream.id, {
|
||||
protocol,
|
||||
...metadata
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove stream registry after it is closed.
|
||||
* @param {string} id identifier of the stream
|
||||
*/
|
||||
removeStream (id) {
|
||||
this.registry.delete(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection.
|
||||
* @return {Promise}
|
||||
*/
|
||||
async close () {
|
||||
if (this.stat.status === 'closed') {
|
||||
return
|
||||
}
|
||||
|
||||
if (this._closing) {
|
||||
return this._closing
|
||||
}
|
||||
|
||||
this.stat.status = 'closing'
|
||||
|
||||
// Close raw connection
|
||||
this._closing = await this._close()
|
||||
|
||||
this._stat.timeline.close = Date.now()
|
||||
this.stat.status = 'closed'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = withIs(Connection, { className: 'Connection', symbolName: '@libp2p/interface-connection/connection' })
|
BIN
src/connection/img/badge.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
src/connection/img/badge.sketch
Normal file
19
src/connection/img/badge.svg
Normal file
After Width: | Height: | Size: 42 KiB |
3
src/connection/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
'use strict'
|
||||
|
||||
exports.Connection = require('./connection')
|
66
src/connection/package.json
Normal file
@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "interface-connection",
|
||||
"version": "0.4.1",
|
||||
"description": "A test suite and interface you can use to implement a connection interface.",
|
||||
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>",
|
||||
"main": "src/index.js",
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"lint": "aegir lint",
|
||||
"build": "aegir build",
|
||||
"test": "aegir test",
|
||||
"test:node": "aegir test -t node",
|
||||
"test:browser": "aegir test -t browser -t webworker",
|
||||
"release": "aegir release",
|
||||
"release-minor": "aegir release --type minor",
|
||||
"release-major": "aegir release --type major"
|
||||
},
|
||||
"pre-push": [
|
||||
"lint"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/libp2p/interface-connection.git"
|
||||
},
|
||||
"keywords": [
|
||||
"IPFS"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/libp2p/interface-connection/issues"
|
||||
},
|
||||
"homepage": "https://github.com/libp2p/interface-connection",
|
||||
"dependencies": {
|
||||
"abortable-iterator": "^2.1.0",
|
||||
"chai": "^4.2.0",
|
||||
"class-is": "^1.1.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"err-code": "^2.0.0",
|
||||
"multiaddr": "^7.1.0",
|
||||
"peer-id": "~0.13.2",
|
||||
"sinon": "^7.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aegir": "^20.2.0",
|
||||
"it-pair": "^1.0.0",
|
||||
"it-pipe": "^1.0.1",
|
||||
"mocha": "^6.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"contributors": [
|
||||
"David Dias <daviddias.p@gmail.com>",
|
||||
"Friedel Ziegelmayer <dignifiedquire@gmail.com>",
|
||||
"Greenkeeper <support@greenkeeper.io>",
|
||||
"Jacob Heun <jacobheun@gmail.com>",
|
||||
"James Ray <16969914+jamesray1@users.noreply.github.com>",
|
||||
"Pau Ramon Revilla <masylum@gmail.com>",
|
||||
"Richard Littauer <richard.littauer@gmail.com>",
|
||||
"Vasco Santos <vasco.santos@moxy.studio>"
|
||||
]
|
||||
}
|
65
src/connection/test/compliance.spec.js
Normal file
@ -0,0 +1,65 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const tests = require('../src/tests')
|
||||
const Connection = require('../src/connection')
|
||||
const peers = require('./utils/peers')
|
||||
const PeerId = require('peer-id')
|
||||
const multiaddr = require('multiaddr')
|
||||
const pair = require('it-pair')
|
||||
|
||||
describe('compliance tests', () => {
|
||||
tests({
|
||||
/**
|
||||
* Test setup. `properties` allows the compliance test to override
|
||||
* certain values for testing.
|
||||
* @param {*} properties
|
||||
*/
|
||||
async setup (properties) {
|
||||
const localAddr = multiaddr('/ip4/127.0.0.1/tcp/8080')
|
||||
const remoteAddr = multiaddr('/ip4/127.0.0.1/tcp/8081')
|
||||
const [localPeer, remotePeer] = await Promise.all([
|
||||
PeerId.createFromJSON(peers[0]),
|
||||
PeerId.createFromJSON(peers[1])
|
||||
])
|
||||
const openStreams = []
|
||||
let streamId = 0
|
||||
|
||||
return new Connection({
|
||||
localPeer,
|
||||
remotePeer,
|
||||
localAddr,
|
||||
remoteAddr,
|
||||
stat: {
|
||||
timeline: {
|
||||
open: Date.now() - 10,
|
||||
upgraded: Date.now()
|
||||
},
|
||||
direction: 'outbound',
|
||||
encryption: '/secio/1.0.0',
|
||||
multiplexer: '/mplex/6.7.0'
|
||||
},
|
||||
newStream: (protocols) => {
|
||||
const id = streamId++
|
||||
const stream = pair()
|
||||
|
||||
stream.close = () => stream.sink([])
|
||||
stream.id = id
|
||||
|
||||
openStreams.push(stream)
|
||||
|
||||
return {
|
||||
stream,
|
||||
protocol: protocols[0]
|
||||
}
|
||||
},
|
||||
close: () => {},
|
||||
getStreams: () => openStreams,
|
||||
...properties
|
||||
})
|
||||
},
|
||||
async teardown () {
|
||||
// cleanup resources created by setup()
|
||||
}
|
||||
})
|
||||
})
|
166
src/connection/test/connection.js
Normal file
@ -0,0 +1,166 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const expect = chai.expect
|
||||
chai.use(require('dirty-chai'))
|
||||
const sinon = require('sinon')
|
||||
|
||||
module.exports = (test) => {
|
||||
describe('connection', () => {
|
||||
describe('open connection', () => {
|
||||
let connection
|
||||
|
||||
beforeEach(async () => {
|
||||
connection = await test.setup()
|
||||
if (!connection) throw new Error('missing connection')
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await connection.close()
|
||||
await test.teardown()
|
||||
})
|
||||
|
||||
it('should have properties set', () => {
|
||||
expect(connection.id).to.exist()
|
||||
expect(connection.localPeer).to.exist()
|
||||
expect(connection.remotePeer).to.exist()
|
||||
expect(connection.localAddr).to.exist()
|
||||
expect(connection.remoteAddr).to.exist()
|
||||
expect(connection.stat.status).to.equal('open')
|
||||
expect(connection.stat.timeline.open).to.exist()
|
||||
expect(connection.stat.timeline.upgraded).to.exist()
|
||||
expect(connection.stat.timeline.close).to.not.exist()
|
||||
expect(connection.stat.direction).to.exist()
|
||||
expect(connection.streams).to.eql([])
|
||||
expect(connection.tags).to.eql([])
|
||||
})
|
||||
|
||||
it('should get the metadata of an open connection', () => {
|
||||
const stat = connection.stat
|
||||
|
||||
expect(stat.status).to.equal('open')
|
||||
expect(stat.direction).to.exist()
|
||||
expect(stat.timeline.open).to.exist()
|
||||
expect(stat.timeline.upgraded).to.exist()
|
||||
expect(stat.timeline.close).to.not.exist()
|
||||
})
|
||||
|
||||
it('should return an empty array of streams', () => {
|
||||
const streams = connection.streams
|
||||
|
||||
expect(streams).to.eql([])
|
||||
})
|
||||
|
||||
it('should be able to create a new stream', async () => {
|
||||
const protocolToUse = '/echo/0.0.1'
|
||||
const { stream, protocol } = await connection.newStream(protocolToUse)
|
||||
|
||||
expect(protocol).to.equal(protocolToUse)
|
||||
|
||||
const connStreams = await connection.streams
|
||||
|
||||
expect(stream).to.exist()
|
||||
expect(connStreams).to.exist()
|
||||
expect(connStreams).to.have.lengthOf(1)
|
||||
expect(connStreams[0]).to.equal(stream)
|
||||
})
|
||||
})
|
||||
|
||||
describe('close connection', () => {
|
||||
let connection
|
||||
let timelineProxy
|
||||
const proxyHandler = {
|
||||
set () {
|
||||
return Reflect.set(...arguments)
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
timelineProxy = new Proxy({
|
||||
open: Date.now() - 10,
|
||||
upgraded: Date.now()
|
||||
}, proxyHandler)
|
||||
|
||||
connection = await test.setup({
|
||||
stat: {
|
||||
timeline: timelineProxy,
|
||||
direction: 'outbound',
|
||||
encryption: '/crypto/1.0.0',
|
||||
multiplexer: '/muxer/1.0.0'
|
||||
}
|
||||
})
|
||||
if (!connection) throw new Error('missing connection')
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await test.teardown()
|
||||
})
|
||||
|
||||
it('should be able to close the connection after being created', async () => {
|
||||
expect(connection.stat.timeline.close).to.not.exist()
|
||||
await connection.close()
|
||||
|
||||
expect(connection.stat.timeline.close).to.exist()
|
||||
expect(connection.stat.status).to.equal('closed')
|
||||
})
|
||||
|
||||
it('should be able to close the connection after opening a stream', async () => {
|
||||
// Open stream
|
||||
const protocol = '/echo/0.0.1'
|
||||
await connection.newStream(protocol)
|
||||
|
||||
// Close connection
|
||||
expect(connection.stat.timeline.close).to.not.exist()
|
||||
await connection.close()
|
||||
|
||||
expect(connection.stat.timeline.close).to.exist()
|
||||
expect(connection.stat.status).to.equal('closed')
|
||||
})
|
||||
|
||||
it('should support a proxy on the timeline', async () => {
|
||||
sinon.spy(proxyHandler, 'set')
|
||||
expect(connection.stat.timeline.close).to.not.exist()
|
||||
|
||||
await connection.close()
|
||||
expect(proxyHandler.set.callCount).to.equal(1)
|
||||
const [obj, key, value] = proxyHandler.set.getCall(0).args
|
||||
expect(obj).to.eql(connection.stat.timeline)
|
||||
expect(key).to.equal('close')
|
||||
expect(value).to.be.a('number').that.equals(connection.stat.timeline.close)
|
||||
})
|
||||
|
||||
it('should fail to create a new stream if the connection is closing', async () => {
|
||||
expect(connection.stat.timeline.close).to.not.exist()
|
||||
connection.close()
|
||||
|
||||
try {
|
||||
const protocol = '/echo/0.0.1'
|
||||
await connection.newStream(protocol)
|
||||
} catch (err) {
|
||||
expect(err).to.exist()
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error('should fail to create a new stream if the connection is closing')
|
||||
})
|
||||
|
||||
it('should fail to create a new stream if the connection is closed', async () => {
|
||||
expect(connection.stat.timeline.close).to.not.exist()
|
||||
await connection.close()
|
||||
|
||||
try {
|
||||
const protocol = '/echo/0.0.1'
|
||||
await connection.newStream(protocol)
|
||||
} catch (err) {
|
||||
expect(err).to.exist()
|
||||
expect(err.code).to.equal('ERR_CONNECTION_CLOSED')
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error('should fail to create a new stream if the connection is closing')
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
27
src/connection/test/utils/peers.js
Normal file
@ -0,0 +1,27 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = [{
|
||||
id: 'QmNMMAqSxPetRS1cVMmutW5BCN1qQQyEr4u98kUvZjcfEw',
|
||||
privKey: 'CAASpQkwggShAgEAAoIBAQDPek2aeHMa0blL42RTKd6xgtkk4Zkldvq4LHxzcag5uXepiQzWANEUvoD3KcUTmMRmx14PvsxdLCNst7S2JSa0R2n5wSRs14zGy6892lx4H4tLBD1KSpQlJ6vabYM1CJhIQRG90BtzDPrJ/X1iJ2HA0PPDz0Mflam2QUMDDrU0IuV2m7gSCJ5r4EmMs3U0xnH/1gShkVx4ir0WUdoWf5KQUJOmLn1clTRHYPv4KL9A/E38+imNAXfkH3c2T7DrCcYRkZSpK+WecjMsH1dCX15hhhggNqfp3iulO1tGPxHjm7PDGTPUjpCWKpD5e50sLqsUwexac1ja6ktMfszIR+FPAgMBAAECggEAB2H2uPRoRCAKU+T3gO4QeoiJaYKNjIO7UCplE0aMEeHDnEjAKC1HQ1G0DRdzZ8sb0fxuIGlNpFMZv5iZ2ZFg2zFfV//DaAwTek9tIOpQOAYHUtgHxkj5FIlg2BjlflGb+ZY3J2XsVB+2HNHkUEXOeKn2wpTxcoJE07NmywkO8Zfr1OL5oPxOPlRN1gI4ffYH2LbfaQVtRhwONR2+fs5ISfubk5iKso6BX4moMYkxubYwZbpucvKKi/rIjUA3SK86wdCUnno1KbDfdXSgCiUlvxt/IbRFXFURQoTV6BOi3sP5crBLw8OiVubMr9/8WE6KzJ0R7hPd5+eeWvYiYnWj4QKBgQD6jRlAFo/MgPO5NZ/HRAk6LUG+fdEWexA+GGV7CwJI61W/Dpbn9ZswPDhRJKo3rquyDFVZPdd7+RlXYg1wpmp1k54z++L1srsgj72vlg4I8wkZ4YLBg0+zVgHlQ0kxnp16DvQdOgiRFvMUUMEgetsoIx1CQWTd67hTExGsW+WAZQKBgQDT/WaHWvwyq9oaZ8G7F/tfeuXvNTk3HIJdfbWGgRXB7lJ7Gf6FsX4x7PeERfL5a67JLV6JdiLLVuYC2CBhipqLqC2DB962aKMvxobQpSljBBZvZyqP1IGPoKskrSo+2mqpYkeCLbDMuJ1nujgMP7gqVjabs2zj6ACKmmpYH/oNowJ/T0ZVtvFsjkg+1VsiMupUARRQuPUWMwa9HOibM1NIZcoQV2NGXB5Z++kR6JqxQO0DZlKArrviclderUdY+UuuY4VRiSEprpPeoW7ZlbTku/Ap8QZpWNEzZorQDro7bnfBW91fX9/81ets/gCPGrfEn+58U3pdb9oleCOQc/ifpQKBgBTYGbi9bYbd9vgZs6bd2M2um+VFanbMytS+g5bSIn2LHXkVOT2UEkB+eGf9KML1n54QY/dIMmukA8HL1oNAyalpw+/aWj+9Ui5kauUhGEywHjSeBEVYM9UXizxz+m9rsoktLLLUI0o97NxCJzitG0Kub3gn0FEogsUeIc7AdinZAoGBANnM1vcteSQDs7x94TDEnvvqwSkA2UWyLidD2jXgE0PG4V6tTkK//QPBmC9eq6TIqXkzYlsErSw4XeKO91knFofmdBzzVh/ddgx/NufJV4tXF+a2iTpqYBUJiz9wpIKgf43/Ob+P1EA99GAhSdxz1ess9O2aTqf3ANzn6v6g62Pv',
|
||||
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPek2aeHMa0blL42RTKd6xgtkk4Zkldvq4LHxzcag5uXepiQzWANEUvoD3KcUTmMRmx14PvsxdLCNst7S2JSa0R2n5wSRs14zGy6892lx4H4tLBD1KSpQlJ6vabYM1CJhIQRG90BtzDPrJ/X1iJ2HA0PPDz0Mflam2QUMDDrU0IuV2m7gSCJ5r4EmMs3U0xnH/1gShkVx4ir0WUdoWf5KQUJOmLn1clTRHYPv4KL9A/E38+imNAXfkH3c2T7DrCcYRkZSpK+WecjMsH1dCX15hhhggNqfp3iulO1tGPxHjm7PDGTPUjpCWKpD5e50sLqsUwexac1ja6ktMfszIR+FPAgMBAAE='
|
||||
}, {
|
||||
id: 'QmW8rAgaaA6sRydK1k6vonShQME47aDxaFidbtMevWs73t',
|
||||
privKey: 'CAASpwkwggSjAgEAAoIBAQCTU3gVDv3SRXLOsFln9GEf1nJ/uCEDhOG10eC0H9l9IPpVxjuPT1ep+ykFUdvefq3D3q+W3hbmiHm81o8dYv26RxZIEioToUWp7Ec5M2B/niYoE93za9/ZDwJdl7eh2hNKwAdxTmdbXUPjkIU4vLyHKRFbJIn9X8w9djldz8hoUvC1BK4L1XrT6F2l0ruJXErH2ZwI1youfSzo87TdXIoFKdrQLuW6hOtDCGKTiS+ab/DkMODc6zl8N47Oczv7vjzoWOJMUJs1Pg0ZsD1zmISY38P0y/QyEhatZn0B8BmSWxlLQuukatzOepQI6k+HtfyAAjn4UEqnMaXTP1uwLldVAgMBAAECggEAHq2f8MqpYjLiAFZKl9IUs3uFZkEiZsgx9BmbMAb91Aec+WWJG4OLHrNVTG1KWp+IcaQablEa9bBvoToQnS7y5OpOon1d066egg7Ymfmv24NEMM5KRpktCNcOSA0CySpPIB6yrg6EiUr3ixiaFUGABKkxmwgVz/Q15IqM0ZMmCUsC174PMAz1COFZxD0ZX0zgHblOJQW3dc0X3XSzhht8vU02SMoVObQHQfeXEHv3K/RiVj/Ax0bTc5JVkT8dm8xksTtsFCNOzRBqFS6MYqX6U/u0Onz3Jm5Jt7fLWb5n97gZR4SleyGrqxYNb46d9X7mP0ie7E6bzFW0DsWBIeAqVQKBgQDW0We2L1n44yOvJaMs3evpj0nps13jWidt2I3RlZXjWzWHiYQfvhWUWqps/xZBnAYgnN/38xbKzHZeRNhrqOo+VB0WK1IYl0lZVE4l6TNKCsLsUfQzsb1pePkd1eRZA+TSqsi+I/IOQlQU7HA0bMrah/5FYyUBP0jYvCOvYTlZuwKBgQCvkcVRydVlzjUgv7lY5lYvT8IHV5iYO4Qkk2q6Wjv9VUKAJZauurMdiy05PboWfs5kbETdwFybXMBcknIvZO4ihxmwL8mcoNwDVZHI4bXapIKMTCyHgUKvJ9SeTcKGC7ZuQJ8mslRmYox/HloTOXEJgQgPRxXcwa3amzvdZI+6LwKBgQCLsnQqgxKUi0m6bdR2qf7vzTH4258z6X34rjpT0F5AEyF1edVFOz0XU/q+lQhpNEi7zqjLuvbYfSyA026WXKuwSsz7jMJ/oWqev/duKgAjp2npesY/E9gkjfobD+zGgoS9BzkyhXe1FCdP0A6L2S/1+zg88WOwMvJxl6/xLl24XwKBgCm60xSajX8yIQyUpWBM9yUtpueJ2Xotgz4ST+bVNbcEAddll8gWFiaqgug9FLLuFu5lkYTHiPtgc1RNdphvO+62/9MRuLDixwh/2TPO+iNqwKDKJjda8Nei9vVddCPaOtU/xNQ0xLzFJbG9LBmvqH9izOCcu8SJwGHaTcNUeJj/AoGADCJ26cY30c13F/8awAAmFYpZWCuTP5ppTsRmjd63ixlrqgkeLGpJ7kYb5fXkcTycRGYgP0e1kssBGcmE7DuG955fx3ZJESX3GQZ+XfMHvYGONwF1EiK1f0p6+GReC2VlQ7PIkoD9o0hojM6SnWvv9EXNjCPALEbfPFFvcniKVsE=',
|
||||
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCTU3gVDv3SRXLOsFln9GEf1nJ/uCEDhOG10eC0H9l9IPpVxjuPT1ep+ykFUdvefq3D3q+W3hbmiHm81o8dYv26RxZIEioToUWp7Ec5M2B/niYoE93za9/ZDwJdl7eh2hNKwAdxTmdbXUPjkIU4vLyHKRFbJIn9X8w9djldz8hoUvC1BK4L1XrT6F2l0ruJXErH2ZwI1youfSzo87TdXIoFKdrQLuW6hOtDCGKTiS+ab/DkMODc6zl8N47Oczv7vjzoWOJMUJs1Pg0ZsD1zmISY38P0y/QyEhatZn0B8BmSWxlLQuukatzOepQI6k+HtfyAAjn4UEqnMaXTP1uwLldVAgMBAAE='
|
||||
}, {
|
||||
id: 'QmZqCdSzgpsmB3Qweb9s4fojAoqELWzqku21UVrqtVSKi4',
|
||||
privKey: 'CAASpgkwggSiAgEAAoIBAQCdbSEsTmw7lp5HagRcx57DaLiSUEkh4iBcKc7Y+jHICEIA8NIVi9FlfGEZj9G21FpiTR4Cy+BLVEuf8Nm90bym4iV+cSumeS21fvD8xGTEbeKGljs6OYHy3M45JhWF85gqHQJOqZufI2NRDuRgMZEO2+qGEXmSlv9mMXba/+9ecze8nSpB7bG2Z2pnKDeYwhF9Cz+ElMyn7TBWDjJERGVgFbTpdM3rBnbhB/TGpvs732QqZmIBlxnDb/Jn0l1gNZCgkEDcJ/0NDMBJTQ8vbvcdmaw3eaMPLkn1ix4wdu9QWCA0IBtuY1R7vSUtf4irnLJG7DnAw2GfM5QrF3xF1GLXAgMBAAECggEAQ1N0qHoxl5pmvqv8iaFlqLSUmx5y6GbI6CGJMQpvV9kQQU68yjItr3VuIXx8d/CBZyEMAK4oko7OeOyMcr3MLKLy3gyQWnXgsopDjhZ/8fH8uwps8g2+IZuFJrO+6LaxEPGvFu06fOiphPUVfn40R2KN/iBjGeox+AaXijmCqaV2vEdNJJPpMfz6VKZBDLTrbiqvo/3GN1U99PUqfPWpOWR29oAhh/Au6blSqvqTUPXB2+D/X6e1JXv31mxMPK68atDHSUjZWKB9lE4FMK1bkSKJRbyXmNIlbZ9V8X4/0r8/6T7JnW7ZT8ugRkquohmwgG7KkDXB1YsOCKXYUqzVYQKBgQDtnopFXWYl7XUyePJ/2MA5i7eoko9jmF44L31irqmHc5unNf6JlNBjlxTNx3WyfzhUzrn3c18psnGkqtow0tkBj5hmqn8/WaPbc5UA/5R1FNaNf8W5khn7MDm6KtYRPjN9djqTDiVHyC6ljONYd+5S+MqyKVWZ3t/xvG60sw85qwKBgQCpmpDtL+2JBwkfeUr3LyDcQxvbfzcv8lXj2otopWxWiLiZF1HzcqgAa2CIwu9kCGEt9Zr+9E4uINbe1To0b01/FhvR6xKO/ukceGA/mBB3vsKDcRmvpBUp+3SmnhY0nOk+ArQl4DhJ34k8pDM3EDPrixPf8SfVdU/8IM32lsdHhQKBgHLgpvCKCwxjFLnmBzcPzz8C8TOqR3BbBZIcQ34l+wflOGdKj1hsfaLoM8KYn6pAHzfBCd88A9Hg11hI0VuxVACRL5jS7NnvuGwsIOluppNEE8Ys86aXn7/0vLPoab3EWJhbRE48FIHzobmft3nZ4XpzlWs02JGfUp1IAC2UM9QpAoGAeWy3pZhSr2/iEC5+hUmwdQF2yEbj8+fDpkWo2VrVnX506uXPPkQwE1zM2Bz31t5I9OaJ+U5fSpcoPpDaAwBMs1fYwwlRWB8YNdHY1q6/23svN3uZsC4BGPV2JnO34iMUudilsRg+NGVdk5TbNejbwx7nM8Urh59djFzQGGMKeSECgYA0QMCARPpdMY50Mf2xQaCP7HfMJhESSPaBq9V3xY6ToEOEnXgAR5pNjnU85wnspHp+82r5XrKfEQlFxGpj2YA4DRRmn239sjDa29qP42UNAFg1+C3OvXTht1d5oOabaGhU0udwKmkEKUbb0bG5xPQJ5qeSJ5T1gLzLk3SIP0GlSw==',
|
||||
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCdbSEsTmw7lp5HagRcx57DaLiSUEkh4iBcKc7Y+jHICEIA8NIVi9FlfGEZj9G21FpiTR4Cy+BLVEuf8Nm90bym4iV+cSumeS21fvD8xGTEbeKGljs6OYHy3M45JhWF85gqHQJOqZufI2NRDuRgMZEO2+qGEXmSlv9mMXba/+9ecze8nSpB7bG2Z2pnKDeYwhF9Cz+ElMyn7TBWDjJERGVgFbTpdM3rBnbhB/TGpvs732QqZmIBlxnDb/Jn0l1gNZCgkEDcJ/0NDMBJTQ8vbvcdmaw3eaMPLkn1ix4wdu9QWCA0IBtuY1R7vSUtf4irnLJG7DnAw2GfM5QrF3xF1GLXAgMBAAE='
|
||||
}, {
|
||||
id: 'QmR5VwgsL7jyfZHAGyp66tguVrQhCRQuRc3NokocsCZ3fA',
|
||||
privKey: 'CAASpwkwggSjAgEAAoIBAQCGXYU+uc2nn1zuJhfdFOl34upztnrD1gpHu58ousgHdGlGgYgbqLBAvIAauXdEL0+e30HofjA634SQxE+9nV+0FQBam1DDzHQlXsuwHV+2SKvSDkk4bVllMFpu2SJtts6VH+OXC/2ANJOm+eTALykQPYXgLIBxrhp/eD+Jz5r6wW2nq3k6OmYyK/4pgGzFjo5UyX+fa/171AJ68UPboFpDy6BZCcUjS0ondxPvD7cv5jMNqqMKIB/7rpi8n+Q3oeccRqVL56wH+FE3/QLjwYHwY6ILNRyvNXRqHjwBEXB2R5moXN0AFUWTw9rt3KhFiEjR1U81BTw5/xS7W2Iu0FgZAgMBAAECggEAS64HK8JZfE09eYGJNWPe8ECmD1C7quw21BpwVe+GVPSTizvQHswPohbKDMNj0srXDMPxCnNw1OgqcaOwyjsGuZaOoXoTroTM8nOHRIX27+PUqzaStS6aCG2IsiCozKUHjGTuupftS7XRaF4eIsUtWtFcQ1ytZ9pJYHypRQTi5NMSrTze5ThjnWxtHilK7gnBXik+aR0mYEVfSn13czQEC4rMOs+b9RAc/iibDNoLopfIdvmCCvfxzmySnR7Cu1iSUAONkir7PB+2Mt/qRFCH6P+jMamtCgQ8AmifXgVmDUlun+4MnKg3KrPd6ZjOEKhVe9mCHtGozk65RDREShfDdQKBgQDi+x2MuRa9peEMOHnOyXTS+v+MFcfmG0InsO08rFNBKZChLB+c9UHBdIvexpfBHigSyERfuDye4z6lxi8ZnierWMYJP30nxmrnxwTGTk1MQquhfs1A0kpmDnPsjlOS/drEIEIssNx2WbfJ7YtMxLWBtp+BJzGpQmr0LKC+NHRSrwKBgQCXiy2kJESIUkIs2ihV55hhT6/bZo1B1O5DPA2nkjOBXqXF6fvijzMDX82JjLd07lQZlI0n1Q/Hw0p4iYi9YVd2bLkLXF5UIb2qOeHj76enVFOrPHUSkC9Y2g/0Xs+60Ths2xRd8RrrfQU3kl5iVpBywkCIrb2M5+wRnNTk1W3TtwKBgQCvplyrteAfSurpJhs9JzE8w/hWU9SqAZYkWQp91W1oE95Um2yrbjBAoQxMjaqKS+f/APPIjy56Vqj4aHGyhW11b/Fw3qzfxvCcBKtxOs8eoMlo5FO6QgJJEA4tlcafDcvp0nzjUMqK28safLU7503+33B35fjMXxWdd5u9FaKfCQKBgC4W6j6tuRosymuRvgrCcRnHfpify/5loEFallyMnpWOD6Tt0OnK25z/GifnYDRz96gAAh5HMpFy18dpLOlMHamqz2yhHx8/U8vd5tHIJZlCkF/X91M5/uxrBccwvsT2tM6Got8fYSyVzWxlW8dUxIHiinYHQUsFjkqdBDLEpq5pAoGASoTw5RBEWFM0GuAZdXsyNyxU+4S+grkTS7WdW/Ymkukh+bJZbnvF9a6MkSehqXnknthmufonds2AFNS//63gixENsoOhzT5+2cdfc6tJECvJ9xXVXkf85AoQ6T/RrXF0W4m9yQyCngNJUrKUOIH3oDIfdZITlYzOC3u1ojj7VuQ=',
|
||||
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCGXYU+uc2nn1zuJhfdFOl34upztnrD1gpHu58ousgHdGlGgYgbqLBAvIAauXdEL0+e30HofjA634SQxE+9nV+0FQBam1DDzHQlXsuwHV+2SKvSDkk4bVllMFpu2SJtts6VH+OXC/2ANJOm+eTALykQPYXgLIBxrhp/eD+Jz5r6wW2nq3k6OmYyK/4pgGzFjo5UyX+fa/171AJ68UPboFpDy6BZCcUjS0ondxPvD7cv5jMNqqMKIB/7rpi8n+Q3oeccRqVL56wH+FE3/QLjwYHwY6ILNRyvNXRqHjwBEXB2R5moXN0AFUWTw9rt3KhFiEjR1U81BTw5/xS7W2Iu0FgZAgMBAAE='
|
||||
}, {
|
||||
id: 'QmScLDqRg7H6ipCYxm9fVk152UWavQFKscTdoT4YNHxgqp',
|
||||
privKey: 'CAASpwkwggSjAgEAAoIBAQCWEHaTZ6LBLFP5OPrUqjDM/cF4b2zrfh1Zm3kd02ZtgQB3iYtZqRPJT5ctT3A7WdVF/7dCxPGOCkJlLekTx4Y4gD8JtjA+EfN9fR/2RBKbti2N3CD4vkGp9ss4hbBFcXIhl8zuD/ELHutbV6b8b4QXJGnxfp/B+1kNPnyd7SJznS0QyvI8OLI1nAkVKdYLDRW8kPKeHyx1xhdNDuTQVTFyAjRGQ4e3UYFB7bYIHW3E6kCtCoJDlj+JPC02Yt1LHzIzZVLvPvNFnYY2mag6OiGFuh/oMBIqvnPc1zRZ3eLUqeGZjQVaoR0kdgZUKz7Q2TBeNldxK/s6XO0DnkQTlelNAgMBAAECggEAdmt1dyswR2p4tdIeNpY7Pnj9JNIhTNDPznefI0dArCdBvBMhkVaYk6MoNIxcj6l7YOrDroAF8sXr0TZimMY6B/pERKCt/z1hPWTxRQBBAvnHhwvwRPq2jK6BfhAZoyM8IoBNKowP9mum5QUNdGV4Al8s73KyFX0IsCfgZSvNpRdlt+DzPh+hu/CyoZaMpRchJc1UmK8Fyk3KfO+m0DZNfHP5P08lXNfM6MZLgTJVVgERHyG+vBOzTd2RElMe19nVCzHwb3dPPRZSQ7Fnz3rA+GeLqsM2Zi4HNhfbD1OcD9C4wDj5tYL6hWTkdz4IlfVcjCeUHxgIOhdDV2K+OwbuAQKBgQD0FjUZ09UW2FQ/fitbvIB5f1SkXWPxTF9l6mAeuXhoGv2EtQUO4vq/PK6N08RjrZdWQy6UsqHgffi7lVQ8o3hvCKdbtf4sP+cM92OrY0WZV89os79ndj4tyvmnP8WojwRjt/2XEfgdoWcgWxW9DiYINTOQVimZX+X/3on4s8hEgQKBgQCdY3kOMbyQeLTRkqHXjVTY4ddO+v4S4wOUa1l4rTqAbq1W3JYWwoDQgFuIu3limIHmjnSJpCD4EioXFsM7p6csenoc20sHxsaHnJ6Mn5Te41UYmY9EW0otkQ0C3KbXM0hwQkjyplnEmZawGKmjEHW8DJ3vRYTv9TUCgYKxDHgOzQKBgB4A/NYH7BG61eBYKgxEx6YnuMfbkwV+Vdu5S8d7FQn3B2LgvZZu4FPRqcNVXLbEB+5ao8czjiKCWaj1Wj15+rvrXGcxn+Tglg5J+r5+nXeUC7LbJZQaPNp0MOwWMr3dlrSLUWjYlJ9Pz9VyXOG4c4Rexc/gR4zK9QLW4C7qKpwBAoGAZzyUb0cYlPtYQA+asTU3bnvVKy1f8yuNcZFowst+EDiI4u0WVh+HNzy6zdmLKa03p+/RaWeLaK0hhrubnEnAUmCUMNF3ScaM+u804LDcicc8TkKLwx7ObU0z56isl4RAA8K27tNHFrpYKXJD834cfBkaj5ReOrfw6Y/iFhhDuBECgYEA8gbC76uz7LSHhW30DSRTcqOzTyoe2oYKQaxuxYNp7vSSOkcdRen+mrdflDvud2q/zN2QdL4pgqdldHlR35M/lJ0f0B6zp74jlzbO9700wzsOqreezGc5eWiroDL100U9uIZ50BKb8CKtixIHpinUSPIUcVDkSAZ2y7mbfCxQwqQ=',
|
||||
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCWEHaTZ6LBLFP5OPrUqjDM/cF4b2zrfh1Zm3kd02ZtgQB3iYtZqRPJT5ctT3A7WdVF/7dCxPGOCkJlLekTx4Y4gD8JtjA+EfN9fR/2RBKbti2N3CD4vkGp9ss4hbBFcXIhl8zuD/ELHutbV6b8b4QXJGnxfp/B+1kNPnyd7SJznS0QyvI8OLI1nAkVKdYLDRW8kPKeHyx1xhdNDuTQVTFyAjRGQ4e3UYFB7bYIHW3E6kCtCoJDlj+JPC02Yt1LHzIzZVLvPvNFnYY2mag6OiGFuh/oMBIqvnPc1zRZ3eLUqeGZjQVaoR0kdgZUKz7Q2TBeNldxK/s6XO0DnkQTlelNAgMBAAE='
|
||||
}, {
|
||||
id: 'QmckxVrJw1Yo8LqvmDJNUmdAsKtSbiKWmrXJFyKmUraBoN',
|
||||
privKey: 'CAASpwkwggSjAgEAAoIBAQC1/GFud/7xutux7qRfMj1sIdMRh99/chR6HqVj6LQqrgk4jil0mdN/LCk/tqPqmDtObHdmEhCoybzuhLbCKgUqryKDwO6yBJHSKWY9QqrKZtLJ37SgKwGjE3+NUD4r1dJHhtQrICFdOdSCBzs/v8gi+J+KZLHo7+Nms4z09ysy7qZh94Pd7cW4gmSMergqUeANLD9C0ERw1NXolswOW7Bi7UGr7yuBxejICLO3nkxe0OtpQBrYrqdCD9vs3t/HQZbPWVoiRj4VO7fxkAPKLl30HzcIfxj/ayg8NHcH59d08D+N2v5Sdh28gsiYKIPE9CXvuw//HUY2WVRY5fDC5JglAgMBAAECggEBAKb5aN/1w3pBqz/HqRMbQpYLNuD33M3PexBNPAy+P0iFpDo63bh5Rz+A4lvuFNmzUX70MFz7qENlzi6+n/zolxMB29YtWBUH8k904rTEjXXl//NviQgITZk106tx+4k2x5gPEm57LYGfBOdFAUzNhzDnE2LkXwRNzkS161f7zKwOEsaGWRscj6UvhO4MIFxjb32CVwt5eK4yOVqtyMs9u30K4Og+AZYTlhtm+bHg6ndCCBO6CQurCQ3jD6YOkT+L3MotKqt1kORpvzIB0ujZRf49Um8wlcjC5G9aexBeGriXaVdPF62zm7GA7RMsbQM/6aRbA1fEQXvJhHUNF9UFeaECgYEA8wCjKqQA7UQnHjRwTsktdwG6szfxd7z+5MTqHHTWhWzgcQLgdh5/dO/zanEoOThadMk5C1Bqjq96gH2xim8dg5XQofSVtV3Ui0dDa+XRB3E3fyY4D3RF5hHv85O0GcvQc6DIb+Ja1oOhvHowFB1C+CT3yEgwzX/EK9xpe+KtYAkCgYEAv7hCnj/DcZFU3fAfS+unBLuVoVJT/drxv66P686s7J8UM6tW+39yDBZ1IcwY9vHFepBvxY2fFfEeLI02QFM+lZXVhNGzFkP90agNHK01psGgrmIufl9zAo8WOKgkLgbYbSHzkkDeqyjEPU+B0QSsZOCE+qLCHSdsnTmo/TjQhj0CgYAz1+j3yfGgrS+jVBC53lXi0+2fGspbf2jqKdDArXSvFqFzuudki/EpY6AND4NDYfB6hguzjD6PnoSGMUrVfAtR7X6LbwEZpqEX7eZGeMt1yQPMDr1bHrVi9mS5FMQR1NfuM1lP9Xzn00GIUpE7WVrWUhzDEBPJY/7YVLf0hFH08QKBgDWBRQZJIVBmkNrHktRrVddaSq4U/d/Q5LrsCrpymYwH8WliHgpeTQPWmKXwAd+ZJdXIzYjCt202N4eTeVqGYOb6Q/anV2WVYBbM4avpIxoA28kPGY6nML+8EyWIt2ApBOmgGgvtEreNzwaVU9NzjHEyv6n7FlVwlT1jxCe3XWq5AoGASYPKQoPeDlW+NmRG7z9EJXJRPVtmLL40fmGgtju9QIjLnjuK8XaczjAWT+ySI93Whu+Eujf2Uj7Q+NfUjvAEzJgwzuOd3jlQvoALq11kuaxlNQTn7rx0A1QhBgUJE8AkvShPC9FEnA4j/CLJU0re9H/8VvyN6qE0Mho0+YbjpP8=',
|
||||
pubKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1/GFud/7xutux7qRfMj1sIdMRh99/chR6HqVj6LQqrgk4jil0mdN/LCk/tqPqmDtObHdmEhCoybzuhLbCKgUqryKDwO6yBJHSKWY9QqrKZtLJ37SgKwGjE3+NUD4r1dJHhtQrICFdOdSCBzs/v8gi+J+KZLHo7+Nms4z09ysy7qZh94Pd7cW4gmSMergqUeANLD9C0ERw1NXolswOW7Bi7UGr7yuBxejICLO3nkxe0OtpQBrYrqdCD9vs3t/HQZbPWVoiRj4VO7fxkAPKLl30HzcIfxj/ayg8NHcH59d08D+N2v5Sdh28gsiYKIPE9CXvuw//HUY2WVRY5fDC5JglAgMBAAE='
|
||||
}]
|
9
src/connection/tests.js
Normal file
@ -0,0 +1,9 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
'use strict'
|
||||
|
||||
const connectionSuite = require('../test/connection')
|
||||
|
||||
module.exports = (test) => {
|
||||
connectionSuite(test)
|
||||
}
|
59
src/content-routing/.gitignore
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
22
src/content-routing/LICENSE
Normal file
@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 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.
|
||||
|
68
src/content-routing/README.md
Normal file
@ -0,0 +1,68 @@
|
||||
interface-content-routing
|
||||
=====================
|
||||
|
||||
[](http://protocol.ai)
|
||||
[](http://libp2p.io/)
|
||||
[](http://webchat.freenode.net/?channels=%23libp2p)
|
||||
[](https://discuss.libp2p.io)
|
||||
|
||||
> A test suite and interface you can use to implement a Content Routing module for libp2p.
|
||||
|
||||
The primary goal of this module is to enable developers to pick and swap their Content Routing module as they see fit for their libp2p installation, without having to go through shims or compatibility issues. This module and test suite were heavily inspired by abstract-blob-store and interface-stream-muxer.
|
||||
|
||||
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 not actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks.
|
||||
|
||||
## Lead Maintainer
|
||||
|
||||
[Vasco Santos](https://github.com/vasco-santos).
|
||||
|
||||
# Modules that implement the interface
|
||||
|
||||
- [JavaScript libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht)
|
||||
- [JavaScript libp2p-delegated-peer-routing](https://github.com/libp2p/js-libp2p-delegated-peer-routing)
|
||||
- [JavaScript libp2p-kad-routing](https://github.com/libp2p/js-libp2p-kad-routing)
|
||||
|
||||
# Badge
|
||||
|
||||
Include this badge in your readme if you make a module that is compatible with the interface-content-routing API. You can validate this by running the tests.
|
||||
|
||||

|
||||
|
||||
# How to use the battery of tests
|
||||
|
||||
## Node.js
|
||||
|
||||
```javascript
|
||||
var tape = require('tape')
|
||||
var tests = require('interface-content-routing/tests')
|
||||
var yourImpl = require('../src')
|
||||
|
||||
var common = {
|
||||
setup: function (t, cb) {
|
||||
cb(null, yourImpl)
|
||||
},
|
||||
teardown: function (t, cb) {
|
||||
cb()
|
||||
}
|
||||
}
|
||||
|
||||
tests(tape, common)
|
||||
```
|
||||
|
||||
## Go
|
||||
|
||||
> WIP - The go-libp2p implementation does not have a test suite to be used, yet.
|
||||
|
||||
# API
|
||||
|
||||
A valid (read: that follows this abstraction) Content Routing module must implement the following API.
|
||||
|
||||
### `.findProviders`
|
||||
|
||||
- `JavaScript` peerRouting.findProviders
|
||||
|
||||
### `.provide`
|
||||
|
||||
- `JavaScript` peerRouting.provide
|
BIN
src/content-routing/img/badge.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
src/content-routing/img/badge.sketch
Normal file
25
src/content-routing/img/badge.svg
Normal file
After Width: | Height: | Size: 223 KiB |
23
src/content-routing/package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "interface-content-routing",
|
||||
"version": "0.0.0",
|
||||
"description": "A test suite and interface you can use to implement a Content Routing for libp2p.",
|
||||
"leadMaintainer": "Vasco Santos <vasco.santos@moxy.studio>",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/libp2p/interface-content-routing.git"
|
||||
},
|
||||
"keywords": [
|
||||
"IPFS"
|
||||
],
|
||||
"author": "David Dias <daviddias@ipfs.io>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/libp2p/interface-content-routing/issues"
|
||||
},
|
||||
"homepage": "https://github.com/libp2p/interface-content-routing",
|
||||
"devDependencies": {},
|
||||
"dependencies": {
|
||||
"timed-tape": "^0.1.1"
|
||||
}
|
||||
}
|
10
src/content-routing/tests/base-test.js
Normal file
@ -0,0 +1,10 @@
|
||||
'use strict'
|
||||
|
||||
module.exports.all = function (test, common) {
|
||||
test('test', function (t) {
|
||||
common.setup(test, function (err, pr) {
|
||||
if (err) {}
|
||||
common.teardown()
|
||||
})
|
||||
})
|
||||
}
|
7
src/content-routing/tests/index.js
Normal file
@ -0,0 +1,7 @@
|
||||
'use strict'
|
||||
const timed = require('timed-tape')
|
||||
|
||||
module.exports = function (test, common) {
|
||||
test = timed(test)
|
||||
require('./base-test.js').all(test, common)
|
||||
}
|
8
src/peer-discovery/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
**/node_modules/
|
||||
**/*.log
|
||||
package-lock.json
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
docs
|
||||
dist
|
40
src/peer-discovery/.travis.yml
Normal file
@ -0,0 +1,40 @@
|
||||
language: node_js
|
||||
cache: npm
|
||||
stages:
|
||||
- check
|
||||
- test
|
||||
- cov
|
||||
|
||||
node_js:
|
||||
- '10'
|
||||
- '12'
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
- windows
|
||||
|
||||
script: npx nyc -s npm run test:node -- --bail
|
||||
after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: check
|
||||
script:
|
||||
- npx aegir dep-check
|
||||
- npm run lint
|
||||
|
||||
- stage: test
|
||||
name: chrome
|
||||
addons:
|
||||
chrome: stable
|
||||
script: npx aegir test -t browser -t webworker
|
||||
|
||||
- stage: test
|
||||
name: firefox
|
||||
addons:
|
||||
firefox: latest
|
||||
script: npx aegir test -t browser -t webworker -- --browsers FirefoxHeadless
|
||||
|
||||
notifications:
|
||||
email: false
|
20
src/peer-discovery/CHANGELOG.md
Normal file
@ -0,0 +1,20 @@
|
||||
<a name="0.1.1"></a>
|
||||
## [0.1.1](https://github.com/libp2p/interface-peer-discovery/compare/v0.1.0...v0.1.1) (2019-09-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add async support to setup ([#11](https://github.com/libp2p/interface-peer-discovery/issues/11)) ([2814c76](https://github.com/libp2p/interface-peer-discovery/commit/2814c76))
|
||||
|
||||
|
||||
|
||||
<a name="0.1.0"></a>
|
||||
# [0.1.0](https://github.com/libp2p/interface-peer-discovery/compare/v0.0.1...v0.1.0) (2019-09-27)
|
||||
|
||||
|
||||
|
||||
<a name="0.0.1"></a>
|
||||
## 0.0.1 (2017-02-09)
|
||||
|
||||
|
||||
|
21
src/peer-discovery/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 libp2p
|
||||
|
||||
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.
|
83
src/peer-discovery/README.md
Normal file
@ -0,0 +1,83 @@
|
||||
interface-peer-discovery
|
||||
========================
|
||||
|
||||
[](http://protocol.ai)
|
||||
[](http://libp2p.io/)
|
||||
[](http://webchat.freenode.net/?channels=%23libp2p)
|
||||
[](https://discuss.libp2p.io)
|
||||
|
||||
> A test suite and interface you can use to implement a Peer Discovery module for libp2p.
|
||||
|
||||
The primary goal of this module is to enable developers to pick and/or swap their Peer Discovery modules 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 not actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks.
|
||||
|
||||
## Lead Maintainer
|
||||
|
||||
[Vasco Santos](https://github.com/vasco-santos).
|
||||
|
||||
## Modules that implement the interface
|
||||
|
||||
- [JavaScript libp2p-mdns](https://github.com/libp2p/js-libp2p-mdns)
|
||||
- [JavaScript libp2p-bootstrap](https://github.com/libp2p/js-libp2p-bootstrap)
|
||||
- [JavaScript libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht)
|
||||
- [JavaScript libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star)
|
||||
- [JavaScript libp2p-websocket-star](https://github.com/libp2p/js-libp2p-websocket-star)
|
||||
|
||||
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-peer-discovery API.
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
### Node.js
|
||||
|
||||
Install `interface-discovery` 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-discovery')
|
||||
|
||||
const common = {
|
||||
setup () {
|
||||
return YourDiscovery
|
||||
},
|
||||
teardown () {
|
||||
// Clean up any resources created by setup()
|
||||
}
|
||||
}
|
||||
|
||||
// use all of the test suits
|
||||
test(common)
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
A valid (read: that follows this abstraction) Peer Discovery module must implement the following API:
|
||||
|
||||
### `start` the service
|
||||
|
||||
- `await discovery.start()`
|
||||
|
||||
Start the discovery service.
|
||||
|
||||
It returns a `Promise`
|
||||
|
||||
### `stop` the service
|
||||
|
||||
- `await discovery.stop()`
|
||||
|
||||
Stop the discovery service.
|
||||
|
||||
It returns a `Promise`
|
||||
|
||||
### discoverying peers
|
||||
|
||||
- `discovery.on('peer', (peerInfo) => {})`
|
||||
|
||||
Everytime a peer is discovered by a discovery service, it emmits a `peer` event with the discover peer's [PeerInfo](https://github.com/libp2p/js-peer-info).
|
BIN
src/peer-discovery/img/badge.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
src/peer-discovery/img/badge.sketch
Normal file
39
src/peer-discovery/img/badge.svg
Normal file
After Width: | Height: | Size: 88 KiB |
34
src/peer-discovery/index.js
Normal file
@ -0,0 +1,34 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
module.exports = (common) => {
|
||||
describe('interface-peer-discovery', () => {
|
||||
let discovery
|
||||
|
||||
before(async () => {
|
||||
discovery = await common.setup()
|
||||
})
|
||||
|
||||
after(() => common.teardown && common.teardown())
|
||||
|
||||
afterEach('ensure discovery was stopped', () => discovery.stop())
|
||||
|
||||
it('can start the service', async () => {
|
||||
await discovery.start()
|
||||
})
|
||||
|
||||
it('can start and stop the service', async () => {
|
||||
await discovery.start()
|
||||
await discovery.stop()
|
||||
})
|
||||
|
||||
it('should not fail to stop the service if it was not started', async () => {
|
||||
await discovery.stop()
|
||||
})
|
||||
|
||||
it('should not fail to start the service if it is already started', async () => {
|
||||
await discovery.start()
|
||||
await discovery.start()
|
||||
})
|
||||
})
|
||||
}
|
58
src/peer-discovery/package.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "interface-discovery",
|
||||
"version": "0.1.1",
|
||||
"description": "A test suite and interface you can use to implement a discovery interface.",
|
||||
"leadMaintainer": "Vasco Santos <vasco.santos@moxy.studio>",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"lint": "aegir lint",
|
||||
"build": "aegir build",
|
||||
"test": "aegir test",
|
||||
"test:node": "aegir test -t node",
|
||||
"test:browser": "aegir test -t browser -t webworker",
|
||||
"release": "aegir release",
|
||||
"release-minor": "aegir release --type minor",
|
||||
"release-major": "aegir release --type major"
|
||||
},
|
||||
"pre-push": [
|
||||
"lint"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/libp2p/interface-peer-discovery.git"
|
||||
},
|
||||
"keywords": [
|
||||
"libp2p",
|
||||
"network",
|
||||
"p2p",
|
||||
"peer",
|
||||
"discovery",
|
||||
"peer-to-peer",
|
||||
"IPFS"
|
||||
],
|
||||
"author": "David Dias <daviddias@ipfs.io>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/libp2p/interface-peer-discovery/issues"
|
||||
},
|
||||
"homepage": "https://github.com/libp2p/interface-peer-discovery",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"aegir": "^20.3.1",
|
||||
"chai": "^4.2.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"peer-id": "^0.13.3",
|
||||
"peer-info": "^0.17.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"contributors": [
|
||||
"David Dias <daviddias.p@gmail.com>",
|
||||
"Jacob Heun <jacobheun@gmail.com>",
|
||||
"Mike Goelzer <mgoelzer@docker.com>",
|
||||
"Vasco Santos <vasco.santos@moxy.studio>",
|
||||
"Vasco Santos <vasco.santos@ua.pt>"
|
||||
]
|
||||
}
|
17
src/peer-discovery/test/compliance.spec.js
Normal file
@ -0,0 +1,17 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const tests = require('../src')
|
||||
const MockDiscovery = require('./mock-discovery')
|
||||
|
||||
describe('compliance tests', () => {
|
||||
tests({
|
||||
async setup () {
|
||||
await new Promise(resolve => setTimeout(resolve, 10))
|
||||
return new MockDiscovery()
|
||||
},
|
||||
async teardown () {
|
||||
await new Promise(resolve => setTimeout(resolve, 10))
|
||||
}
|
||||
})
|
||||
})
|
48
src/peer-discovery/test/mock-discovery.js
Normal file
@ -0,0 +1,48 @@
|
||||
'use strict'
|
||||
|
||||
const { EventEmitter } = require('events')
|
||||
|
||||
const PeerId = require('peer-id')
|
||||
const PeerInfo = require('peer-info')
|
||||
|
||||
/**
|
||||
* Emits 'peer' events on discovery.
|
||||
*/
|
||||
class MockDiscovery extends EventEmitter {
|
||||
/**
|
||||
* Constructs a new Bootstrap.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {number} options.discoveryDelay - the delay to find a peer (in milli-seconds)
|
||||
*/
|
||||
constructor (options = {}) {
|
||||
super()
|
||||
|
||||
this.options = options
|
||||
this._isRunning = false
|
||||
this._timer = null
|
||||
}
|
||||
|
||||
start () {
|
||||
this._isRunning = true
|
||||
this._discoverPeer()
|
||||
}
|
||||
|
||||
stop () {
|
||||
clearTimeout(this._timer)
|
||||
this._isRunning = false
|
||||
}
|
||||
|
||||
async _discoverPeer () {
|
||||
if (!this._isRunning) return
|
||||
|
||||
const peerId = await PeerId.create({ bits: 512 })
|
||||
const peerInfo = new PeerInfo(peerId)
|
||||
|
||||
this._timer = setTimeout(() => {
|
||||
this.emit('peer', peerInfo)
|
||||
}, this.options.discoveryDelay || 1000)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MockDiscovery
|
27
src/peer-routing/.gitignore
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# 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
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
node_modules
|
22
src/peer-routing/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.
|
||||
|
71
src/peer-routing/README.md
Normal file
@ -0,0 +1,71 @@
|
||||
interface-peer-routing
|
||||
=====================
|
||||
|
||||
[](http://protocol.ai)
|
||||
[](http://libp2p.io/)
|
||||
[](http://webchat.freenode.net/?channels=%23libp2p)
|
||||
[](https://discuss.libp2p.io)
|
||||
[](https://github.com/feross/standard)
|
||||
|
||||
> A test suite and interface you can use to implement a Peer Routing module for libp2p.
|
||||
|
||||
The primary goal of this module is to enable developers to pick and swap their Peer Routing module as they see fit for their libp2p installation, without having to go through shims or compatibility issues. This module and test suite were heavily inspired by abstract-blob-store and interface-stream-muxer.
|
||||
|
||||
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 not actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks.
|
||||
|
||||
## Lead Maintainer
|
||||
|
||||
[Vasco Santos](https://github.com/vasco-santos).
|
||||
|
||||
# Modules that implement the interface
|
||||
|
||||
- [JavaScript libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht)
|
||||
- [JavaScript libp2p-delegated-peer-routing](https://github.com/libp2p/js-libp2p-delegated-peer-routing)
|
||||
- [JavaScript libp2p-kad-routing](https://github.com/libp2p/js-libp2p-kad-routing)
|
||||
|
||||
# Badge
|
||||
|
||||
Include this badge in your readme if you make a module that is compatible with the interface-record-store API. You can validate this by running the tests.
|
||||
|
||||

|
||||
|
||||
# How to use the battery of tests
|
||||
|
||||
## Node.js
|
||||
|
||||
```javascript
|
||||
var tape = require('tape')
|
||||
var tests = require('interface-peer-routing/tests')
|
||||
var YourPeerRouter = require('../src')
|
||||
|
||||
var common = {
|
||||
setup: function (t, cb) {
|
||||
cb(null, YourPeerRouter)
|
||||
},
|
||||
teardown: function (t, cb) {
|
||||
cb()
|
||||
}
|
||||
}
|
||||
|
||||
tests(tape, common)
|
||||
```
|
||||
|
||||
## Go
|
||||
|
||||
> WIP - The go-libp2p implementation does not have a test suite to be used, yet.
|
||||
|
||||
# API
|
||||
|
||||
A valid (read: that follows this abstraction) Peer Routing module must implement the following API.
|
||||
|
||||
### `.findPeers` - Find peers 'responsible' or 'closest' to a given key
|
||||
|
||||
- `Node.js` peerRouting.findPeers(key, function (err, peersPriorityQueue) {})
|
||||
|
||||
In a peer to peer context, the concept of 'responsability' or 'closeness' for a given key translates to having a way to find deterministically or that at least there is a significant overlap between searches, the same group of peers when searching for the same given key.
|
||||
|
||||
This method will query the network (route it) and return a Priority Queue datastructe with a list of PeerInfo objects, ordered by 'closeness'.
|
||||
|
||||
key is a multihash
|
BIN
src/peer-routing/img/badge.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
src/peer-routing/img/badge.sketch
Normal file
19
src/peer-routing/img/badge.svg
Normal file
After Width: | Height: | Size: 133 KiB |
24
src/peer-routing/package.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "interface-peer-routing",
|
||||
"version": "0.1.3",
|
||||
"description": "A test suite and interface you can use to implement a Peer Routing for libp2p.",
|
||||
"leadMaintainer": "Vasco Santos <vasco.santos@moxy.studio>",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/diasdavid/interface-peer-routing.git"
|
||||
},
|
||||
"keywords": [
|
||||
"IPFS"
|
||||
],
|
||||
"author": "David Dias <daviddias@ipfs.io>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/diasdavid/interface-peer-routing/issues"
|
||||
},
|
||||
"homepage": "https://github.com/diasdavid/interface-peer-routing",
|
||||
"devDependencies": {},
|
||||
"dependencies": {
|
||||
"peer-id": "~0.12.2",
|
||||
"timed-tape": "^0.1.1"
|
||||
}
|
||||
}
|
15
src/peer-routing/tests/base-test.js
Normal file
@ -0,0 +1,15 @@
|
||||
var Id = require('peer-id')
|
||||
|
||||
module.exports.all = function (test, common) {
|
||||
test('Simple findPeers test', function (t) {
|
||||
common.setup(test, function (err, pr) {
|
||||
t.plan(3)
|
||||
t.ifError(err)
|
||||
pr.findPeers(Id.create().toBytes(), function (err, peerQueue) {
|
||||
t.ifError(err)
|
||||
t.equal(peerQueue.length >= 1, true)
|
||||
common.teardown()
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
6
src/peer-routing/tests/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
var timed = require('timed-tape')
|
||||
|
||||
module.exports = function (test, common) {
|
||||
test = timed(test)
|
||||
require('./base-test.js').all(test, common)
|
||||
}
|
27
src/record-store/.gitignore
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# 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
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
node_modules
|
22
src/record-store/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.
|
||||
|
87
src/record-store/README.md
Normal file
@ -0,0 +1,87 @@
|
||||
interface-record-store
|
||||
=====================
|
||||
|
||||
[](http://protocol.ai)
|
||||
[](http://libp2p.io/)
|
||||
[](http://webchat.freenode.net/?channels=%23libp2p)
|
||||
[](https://discuss.libp2p.io)
|
||||
|
||||
> A test suite and interface you can use to implement a a [IPRS compliant](/IPRS.md) Record Store.
|
||||
|
||||
The primary goal of this module is to enable developers to pick and swap their Record Store module as they see fit for their libp2p installation, without having to go through shims or compatibility issues. This module and test suite were heavily inspired by [`abstract-blob-store`](https://github.com/maxogden/abstract-blob-store) and [`interface-stream-muxer`](https://github.com/libp2p/interface-stream-muxer).
|
||||
|
||||
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 not actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks.
|
||||
|
||||
## Lead Maintainer
|
||||
|
||||
[Vasco Santos](https://github.com/vasco-santos).
|
||||
|
||||
# Modules that implement the interface
|
||||
|
||||
- [ipfs-distributed-record-store](https://github.com/libp2p/js-libp2p-distributed-record-store)
|
||||
- [ipfs-kad-record-store](https://github.com/libp2p/js-libp2p-kad-record-store)
|
||||
|
||||
# Badge
|
||||
|
||||
Include this badge in your readme if you make a module that is compatible with the interface-record-store API. You can validate this by running the tests.
|
||||
|
||||

|
||||
|
||||
# How to use the battery of tests
|
||||
|
||||
## Node.js
|
||||
|
||||
```
|
||||
var tape = require('tape')
|
||||
var tests = require('interface-record-store/tests')
|
||||
var YourRecordStore = require('../src')
|
||||
|
||||
var common = {
|
||||
setup: function (t, cb) {
|
||||
cb(null, YourRecordStore)
|
||||
},
|
||||
teardown: function (t, cb) {
|
||||
cb()
|
||||
}
|
||||
}
|
||||
|
||||
tests(tape, common)
|
||||
```
|
||||
|
||||
## Go
|
||||
|
||||
> WIP
|
||||
|
||||
# API
|
||||
|
||||
A valid (read: that follows this abstraction) stream muxer, must implement the following API.
|
||||
|
||||
### Obtain a Record
|
||||
|
||||
- `Node.js` rs.get(key, function (err, records) {})
|
||||
|
||||
This method returns an array of records, found in the Record Store.
|
||||
|
||||
If `err` is passed, `records` will be a `undefined` value.
|
||||
|
||||
`key` is a multihash value that represents any arbitraty random value, that may have records associated with it.
|
||||
|
||||
### Store a Record
|
||||
|
||||
- `Node.js` rs.put(key, recordSignatureMultiHash, function (err) {})
|
||||
|
||||
`recordSignatureMultihash` is multihash of the Record Signature MerkleDAG obj, as described by IPRS - InterPlanetary Record Spec
|
||||
|
||||
if `err` is passed, means that the record wasn't stored properly or it was unvalid.
|
||||
|
||||
### Implementation considerations
|
||||
|
||||
|
||||
- the key is a multihash but not necessarily the hash of the record signature object.
|
||||
- a DRS instance must have a mapping of key->[hash(recordSignature)] to know which records belong to a given key (provided value)
|
||||
- DRS implements the interface-record-store interface
|
||||
- DRS may levarage other implementations of interface-record-store to find records in the network or other storage mechanisms
|
||||
- DRS should return every valid record it can find in a query
|
||||
- all unvalid records detected in the process should be discarded/deleted
|
BIN
src/record-store/img/badge.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
src/record-store/img/badge.sketch
Normal file
18
src/record-store/img/badge.svg
Normal file
After Width: | Height: | Size: 20 KiB |
31
src/record-store/package.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "interface-record-store",
|
||||
"version": "0.1.3",
|
||||
"description": "A test suite and interface you can use to implement a a IPRS compliant(https://github.com/ipfs/specs/tree/master/records) Record Store",
|
||||
"leadMaintainer": "Vasco Santos <vasco.santos@moxy.studio>",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/libp2p/interface-record-store.git"
|
||||
},
|
||||
"keywords": [
|
||||
"IPFS"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^4.0.0"
|
||||
},
|
||||
"author": "David Dias <daviddias@ipfs.io>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/libp2p/interface-record-store/issues"
|
||||
},
|
||||
"homepage": "https://github.com/libp2p/interface-record-store",
|
||||
"dependencies": {
|
||||
"ecdsa": "~0.7.0",
|
||||
"ipld": "~0.22.0",
|
||||
"multihashing": "~0.3.3",
|
||||
"timed-tape": "~0.1.1"
|
||||
}
|
||||
}
|
90
src/record-store/tests/base-test.js
Normal file
@ -0,0 +1,90 @@
|
||||
var crypto = require('crypto')
|
||||
var multihashing = require('multihashing')
|
||||
var ipld = require('ipld')
|
||||
var ecdsa = require('ecdsa')
|
||||
|
||||
module.exports.all = function (test, common) {
|
||||
|
||||
test('Store a valid record', function (t) {
|
||||
common.setup(test, function (err, recordStore) {
|
||||
t.ifError(err, 'Should not throw')
|
||||
|
||||
var mdagStore = recordStore.mdagStore
|
||||
|
||||
var ecdh = crypto.createECDH('secp256k1')
|
||||
ecdh.generateKeys()
|
||||
|
||||
var mdagObj_pubKey = {
|
||||
'@context': ipld.context.merkleweb,
|
||||
algorithm: {
|
||||
mlink: 'secp256k1'
|
||||
},
|
||||
encoding: {
|
||||
mlink: 'raw'
|
||||
},
|
||||
bytes: ecdh.getPublicKey()
|
||||
}
|
||||
|
||||
var mdagObj_pubKey_encoded = ipld.marshal(mdagObj_pubKey)
|
||||
var mdagObj_pubKey_mh = multihashing(mdagObj_pubKey_encoded, 'sha2-256')
|
||||
mdagStore.put(mdagObj_pubKey, mdagObj_pubKey_mh)
|
||||
|
||||
var current = new Date()
|
||||
|
||||
var mdagObj_record = {
|
||||
'@context': ipld.context.merkleweb,
|
||||
scheme: {
|
||||
mlink: 'type-a'
|
||||
},
|
||||
expires: (new Date()).setDate(current.getDate() + 1),
|
||||
value: 'aaah the data!'
|
||||
}
|
||||
|
||||
var mdagObj_record_encoded = ipld.marshal(mdagObj_record)
|
||||
var mdagObj_record_mh = multihashing(mdagObj_record_encoded, 'sha2-256')
|
||||
mdagStore.put(mdagObj_record, mdagObj_record_mh)
|
||||
|
||||
var mdagObj_record_encoded_hash = crypto.createHash('sha256').update(mdagObj_record_encoded).digest()
|
||||
var record_signed = ecdsa.sign(mdagObj_record_encoded_hash, ecdh.getPrivateKey())
|
||||
|
||||
var mdagObj_record_signature = {
|
||||
'@context': ipld.context.merkleweb,
|
||||
pubKey: {
|
||||
mlink: mdagObj_pubKey_mh
|
||||
},
|
||||
algorithm: {
|
||||
mlink: 'secp256k1'
|
||||
},
|
||||
encoding: {
|
||||
mlink: 'binary'
|
||||
},
|
||||
signee: {
|
||||
mlink: mdagObj_record_mh
|
||||
},
|
||||
bytes: record_signed
|
||||
}
|
||||
|
||||
var mdagObj_record_signature_encoded = ipld.marshal(mdagObj_record_signature)
|
||||
var mdagObj_record_signature_encoded_mh = multihashing(mdagObj_record_signature_encoded, 'sha2-256')
|
||||
|
||||
mdagStore.put(mdagObj_record_signature, mdagObj_record_signature_encoded_mh)
|
||||
|
||||
recordStore.put('bananas', mdagObj_record_signature, function (err) {
|
||||
t.ifError(err, 'Should not throw')
|
||||
recordStore.get('bananas', function (err, records) {
|
||||
t.ifError(err, 'Should not throw')
|
||||
t.equal(records.length, 1)
|
||||
t.pass('record was stored successfully')
|
||||
t.end()
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// test('Store an unvalid record')
|
||||
// test('Store and retrieve a valid record')
|
||||
// test('Store a bunch of valid and unvalid records and check what gets retrieved')
|
||||
// test('Store a bunch of records with variable validity, wait for some to expire, check what gets retrieved')
|
||||
|
||||
}
|
6
src/record-store/tests/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
var timed = require('timed-tape')
|
||||
|
||||
module.exports = function (test, common) {
|
||||
test = timed(test)
|
||||
require('./base-test.js').all(test, common)
|
||||
}
|
35
src/stream-muxer/.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
src/stream-muxer/.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
src/stream-muxer/.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
src/stream-muxer/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
src/stream-muxer/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
src/stream-muxer/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.
|
153
src/stream-muxer/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/stream-muxer/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)
|
||||
})
|
||||
})
|
||||
}
|
BIN
src/stream-muxer/img/badge.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
src/stream-muxer/img/badge.sketch
Normal file
18
src/stream-muxer/img/badge.svg
Normal file
After Width: | Height: | Size: 20 KiB |
19
src/stream-muxer/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/stream-muxer/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))
|
||||
})
|
||||
}
|
63
src/stream-muxer/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>"
|
||||
]
|
||||
}
|
82
src/stream-muxer/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/stream-muxer/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
src/stream-muxer/test/stream-muxer.spec.js
Normal file
@ -0,0 +1 @@
|
||||
'use strict'
|
8
src/transport/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
**/node_modules/
|
||||
**/*.log
|
||||
package-lock.json
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
docs
|
||||
dist
|
34
src/transport/.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
|
10
src/transport/.travis.yml
Normal file
@ -0,0 +1,10 @@
|
||||
sudo: false
|
||||
language: node_js
|
||||
node_js:
|
||||
- "10"
|
||||
|
||||
before_install:
|
||||
- npm install -g npm
|
||||
|
||||
script:
|
||||
- npm run lint
|
171
src/transport/CHANGELOG.md
Normal file
@ -0,0 +1,171 @@
|
||||
<a name="0.7.0"></a>
|
||||
# [0.7.0](https://github.com/libp2p/interface-transport/compare/v0.6.1...v0.7.0) (2019-09-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* timeline and close checking ([#55](https://github.com/libp2p/interface-transport/issues/55)) ([993ca1c](https://github.com/libp2p/interface-transport/commit/993ca1c))
|
||||
|
||||
|
||||
|
||||
<a name="0.6.1"></a>
|
||||
## [0.6.1](https://github.com/libp2p/interface-transport/compare/v0.6.0...v0.6.1) (2019-09-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **test:** close with timeout ([#54](https://github.com/libp2p/interface-transport/issues/54)) ([583f02d](https://github.com/libp2p/interface-transport/commit/583f02d))
|
||||
|
||||
|
||||
|
||||
<a name="0.6.0"></a>
|
||||
# [0.6.0](https://github.com/libp2p/interface-transport/compare/v0.5.2...v0.6.0) (2019-09-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add upgrader support to transports ([#53](https://github.com/libp2p/interface-transport/issues/53)) ([a5ad120](https://github.com/libp2p/interface-transport/commit/a5ad120))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* Transports must now be passed and use an `Upgrader` instance. See the Readme for usage. Compliance test suites will now need to pass `options` from `common.setup(options)` to their Transport constructor.
|
||||
|
||||
* docs: update readme to include upgrader
|
||||
|
||||
* docs: update readme to include MultiaddrConnection ref
|
||||
|
||||
* feat: add upgrader spy to test suite
|
||||
|
||||
* test: validate returned value of spy
|
||||
|
||||
|
||||
|
||||
<a name="0.5.2"></a>
|
||||
## [0.5.2](https://github.com/libp2p/interface-transport/compare/v0.5.1...v0.5.2) (2019-06-11)
|
||||
|
||||
|
||||
|
||||
<a name="0.5.1"></a>
|
||||
## [0.5.1](https://github.com/libp2p/interface-transport/compare/v0.5.0...v0.5.1) (2019-05-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* move dirty-chai to dependencies ([#52](https://github.com/libp2p/interface-transport/issues/52)) ([f9a7908](https://github.com/libp2p/interface-transport/commit/f9a7908))
|
||||
|
||||
|
||||
|
||||
<a name="0.5.0"></a>
|
||||
# [0.5.0](https://github.com/libp2p/interface-transport/compare/v0.4.0...v0.5.0) (2019-04-29)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* "feat: make listen take an array of addrs ([#46](https://github.com/libp2p/interface-transport/issues/46))" ([#51](https://github.com/libp2p/interface-transport/issues/51)) ([030195e](https://github.com/libp2p/interface-transport/commit/030195e))
|
||||
|
||||
|
||||
|
||||
<a name="0.4.0"></a>
|
||||
# [0.4.0](https://github.com/libp2p/interface-transport/compare/v0.3.7...v0.4.0) (2019-04-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add type to AbortError ([#45](https://github.com/libp2p/interface-transport/issues/45)) ([4fd37bb](https://github.com/libp2p/interface-transport/commit/4fd37bb))
|
||||
* callbacks -> async / await ([#44](https://github.com/libp2p/interface-transport/issues/44)) ([b30ee5f](https://github.com/libp2p/interface-transport/commit/b30ee5f))
|
||||
* make listen take an array of addrs ([#46](https://github.com/libp2p/interface-transport/issues/46)) ([1dc5baa](https://github.com/libp2p/interface-transport/commit/1dc5baa))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* All places in the API that used callbacks are now replaced with async/await
|
||||
|
||||
* test: add tests for canceling dials
|
||||
|
||||
* feat: Adapter class
|
||||
|
||||
|
||||
|
||||
<a name="0.3.7"></a>
|
||||
## [0.3.7](https://github.com/libp2p/interface-transport/compare/v0.3.6...v0.3.7) (2018-11-29)
|
||||
|
||||
|
||||
|
||||
<a name="0.3.6"></a>
|
||||
## [0.3.6](https://github.com/libp2p/interface-transport/compare/v0.3.5...v0.3.6) (2018-04-05)
|
||||
|
||||
|
||||
|
||||
<a name="0.3.5"></a>
|
||||
## [0.3.5](https://github.com/libp2p/interface-transport/compare/v0.3.4...v0.3.5) (2017-03-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* wrong main path in package.json ([54b83a7](https://github.com/libp2p/interface-transport/commit/54b83a7))
|
||||
|
||||
|
||||
|
||||
<a name="0.3.4"></a>
|
||||
## [0.3.4](https://github.com/libp2p/interface-transport/compare/v0.3.3...v0.3.4) (2017-02-09)
|
||||
|
||||
|
||||
|
||||
<a name="0.3.3"></a>
|
||||
## [0.3.3](https://github.com/libp2p/interface-transport/compare/v0.3.2...v0.3.3) (2016-09-06)
|
||||
|
||||
|
||||
|
||||
<a name="0.3.2"></a>
|
||||
## [0.3.2](https://github.com/libp2p/interface-transport/compare/v0.3.1...v0.3.2) (2016-09-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **dial-test:** ensure goodbye works over tcp ([e1346da](https://github.com/libp2p/interface-transport/commit/e1346da))
|
||||
|
||||
|
||||
|
||||
<a name="0.3.1"></a>
|
||||
## [0.3.1](https://github.com/libp2p/interface-transport/compare/v0.3.0...v0.3.1) (2016-09-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **package.json:** point to right main ([ace6150](https://github.com/libp2p/interface-transport/commit/ace6150))
|
||||
|
||||
|
||||
|
||||
<a name="0.3.0"></a>
|
||||
# [0.3.0](https://github.com/libp2p/interface-transport/compare/v0.2.0...v0.3.0) (2016-09-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **tests:** add place holder test script for releases ([8e9f7cf](https://github.com/libp2p/interface-transport/commit/8e9f7cf))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **dialer:** remove conn from on connect callback ([1bd20d9](https://github.com/libp2p/interface-transport/commit/1bd20d9))
|
||||
* **spec:** update the dial interface to cope with new pull additions ([2e12166](https://github.com/libp2p/interface-transport/commit/2e12166))
|
||||
* **tests:** add dial and listen tests ([d50224d](https://github.com/libp2p/interface-transport/commit/d50224d))
|
||||
|
||||
|
||||
|
||||
<a name="0.2.0"></a>
|
||||
# [0.2.0](https://github.com/libp2p/interface-transport/compare/v0.1.1...v0.2.0) (2016-06-16)
|
||||
|
||||
|
||||
|
||||
<a name="0.1.1"></a>
|
||||
## [0.1.1](https://github.com/libp2p/interface-transport/compare/v0.1.0...v0.1.1) (2015-12-12)
|
||||
|
||||
|
||||
|
||||
<a name="0.1.0"></a>
|
||||
# 0.1.0 (2015-09-17)
|
||||
|
||||
|
||||
|
22
src/transport/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.
|
||||
|
227
src/transport/README.md
Normal file
@ -0,0 +1,227 @@
|
||||
interface-transport
|
||||
===================
|
||||
|
||||
[](http://protocol.ai)
|
||||
[](http://libp2p.io/)
|
||||
[](http://webchat.freenode.net/?channels=%23libp2p)
|
||||
[](https://discuss.libp2p.io)
|
||||
[](https://travis-ci.com/libp2p/interface-transport)
|
||||
[](https://david-dm.org/libp2p/interface-transport)
|
||||
[](https://github.com/feross/standard)
|
||||
|
||||
> A test suite and interface you can use to implement a libp2p transport. A libp2p transport is understood as something that offers a dial and listen interface.
|
||||
|
||||
The primary goal of this module is to enable developers to pick and swap their transport module as they see fit for their libp2p installation, without having to go through shims or compatibility issues. This module and test suite were heavily inspired by abstract-blob-store, interface-stream-muxer and others.
|
||||
|
||||
Publishing a test suite as a module lets multiple modules all ensure compatibility since they use the same test suite.
|
||||
|
||||
The purpose of this interface is not to reinvent any wheels when it comes to dialing and listening to transports. Instead, it tries to provide a uniform API for several transports through a shimmed interface.
|
||||
|
||||
## Lead Maintainer
|
||||
|
||||
[Jacob Heun](https://github.com/jacobheun/)
|
||||
|
||||
# Modules that implement the interface
|
||||
|
||||
- [js-libp2p-tcp](https://github.com/libp2p/js-libp2p-tcp)
|
||||
- [js-libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star)
|
||||
- [js-libp2p-webrtc-direct](https://github.com/libp2p/js-libp2p-webrtc-direct)
|
||||
- [js-libp2p-websocket-star](https://github.com/libp2p/js-libp2p-websocket-star)
|
||||
- [js-libp2p-websockets](https://github.com/libp2p/js-libp2p-websockets)
|
||||
- [js-libp2p-utp](https://github.com/libp2p/js-libp2p-utp)
|
||||
- [webrtc-explorer](https://github.com/diasdavid/webrtc-explorer)
|
||||
|
||||
# Badge
|
||||
|
||||
Include this badge in your readme if you make a module that is compatible with the interface-transport API. You can validate this by running the tests.
|
||||
|
||||

|
||||
|
||||
# How to use the battery of tests
|
||||
|
||||
## Node.js
|
||||
|
||||
```js
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const tests = require('interface-transport')
|
||||
const multiaddr = require('multiaddr')
|
||||
const YourTransport = require('../src')
|
||||
|
||||
describe('compliance', () => {
|
||||
tests({
|
||||
setup (options) {
|
||||
let transport = new YourTransport(options)
|
||||
|
||||
const addrs = [
|
||||
multiaddr('valid-multiaddr-for-your-transport'),
|
||||
multiaddr('valid-multiaddr2-for-your-transport')
|
||||
]
|
||||
|
||||
const network = require('my-network-lib')
|
||||
const connect = network.connect
|
||||
const connector = {
|
||||
delay (delayMs) {
|
||||
// Add a delay in the connection mechanism for the transport
|
||||
// (this is used by the dial tests)
|
||||
network.connect = (...args) => setTimeout(() => connect(...args), delayMs)
|
||||
},
|
||||
restore () {
|
||||
// Restore the connection mechanism to normal
|
||||
network.connect = connect
|
||||
}
|
||||
}
|
||||
|
||||
return { transport, addrs, connector }
|
||||
},
|
||||
teardown () {
|
||||
// Clean up any resources created by setup()
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
# API
|
||||
|
||||
A valid transport (one that follows the interface defined) must implement the following API:
|
||||
|
||||
**Table of contents:**
|
||||
|
||||
- type: `Transport`
|
||||
- `new Transport({ upgrader, ...[options] })`
|
||||
- `<Promise> transport.dial(multiaddr, [options])`
|
||||
- `<Multiaddr[]> transport.filter(multiaddrs)`
|
||||
- `transport.createListener([options], handlerFunction)`
|
||||
- type: `transport.Listener`
|
||||
- event: 'listening'
|
||||
- event: 'close'
|
||||
- event: 'connection'
|
||||
- event: 'error'
|
||||
- `<Promise> listener.listen(multiaddr)`
|
||||
- `listener.getAddrs()`
|
||||
- `<Promise> listener.close([options])`
|
||||
|
||||
### Types
|
||||
|
||||
#### Upgrader
|
||||
Upgraders have 2 methods: `upgradeOutbound` and `upgradeInbound`.
|
||||
- `upgradeOutbound` must be called and returned by `transport.dial`.
|
||||
- `upgradeInbound` must be called and the results must be passed to the `createListener` `handlerFunction` and the `connection` event handler, anytime a new connection is created.
|
||||
|
||||
```js
|
||||
const connection = await upgrader.upgradeOutbound(multiaddrConnection)
|
||||
const connection = await upgrader.upgradeInbound(multiaddrConnection)
|
||||
```
|
||||
|
||||
The `Upgrader` methods take a [MultiaddrConnection](#multiaddrconnection) and will return an `interface-connection` instance.
|
||||
|
||||
#### MultiaddrConnection
|
||||
|
||||
- `MultiaddrConnection`
|
||||
- `sink<function(source)>`: A [streaming iterable sink](https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#sink-it)
|
||||
- `source<AsyncIterator>`: A [streaming iterable source](https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#source-it)
|
||||
- `close<function(Error)>`: A method for closing the connection
|
||||
- `conn`: The raw connection of the transport, such as a TCP socket.
|
||||
- `remoteAddr<Multiaddr>`: The remote `Multiaddr` of the connection.
|
||||
- `[localAddr<Multiaddr>]`: An optional local `Multiaddr` of the connection.
|
||||
- `timeline<object>`: A hash map of connection time events
|
||||
- `open<number>`: The time in ticks the connection was opened
|
||||
- `close<number>`: The time in ticks the connection was closed
|
||||
|
||||
### Creating a transport instance
|
||||
|
||||
- `const transport = new Transport({ upgrader, ...[options] })`
|
||||
|
||||
Creates a new Transport instance. `options` is an JavaScript object that should include the necessary parameters for the transport instance. Options **MUST** include an `Upgrader` instance, as Transports will use this to return `interface-connection` instances from `transport.dial` and the listener `handlerFunction`.
|
||||
|
||||
**Note: Why is it important to instantiate a transport -** Some transports have state that can be shared between the dialing and listening parts. For example with libp2p-webrtc-star, in order to dial a peer, the peer must be part of some signaling network that is shared with the listener.
|
||||
|
||||
### Dial to another peer
|
||||
|
||||
- `const connection = await transport.dial(multiaddr, [options])`
|
||||
|
||||
This method uses a transport to dial a Peer listening on `multiaddr`.
|
||||
|
||||
`multiaddr` must be of the type [`multiaddr`](https://www.npmjs.com/multiaddr).
|
||||
|
||||
`[options]` the options that may be passed to the dial. Must support the `signal` option (see below)
|
||||
|
||||
Dial **MUST** call and return `upgrader.upgradeOutbound(multiaddrConnection)`. The upgrader will return an [interface-connection](https://github.com/libp2p/interface-connection) instance.
|
||||
|
||||
The dial may throw an `Error` instance if there was a problem connecting to the `multiaddr`.
|
||||
|
||||
### Canceling a dial
|
||||
|
||||
Dials may be cancelled using an `AbortController`:
|
||||
|
||||
```Javascript
|
||||
const AbortController = require('abort-controller')
|
||||
const { AbortError } = require('interface-transport')
|
||||
const controller = new AbortController()
|
||||
try {
|
||||
const conn = await mytransport.dial(ma, { signal: controller.signal })
|
||||
// Do stuff with conn here ...
|
||||
} catch (err) {
|
||||
if(err.code === AbortError.code) {
|
||||
// Dial was aborted, just bail out
|
||||
return
|
||||
}
|
||||
throw err
|
||||
}
|
||||
|
||||
// ----
|
||||
// In some other part of the code:
|
||||
controller.abort()
|
||||
// ----
|
||||
```
|
||||
|
||||
### Filtering Addresses
|
||||
|
||||
- `const supportedAddrs = await transport.filter(multiaddrs)`
|
||||
|
||||
When using a transport its important to be able to filter out `multiaddr`s that the transport doesn't support. A transport instance provides a filter method to return only the valid addresses it supports.
|
||||
|
||||
`multiaddrs` must be an array of type [`multiaddr`](https://www.npmjs.com/multiaddr).
|
||||
Filter returns an array of `multiaddr`.
|
||||
|
||||
### Create a listener
|
||||
|
||||
- `const listener = transport.createListener([options], handlerFunction)`
|
||||
|
||||
This method creates a listener on the transport. Implementations **MUST** call `upgrader.upgradeInbound(multiaddrConnection)` and pass its results to the `handlerFunction` and any emitted `connection` events.
|
||||
|
||||
`options` is an optional object that contains the properties the listener must have, in order to properly listen on a given transport/socket.
|
||||
|
||||
`handlerFunction` is a function called each time a new connection is received. It must follow the following signature: `function (conn) {}`, where `conn` is a connection that follows the [`interface-connection`](https://github.com/diasdavid/interface-connection).
|
||||
|
||||
The listener object created may emit the following events:
|
||||
|
||||
- `listening` - when the listener is ready for incoming connections
|
||||
- `close` - when the listener is closed
|
||||
- `connection` - (`conn`) each time an incoming connection is received
|
||||
- `error` - (`err`) each time there is an error on the connection
|
||||
|
||||
### Start a listener
|
||||
|
||||
- `await listener.listen(multiaddr)`
|
||||
|
||||
This method puts the listener in `listening` mode, waiting for incoming connections.
|
||||
|
||||
`multiaddr` is the address that the listener should bind to.
|
||||
|
||||
### Get listener addrs
|
||||
|
||||
- `listener.getAddrs()`
|
||||
|
||||
This method returns the addresses on which this listener is listening. Useful when listening on port 0 or any interface (0.0.0.0).
|
||||
|
||||
### Stop a listener
|
||||
|
||||
- `await listener.close([options])`
|
||||
|
||||
This method closes the listener so that no more connections can be opened on this transport instance.
|
||||
|
||||
`options` is an optional object that may contain the following properties:
|
||||
|
||||
- `timeout` - A timeout value (in ms) after which all connections on this transport will be destroyed if the transport is not able to close gracefully. (e.g { timeout: 1000 })
|
80
src/transport/adapter.js
Normal file
@ -0,0 +1,80 @@
|
||||
'use strict'
|
||||
|
||||
const { Connection } = require('interface-connection')
|
||||
const toPull = require('async-iterator-to-pull-stream')
|
||||
const error = require('pull-stream/sources/error')
|
||||
const drain = require('pull-stream/sinks/drain')
|
||||
const noop = () => {}
|
||||
|
||||
function callbackify (fn) {
|
||||
return async function (...args) {
|
||||
let cb = args.pop()
|
||||
if (typeof cb !== 'function') {
|
||||
args.push(cb)
|
||||
cb = noop
|
||||
}
|
||||
let res
|
||||
try {
|
||||
res = await fn(...args)
|
||||
} catch (err) {
|
||||
return cb(err)
|
||||
}
|
||||
cb(null, res)
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy adapter to old transport & connection interface
|
||||
class Adapter {
|
||||
constructor (transport) {
|
||||
this.transport = transport
|
||||
}
|
||||
|
||||
dial (ma, options, callback) {
|
||||
if (typeof options === 'function') {
|
||||
callback = options
|
||||
options = {}
|
||||
}
|
||||
|
||||
callback = callback || noop
|
||||
|
||||
const conn = new Connection()
|
||||
|
||||
this.transport.dial(ma, options)
|
||||
.then(socket => {
|
||||
conn.setInnerConn(toPull.duplex(socket))
|
||||
conn.getObservedAddrs = callbackify(socket.getObservedAddrs.bind(socket))
|
||||
conn.close = callbackify(socket.close.bind(socket))
|
||||
callback(null, conn)
|
||||
})
|
||||
.catch(err => {
|
||||
conn.setInnerConn({ sink: drain(), source: error(err) })
|
||||
callback(err)
|
||||
})
|
||||
|
||||
return conn
|
||||
}
|
||||
|
||||
createListener (options, handler) {
|
||||
if (typeof options === 'function') {
|
||||
handler = options
|
||||
options = {}
|
||||
}
|
||||
|
||||
const server = this.transport.createListener(options, socket => {
|
||||
const conn = new Connection(toPull.duplex(socket))
|
||||
conn.getObservedAddrs = callbackify(socket.getObservedAddrs.bind(socket))
|
||||
handler(conn)
|
||||
})
|
||||
|
||||
const proxy = {
|
||||
listen: callbackify(server.listen.bind(server)),
|
||||
close: callbackify(server.close.bind(server)),
|
||||
getAddrs: callbackify(server.getAddrs.bind(server)),
|
||||
getObservedAddrs: callbackify(() => server.getObservedAddrs())
|
||||
}
|
||||
|
||||
return new Proxy(server, { get: (_, prop) => proxy[prop] || server[prop] })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Adapter
|
203
src/transport/dial-test.js
Normal file
@ -0,0 +1,203 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
|
||||
const { isValidTick } = require('./utils')
|
||||
const goodbye = require('it-goodbye')
|
||||
const { collect } = require('streaming-iterables')
|
||||
const pipe = require('it-pipe')
|
||||
const AbortController = require('abort-controller')
|
||||
const AbortError = require('./errors').AbortError
|
||||
const sinon = require('sinon')
|
||||
|
||||
module.exports = (common) => {
|
||||
const upgrader = {
|
||||
_upgrade (multiaddrConnection) {
|
||||
['sink', 'source', 'remoteAddr', 'conn', 'timeline', 'close'].forEach(prop => {
|
||||
expect(multiaddrConnection).to.have.property(prop)
|
||||
})
|
||||
expect(isValidTick(multiaddrConnection.timeline.open)).to.equal(true)
|
||||
return multiaddrConnection
|
||||
},
|
||||
upgradeOutbound (multiaddrConnection) {
|
||||
return upgrader._upgrade(multiaddrConnection)
|
||||
},
|
||||
upgradeInbound (multiaddrConnection) {
|
||||
return upgrader._upgrade(multiaddrConnection)
|
||||
}
|
||||
}
|
||||
|
||||
describe('dial', () => {
|
||||
let addrs
|
||||
let transport
|
||||
let connector
|
||||
let listener
|
||||
|
||||
before(async () => {
|
||||
({ addrs, transport, connector } = await common.setup({ upgrader }))
|
||||
})
|
||||
|
||||
after(() => common.teardown && common.teardown())
|
||||
|
||||
beforeEach(() => {
|
||||
listener = transport.createListener((conn) => pipe(conn, conn))
|
||||
return listener.listen(addrs[0])
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore()
|
||||
return listener.close()
|
||||
})
|
||||
|
||||
it('simple', async () => {
|
||||
const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound')
|
||||
const conn = await transport.dial(addrs[0])
|
||||
|
||||
const s = goodbye({ source: ['hey'], sink: collect })
|
||||
|
||||
const result = await pipe(s, conn, s)
|
||||
|
||||
expect(upgradeSpy.callCount).to.equal(1)
|
||||
expect(upgradeSpy.returned(conn)).to.equal(true)
|
||||
expect(result.length).to.equal(1)
|
||||
expect(result[0].toString()).to.equal('hey')
|
||||
})
|
||||
|
||||
it('can close connections', async () => {
|
||||
const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound')
|
||||
const conn = await transport.dial(addrs[0])
|
||||
|
||||
expect(upgradeSpy.callCount).to.equal(1)
|
||||
expect(upgradeSpy.returned(conn)).to.equal(true)
|
||||
await conn.close()
|
||||
expect(isValidTick(conn.timeline.close)).to.equal(true)
|
||||
})
|
||||
|
||||
it('to non existent listener', async () => {
|
||||
const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound')
|
||||
try {
|
||||
await transport.dial(addrs[1])
|
||||
} catch (_) {
|
||||
expect(upgradeSpy.callCount).to.equal(0)
|
||||
// Success: expected an error to be throw
|
||||
return
|
||||
}
|
||||
expect.fail('Did not throw error attempting to connect to non-existent listener')
|
||||
})
|
||||
|
||||
it('abort before dialing throws AbortError', async () => {
|
||||
const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound')
|
||||
const controller = new AbortController()
|
||||
controller.abort()
|
||||
const socket = transport.dial(addrs[0], { signal: controller.signal })
|
||||
|
||||
try {
|
||||
await socket
|
||||
} catch (err) {
|
||||
expect(upgradeSpy.callCount).to.equal(0)
|
||||
expect(err.code).to.eql(AbortError.code)
|
||||
expect(err.type).to.eql(AbortError.type)
|
||||
return
|
||||
}
|
||||
expect.fail('Did not throw error with code ' + AbortError.code)
|
||||
})
|
||||
|
||||
it('abort while dialing throws AbortError', async () => {
|
||||
const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound')
|
||||
// Add a delay to connect() so that we can abort while the dial is in
|
||||
// progress
|
||||
connector.delay(100)
|
||||
|
||||
const controller = new AbortController()
|
||||
const socket = transport.dial(addrs[0], { signal: controller.signal })
|
||||
setTimeout(() => controller.abort(), 50)
|
||||
|
||||
try {
|
||||
await socket
|
||||
} catch (err) {
|
||||
expect(upgradeSpy.callCount).to.equal(0)
|
||||
expect(err.code).to.eql(AbortError.code)
|
||||
expect(err.type).to.eql(AbortError.type)
|
||||
return
|
||||
} finally {
|
||||
connector.restore()
|
||||
}
|
||||
expect.fail('Did not throw error with code ' + AbortError.code)
|
||||
})
|
||||
|
||||
it('abort while reading throws AbortError', async () => {
|
||||
// Add a delay to the response from the server
|
||||
async function * delayedResponse (source) {
|
||||
for await (const val of source) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
yield val
|
||||
}
|
||||
}
|
||||
const delayedListener = transport.createListener(async (conn) => {
|
||||
await pipe(conn, delayedResponse, conn)
|
||||
})
|
||||
await delayedListener.listen(addrs[1])
|
||||
|
||||
// Create an abort signal and dial the socket
|
||||
const controller = new AbortController()
|
||||
const socket = await transport.dial(addrs[1], { signal: controller.signal })
|
||||
|
||||
try {
|
||||
// Set a timeout to abort before the server responds
|
||||
setTimeout(() => controller.abort(), 100)
|
||||
|
||||
// An AbortError should be thrown before the pipe completes
|
||||
const s = goodbye({ source: ['hey'], sink: collect })
|
||||
await pipe(s, socket, s)
|
||||
} catch (err) {
|
||||
expect(err.code).to.eql(AbortError.code)
|
||||
expect(err.type).to.eql(AbortError.type)
|
||||
return
|
||||
} finally {
|
||||
await delayedListener.close()
|
||||
}
|
||||
expect.fail('Did not throw error with code ' + AbortError.code)
|
||||
})
|
||||
|
||||
it('abort while writing does not throw AbortError', async () => {
|
||||
// Record values received by the listener
|
||||
const recorded = []
|
||||
async function * recorderTransform (source) {
|
||||
for await (const val of source) {
|
||||
recorded.push(val)
|
||||
yield val
|
||||
}
|
||||
}
|
||||
const recordListener = transport.createListener(async (conn) => {
|
||||
await pipe(conn, recorderTransform, conn)
|
||||
})
|
||||
await recordListener.listen(addrs[1])
|
||||
|
||||
// Create an abort signal and dial the socket
|
||||
const controller = new AbortController()
|
||||
const socket = await transport.dial(addrs[1], { signal: controller.signal })
|
||||
|
||||
// Set a timeout to abort before writing has completed
|
||||
setTimeout(() => controller.abort(), 100)
|
||||
|
||||
try {
|
||||
// The pipe should write to the socket until aborted
|
||||
await pipe(
|
||||
async function * () {
|
||||
yield 'hey'
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
yield 'there'
|
||||
},
|
||||
socket)
|
||||
expect(recorded.length).to.eql(1)
|
||||
expect(recorded[0].toString()).to.eql('hey')
|
||||
} finally {
|
||||
await recordListener.close()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
21
src/transport/errors.js
Normal file
@ -0,0 +1,21 @@
|
||||
'use strict'
|
||||
|
||||
class AbortError extends Error {
|
||||
constructor () {
|
||||
super('The operation was aborted')
|
||||
this.code = AbortError.code
|
||||
this.type = AbortError.type
|
||||
}
|
||||
|
||||
static get code () {
|
||||
return 'ABORT_ERR'
|
||||
}
|
||||
|
||||
static get type () {
|
||||
return 'aborted'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
AbortError
|
||||
}
|
37
src/transport/filter-test.js
Normal file
@ -0,0 +1,37 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
|
||||
module.exports = (common) => {
|
||||
const upgrader = {
|
||||
_upgrade (multiaddrConnection) {
|
||||
return multiaddrConnection
|
||||
},
|
||||
upgradeOutbound (multiaddrConnection) {
|
||||
return upgrader._upgrade(multiaddrConnection)
|
||||
},
|
||||
upgradeInbound (multiaddrConnection) {
|
||||
return upgrader._upgrade(multiaddrConnection)
|
||||
}
|
||||
}
|
||||
|
||||
describe('filter', () => {
|
||||
let addrs
|
||||
let transport
|
||||
|
||||
before(async () => {
|
||||
({ addrs, transport } = await common.setup({ upgrader }))
|
||||
})
|
||||
|
||||
after(() => common.teardown && common.teardown())
|
||||
|
||||
it('filters addresses', () => {
|
||||
const filteredAddrs = transport.filter(addrs)
|
||||
expect(filteredAddrs).to.eql(addrs)
|
||||
})
|
||||
})
|
||||
}
|
BIN
src/transport/img/badge.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
src/transport/img/badge.sketch
Normal file
19
src/transport/img/badge.svg
Normal file
After Width: | Height: | Size: 9.5 KiB |
17
src/transport/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const dial = require('./dial-test')
|
||||
const listen = require('./listen-test')
|
||||
const filter = require('./filter-test')
|
||||
|
||||
module.exports = (common) => {
|
||||
describe('interface-transport', () => {
|
||||
dial(common)
|
||||
listen(common)
|
||||
filter(common)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.AbortError = require('./errors').AbortError
|
||||
module.exports.Adapter = require('./adapter')
|
144
src/transport/listen-test.js
Normal file
@ -0,0 +1,144 @@
|
||||
/* eslint max-nested-callbacks: ["error", 8] */
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const dirtyChai = require('dirty-chai')
|
||||
const expect = chai.expect
|
||||
chai.use(dirtyChai)
|
||||
const sinon = require('sinon')
|
||||
|
||||
const pipe = require('it-pipe')
|
||||
const { isValidTick } = require('./utils')
|
||||
|
||||
module.exports = (common) => {
|
||||
const upgrader = {
|
||||
_upgrade (multiaddrConnection) {
|
||||
['sink', 'source', 'remoteAddr', 'conn', 'timeline', 'close'].forEach(prop => {
|
||||
expect(multiaddrConnection).to.have.property(prop)
|
||||
})
|
||||
expect(isValidTick(multiaddrConnection.timeline.open)).to.equal(true)
|
||||
|
||||
return multiaddrConnection
|
||||
},
|
||||
upgradeOutbound (multiaddrConnection) {
|
||||
return upgrader._upgrade(multiaddrConnection)
|
||||
},
|
||||
upgradeInbound (multiaddrConnection) {
|
||||
return upgrader._upgrade(multiaddrConnection)
|
||||
}
|
||||
}
|
||||
|
||||
describe('listen', () => {
|
||||
let addrs
|
||||
let transport
|
||||
|
||||
before(async () => {
|
||||
({ transport, addrs } = await common.setup({ upgrader }))
|
||||
})
|
||||
|
||||
after(() => common.teardown && common.teardown())
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
it('simple', async () => {
|
||||
const listener = transport.createListener((conn) => {})
|
||||
await listener.listen(addrs[0])
|
||||
await listener.close()
|
||||
})
|
||||
|
||||
it('close listener with connections, through timeout', async () => {
|
||||
const upgradeSpy = sinon.spy(upgrader, 'upgradeInbound')
|
||||
const listenerConns = []
|
||||
|
||||
const listener = transport.createListener((conn) => {
|
||||
listenerConns.push(conn)
|
||||
expect(upgradeSpy.returned(conn)).to.equal(true)
|
||||
pipe(conn, conn)
|
||||
})
|
||||
|
||||
// Listen
|
||||
await listener.listen(addrs[0])
|
||||
|
||||
// Create two connections to the listener
|
||||
const [socket1] = await Promise.all([
|
||||
transport.dial(addrs[0]),
|
||||
transport.dial(addrs[0])
|
||||
])
|
||||
|
||||
// Give the listener a chance to finish its upgrade
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
|
||||
// Wait for the data send and close to finish
|
||||
await Promise.all([
|
||||
pipe(
|
||||
[Buffer.from('Some data that is never handled')],
|
||||
socket1
|
||||
),
|
||||
// Closer the listener (will take a couple of seconds to time out)
|
||||
listener.close()
|
||||
])
|
||||
|
||||
await socket1.close()
|
||||
|
||||
expect(isValidTick(socket1.timeline.close)).to.equal(true)
|
||||
listenerConns.forEach(conn => {
|
||||
expect(isValidTick(conn.timeline.close)).to.equal(true)
|
||||
})
|
||||
|
||||
// 2 dials = 2 connections upgraded
|
||||
expect(upgradeSpy.callCount).to.equal(2)
|
||||
})
|
||||
|
||||
describe('events', () => {
|
||||
it('connection', (done) => {
|
||||
const upgradeSpy = sinon.spy(upgrader, 'upgradeInbound')
|
||||
const listener = transport.createListener()
|
||||
|
||||
listener.on('connection', async (conn) => {
|
||||
expect(upgradeSpy.returned(conn)).to.equal(true)
|
||||
expect(upgradeSpy.callCount).to.equal(1)
|
||||
expect(conn).to.exist()
|
||||
await listener.close()
|
||||
done()
|
||||
})
|
||||
|
||||
;(async () => {
|
||||
await listener.listen(addrs[0])
|
||||
await transport.dial(addrs[0])
|
||||
})()
|
||||
})
|
||||
|
||||
it('listening', (done) => {
|
||||
const listener = transport.createListener()
|
||||
listener.on('listening', async () => {
|
||||
await listener.close()
|
||||
done()
|
||||
})
|
||||
listener.listen(addrs[0])
|
||||
})
|
||||
|
||||
it('error', (done) => {
|
||||
const listener = transport.createListener()
|
||||
listener.on('error', async (err) => {
|
||||
expect(err).to.exist()
|
||||
await listener.close()
|
||||
done()
|
||||
})
|
||||
listener.emit('error', new Error('my err'))
|
||||
})
|
||||
|
||||
it('close', (done) => {
|
||||
const listener = transport.createListener()
|
||||
listener.on('close', done)
|
||||
|
||||
;(async () => {
|
||||
await listener.listen(addrs[0])
|
||||
await listener.close()
|
||||
})()
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
65
src/transport/package.json
Normal file
@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "interface-transport",
|
||||
"version": "0.7.0",
|
||||
"description": "A test suite and interface you can use to implement a transport.",
|
||||
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/libp2p/interface-transport.git"
|
||||
},
|
||||
"main": "src/index.js",
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"lint": "aegir lint",
|
||||
"test": "aegir test",
|
||||
"build": "aegir build",
|
||||
"release": "aegir release --no-test",
|
||||
"release-minor": "aegir release --type minor --no-test",
|
||||
"release-major": "aegir release --type major --no-test",
|
||||
"coverage": "exit(0)",
|
||||
"coverage-publish": "exit(0)"
|
||||
},
|
||||
"pre-push": [
|
||||
"lint"
|
||||
],
|
||||
"keywords": [
|
||||
"IPFS"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/libp2p/interface-transport/issues"
|
||||
},
|
||||
"homepage": "https://github.com/libp2p/interface-transport",
|
||||
"devDependencies": {
|
||||
"aegir": "^20.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"abort-controller": "^3.0.0",
|
||||
"async-iterator-to-pull-stream": "^1.3.0",
|
||||
"chai": "^4.2.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"interface-connection": "~0.3.3",
|
||||
"it-goodbye": "^2.0.0",
|
||||
"it-pipe": "^1.0.0",
|
||||
"multiaddr": "^7.0.0",
|
||||
"pull-stream": "^3.6.9",
|
||||
"sinon": "^7.4.2",
|
||||
"streaming-iterables": "^4.1.0"
|
||||
},
|
||||
"contributors": [
|
||||
"Alan Shaw <alan.shaw@protocol.ai>",
|
||||
"David Dias <daviddias.p@gmail.com>",
|
||||
"Friedel Ziegelmayer <dignifiedquire@gmail.com>",
|
||||
"Jacob Heun <jacobheun@gmail.com>",
|
||||
"João Santos <joaosantos15@users.noreply.github.com>",
|
||||
"Maciej Krüger <mkg20001@gmail.com>",
|
||||
"Richard Littauer <richard.littauer@gmail.com>",
|
||||
"Vasco Santos <vasco.santos@ua.pt>",
|
||||
"dirkmc <dirkmdev@gmail.com>",
|
||||
"dmitriy ryajov <dryajov@dmitriys-MBP.HomeNET>",
|
||||
"greenkeeperio-bot <support@greenkeeper.io>"
|
||||
]
|
||||
}
|
1
src/transport/test/transport.spec.js
Normal file
@ -0,0 +1 @@
|
||||
'use strict'
|
16
src/transport/utils/index.js
Normal file
@ -0,0 +1,16 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
isValidTick: function isValidTick (date, ms = 5000) {
|
||||
const now = Date.now()
|
||||
if (date > now - ms && date <= now) return true
|
||||
return false
|
||||
}
|
||||
}
|