Compare commits

...

18 Commits

Author SHA1 Message Date
3f6c1cbee1 chore: release version v0.1.5 2019-11-15 14:51:53 +01:00
64c79d92e0 chore: update contributors 2019-11-15 14:51:52 +01:00
a67abccabf feat: add class-is to topology (#11) 2019-11-15 14:49:39 +01:00
21d8ae6d96 fix: multicodec topology update peers with multicodec (#10) 2019-11-15 14:48:43 +01:00
5969270ce6 nit :) 2019-11-14 12:37:20 +00:00
432da545b4 chore: release version v0.1.4 2019-11-14 11:54:38 +01:00
b00bcd453d chore: update contributors 2019-11-14 11:54:37 +01:00
8bee747c7c feat: add topology interfaces (#7)
* feat: topology

* feat: multicodec-topology

* chore: address review

Co-Authored-By: Jacob Heun <jacobheun@gmail.com>

* chore: remove error from disconnect

* docs: topology

* chore: apply suggestions from code review

Co-Authored-By: Jacob Heun <jacobheun@gmail.com>
2019-11-14 11:52:06 +01:00
b960f29757 chore: release version v0.1.3 2019-10-30 17:10:37 +01:00
5206862504 chore: update contributors 2019-10-30 17:10:37 +01:00
749a8d035d fix: localAddr should be optional (#6)
The local address of a connection will not always be known, such as a browser client, so it should not be required.
2019-10-30 16:56:28 +01:00
763187beb1 chore: release version v0.1.2 2019-10-29 12:15:58 +01:00
44e08c9007 chore: update contributors 2019-10-29 12:15:57 +01:00
d2fe2d1b36 feat: crypto errors (#4)
* chore: ignore docs folder

* feat: add invalid crypto exchange error
2019-10-29 12:12:09 +01:00
bcb52ae709 docs: add crypto to readme (#5) 2019-10-29 11:24:28 +01:00
c52c3dc8a3 chore: release version v0.1.1 2019-10-21 14:45:40 +02:00
92f13f8106 chore: update contributors 2019-10-21 14:45:39 +02:00
5a5c44a770 feat: crypto interface (#2)
* docs: initial crypto readme

* feat: add basic crypto interface test suite

* feat: add optional remotepeer for inbound

feat: add errors export

* docs(fix): update src/crypto/README.md

Co-Authored-By: Vasco Santos <vasco.santos@moxy.studio>
2019-10-21 14:44:17 +02:00
21 changed files with 878 additions and 6 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@
build build
dist dist
docs
# Dependency directory # Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git

View File

@ -1,3 +1,58 @@
<a name="0.1.5"></a>
## [0.1.5](https://github.com/libp2p/js-interfaces/compare/v0.1.4...v0.1.5) (2019-11-15)
### Bug Fixes
* multicodec topology update peers with multicodec ([#10](https://github.com/libp2p/js-interfaces/issues/10)) ([21d8ae6](https://github.com/libp2p/js-interfaces/commit/21d8ae6))
### Features
* add class-is to topology ([#11](https://github.com/libp2p/js-interfaces/issues/11)) ([a67abcc](https://github.com/libp2p/js-interfaces/commit/a67abcc))
<a name="0.1.4"></a>
## [0.1.4](https://github.com/libp2p/js-interfaces/compare/v0.1.3...v0.1.4) (2019-11-14)
### Features
* add topology interfaces ([#7](https://github.com/libp2p/js-interfaces/issues/7)) ([8bee747](https://github.com/libp2p/js-interfaces/commit/8bee747))
<a name="0.1.3"></a>
## [0.1.3](https://github.com/libp2p/js-interfaces/compare/v0.1.2...v0.1.3) (2019-10-30)
### Bug Fixes
* localAddr should be optional ([#6](https://github.com/libp2p/js-interfaces/issues/6)) ([749a8d0](https://github.com/libp2p/js-interfaces/commit/749a8d0))
<a name="0.1.2"></a>
## [0.1.2](https://github.com/libp2p/js-interfaces/compare/v0.1.1...v0.1.2) (2019-10-29)
### Features
* crypto errors ([#4](https://github.com/libp2p/js-interfaces/issues/4)) ([d2fe2d1](https://github.com/libp2p/js-interfaces/commit/d2fe2d1))
<a name="0.1.1"></a>
## [0.1.1](https://github.com/libp2p/js-interfaces/compare/v0.1.0...v0.1.1) (2019-10-21)
### Features
* crypto interface ([#2](https://github.com/libp2p/js-interfaces/issues/2)) ([5a5c44a](https://github.com/libp2p/js-interfaces/commit/5a5c44a))
<a name="0.1.0"></a> <a name="0.1.0"></a>
# 0.1.0 (2019-10-20) # 0.1.0 (2019-10-20)

View File

@ -1,4 +1,4 @@
# JS Libp2p Interfaces # JS libp2p Interfaces
[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://protocol.ai) [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://protocol.ai)
[![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) [![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/)
@ -15,9 +15,11 @@
- [Connection](./src/connection) - [Connection](./src/connection)
- [Content Routing](./src/content-routing) - [Content Routing](./src/content-routing)
- [Crypto](./src/crypto)
- [Peer Discovery](./src/peer-discovery) - [Peer Discovery](./src/peer-discovery)
- [Peer Routing](./src/peer-routing) - [Peer Routing](./src/peer-routing)
- [Stream Muxer](./src/stream-muxer) - [Stream Muxer](./src/stream-muxer)
- [Topology](./src/topology)
- [Transport](./src/transport) - [Transport](./src/transport)
### Origin Repositories ### Origin Repositories

View File

@ -1,6 +1,6 @@
{ {
"name": "libp2p-interfaces", "name": "libp2p-interfaces",
"version": "0.1.0", "version": "0.1.5",
"description": "Interfaces for JS Libp2p", "description": "Interfaces for JS Libp2p",
"main": "src/index.js", "main": "src/index.js",
"files": [ "files": [
@ -51,6 +51,7 @@
}, },
"devDependencies": { "devDependencies": {
"aegir": "^20.4.1", "aegir": "^20.4.1",
"it-handshake": "^1.0.0",
"it-pair": "^1.0.0", "it-pair": "^1.0.0",
"it-pipe": "^1.0.1", "it-pipe": "^1.0.1",
"peer-info": "^0.17.0" "peer-info": "^0.17.0"

View File

@ -109,7 +109,7 @@ const conn = new Connection({
Creates a new Connection instance. Creates a new Connection instance.
`localAddr` is the [multiaddr](https://github.com/multiformats/multiaddr) address used by the local peer to reach the remote. `localAddr` is the optional [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. `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. `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. `remotePeer` is the [PeerId](https://github.com/libp2p/js-peer-id) of the remote peer.

View File

@ -16,7 +16,7 @@ class Connection {
/** /**
* Creates an instance of Connection. * Creates an instance of Connection.
* @param {object} properties properties of the connection. * @param {object} properties properties of the connection.
* @param {multiaddr} properties.localAddr local multiaddr of the connection. * @param {multiaddr} [properties.localAddr] local multiaddr of the connection if known.
* @param {multiaddr} properties.remoteAddr remote multiaddr of the connection. * @param {multiaddr} properties.remoteAddr remote multiaddr of the connection.
* @param {PeerId} properties.localPeer local peer-id. * @param {PeerId} properties.localPeer local peer-id.
* @param {PeerId} properties.remotePeer remote peer-id. * @param {PeerId} properties.remotePeer remote peer-id.
@ -32,7 +32,7 @@ class Connection {
* @param {string} [properties.stat.encryption] connection encryption method identifier. * @param {string} [properties.stat.encryption] connection encryption method identifier.
*/ */
constructor ({ localAddr, remoteAddr, localPeer, remotePeer, newStream, close, getStreams, stat }) { constructor ({ localAddr, remoteAddr, localPeer, remotePeer, newStream, close, getStreams, stat }) {
assert(multiaddr.isMultiaddr(localAddr), 'localAddr must be an instance of multiaddr') localAddr && assert(multiaddr.isMultiaddr(localAddr), 'localAddr must be an instance of multiaddr')
assert(multiaddr.isMultiaddr(remoteAddr), 'remoteAddr 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(localPeer), 'localPeer must be an instance of peer-id')
assert(PeerId.isPeerId(remotePeer), 'remotePeer must be an instance of peer-id') assert(PeerId.isPeerId(remotePeer), 'remotePeer must be an instance of peer-id')

98
src/crypto/README.md Normal file
View File

@ -0,0 +1,98 @@
interface-crypto
==================
> A test suite you can use to implement a libp2p crypto module. A libp2p crypto module is used to ensure all exchanged data between two peers is encrypted.
**Modules that implement the interface**
- [js-libp2p-secio](https://github.com/libp2p/js-libp2p-secio)
## Table of Contents
- [interface-crypto](#interface-crypto)
- [Table of Contents](#table-of-contents)
- [Using the Test Suite](#using-the-test-suite)
- [API](#api)
- [Secure Inbound](#secure-inbound)
- [Secure Outbound](#secure-outbound)
- [Crypto Errors](#crypto-errors)
- [Error Types](#error-types)
## Using the Test Suite
You can also check out the [internal test suite](../../test/crypto/compliance.spec.js) to see the setup in action.
```js
const tests = require('libp2p-interfaces/src/crypto/tests')
const yourCrypto = require('./your-crypto')
tests({
setup () {
// Set up your crypto if needed, then return it
return yourCrypto
},
teardown () {
// Clean up your crypto if needed
}
})
```
## API
- `Crypto`
- `protocol<string>`: The protocol id of the crypto module.
- `secureInbound<function(PeerId, duplex)>`: Secures inbound connections.
- `secureOutbound<function(PeerId, duplex, PeerId)>`: Secures outbound connections.
### Secure Inbound
- `const { conn, remotePeer } = await crypto.secureInbound(localPeer, duplex, [remotePeer])`
Secures an inbound [streaming iterable duplex][iterable-duplex] connection. It returns an encrypted [streaming iterable duplex][iterable-duplex], as well as the [PeerId][peer-id] of the remote peer.
**Parameters**
- `localPeer` is the [PeerId][peer-id] of the receiving peer.
- `duplex` is the [streaming iterable duplex][iterable-duplex] that will be encryption.
- `remotePeer` is the optional [PeerId][peer-id] of the initiating peer, if known. This may only exist during transport upgrades.
**Return Value**
- `<object>`
- `conn<duplex>`: An encrypted [streaming iterable duplex][iterable-duplex].
- `remotePeer<PeerId>`: The [PeerId][peer-id] of the remote peer.
### Secure Outbound
- `const { conn, remotePeer } = await crypto.secureOutbound(localPeer, duplex, remotePeer)`
Secures an outbound [streaming iterable duplex][iterable-duplex] connection. It returns an encrypted [streaming iterable duplex][iterable-duplex], as well as the [PeerId][peer-id] of the remote peer.
**Parameters**
- `localPeer` is the [PeerId][peer-id] of the receiving peer.
- `duplex` is the [streaming iterable duplex][iterable-duplex] that will be encrypted.
- `remotePeer` is the [PeerId][peer-id] of the remote peer. If provided, implementations **should** use this to validate the integrity of the remote peer.
**Return Value**
- `<object>`
- `conn<duplex>`: An encrypted [streaming iterable duplex][iterable-duplex].
- `remotePeer<PeerId>`: The [PeerId][peer-id] of the remote peer. This **should** match the `remotePeer` parameter, and implementations should enforce this.
[peer-id]: https://github.com/libp2p/js-peer-id
[iterable-duplex]: https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#duplex-it
## Crypto Errors
Common crypto errors come with the interface, and can be imported directly. All Errors take an optional message.
```js
const {
InvalidCryptoExchangeError,
UnexpectedPeerError
} = require('libp2p-interfaces/src/crypto/errors')
const error = new UnexpectedPeerError('a custom error message')
console.log(error.code === UnexpectedPeerError.code) // true
```
### Error Types
- `InvalidCryptoExchangeError` - Should be thrown when a peer provides data that is insufficient to finish the crypto exchange.
- `UnexpectedPeerError` - Should be thrown when the expected peer id does not match the peer id determined via the crypto exchange.

28
src/crypto/errors.js Normal file
View File

@ -0,0 +1,28 @@
'use strict'
class UnexpectedPeerError extends Error {
constructor (message = 'Unexpected Peer') {
super(message)
this.code = UnexpectedPeerError.code
}
static get code () {
return 'ERR_UNEXPECTED_PEER'
}
}
class InvalidCryptoExchangeError extends Error {
constructor (message = 'Invalid crypto exchange') {
super(message)
this.code = InvalidCryptoExchangeError.code
}
static get code () {
return 'ERR_INVALID_CRYPTO_EXCHANGE'
}
}
module.exports = {
UnexpectedPeerError,
InvalidCryptoExchangeError
}

102
src/crypto/tests/index.js Normal file
View File

@ -0,0 +1,102 @@
/* eslint-env mocha */
'use strict'
const duplexPair = require('it-pair/duplex')
const pipe = require('it-pipe')
const peers = require('../../utils/peers')
const { UnexpectedPeerError } = require('../errors')
const PeerId = require('peer-id')
const { collect } = require('streaming-iterables')
const chai = require('chai')
const expect = chai.expect
chai.use(require('dirty-chai'))
module.exports = (common) => {
describe('interface-crypto', () => {
let crypto
let localPeer
let remotePeer
let mitmPeer
before(async () => {
[
crypto,
localPeer,
remotePeer,
mitmPeer
] = await Promise.all([
common.setup(),
PeerId.createFromJSON(peers[0]),
PeerId.createFromJSON(peers[1]),
PeerId.createFromJSON(peers[2])
])
})
after(() => common.teardown && common.teardown())
it('has a protocol string', () => {
expect(crypto.protocol).to.exist()
expect(crypto.protocol).to.be.a('string')
})
it('it wraps the provided duplex connection', async () => {
const [localConn, remoteConn] = duplexPair()
const [
inboundResult,
outboundResult
] = await Promise.all([
crypto.secureInbound(remotePeer, localConn),
crypto.secureOutbound(localPeer, remoteConn, remotePeer)
])
// Echo server
pipe(inboundResult.conn, inboundResult.conn)
// Send some data and collect the result
const input = Buffer.from('data to encrypt')
const result = await pipe(
[input],
outboundResult.conn,
// Convert BufferList to Buffer via slice
(source) => (async function * toBuffer () {
for await (const chunk of source) {
yield chunk.slice()
}
})(),
collect
)
expect(result).to.eql([input])
})
it('should return the remote peer id', async () => {
const [localConn, remoteConn] = duplexPair()
const [
inboundResult,
outboundResult
] = await Promise.all([
crypto.secureInbound(remotePeer, localConn),
crypto.secureOutbound(localPeer, remoteConn, remotePeer)
])
// Inbound should return the initiator (local) peer
expect(inboundResult.remotePeer.id).to.eql(localPeer.id)
// Outbound should return the receiver (remote) peer
expect(outboundResult.remotePeer.id).to.eql(remotePeer.id)
})
it('inbound connections should verify peer integrity if known', async () => {
const [localConn, remoteConn] = duplexPair()
await Promise.all([
crypto.secureInbound(remotePeer, localConn, mitmPeer),
crypto.secureOutbound(localPeer, remoteConn, remotePeer)
]).then(expect.fail, (err) => {
expect(err).to.exist()
expect(err).to.have.property('code', UnexpectedPeerError.code)
})
})
})
}

141
src/topology/README.md Normal file
View File

@ -0,0 +1,141 @@
interface-topology
========================
> Implementation of the topology interface used by the `js-libp2p` registrar.
Topologies can be used in conjunction with `js-libp2p` to help shape its network and the overlays of its subsystems, such as pubsub and the DHT.
## Table of Contents
- [Implementations](#implementations)
- [Install](#install)
- [Modules using the interface](#modulesUsingTheInterface)
- [Usage](#usage)
- [Api](#api)
## Implementations
### Topology
A libp2p topology with a group of common peers.
### Multicodec Topology
A libp2p topology with a group of peers that support the same protocol.
## Install
```sh
$ npm install libp2p-interfaces
```
## Modules using the interface
TBA
## Usage
### Topology
```js
const Topology = require('libp2p-interfaces/src/topology')
const toplogy = new Topology({
min: 0,
max: 50
})
```
### Multicodec Topology
```js
const MulticodecTopology = require('libp2p-interfaces/src/topology/multicodec-topology')
const toplogy = new MulticodecTopology({
min: 0,
max: 50,
multicodecs: ['/echo/1.0.0'],
handlers: {
onConnect: (peerInfo, conn) => {},
onDisconnect: (peerInfo) => {}
}
})
```
## API
The `MulticodecTopology` extends the `Topology`, which makes the `Topology` API a subset of the `MulticodecTopology` API.
### Topology
- `Topology`
- `peers<Map<string, PeerInfo>>`: A Map of peers belonging to the topology.
- `disconnect<function(PeerInfo)>`: Called when a peer has been disconnected
#### Constructor
```js
const toplogy = new Topology({
min: 0,
max: 50,
handlers: {
onConnect: (peerInfo, conn) => {},
onDisconnect: (peerInfo) => {}
}
})
```
**Parameters**
- `properties` is an `Object` containing the properties of the topology.
- `min` is a `number` with the minimum needed connections (default: 0)
- `max` is a `number` with the maximum needed connections (default: Infinity)
- `handlers` is an optional `Object` containing the handler called when a peer is connected or disconnected.
- `onConnect` is a `function` called everytime a peer is connected in the topology context.
- `onDisconnect` is a `function` called everytime a peer is disconnected in the topology context.
#### Set a peer
- `topology.peers.set(id, peerInfo)`
Add a peer to the topology.
**Parameters**
- `id` is the `string` that identifies the peer to add.
- `peerInfo` is the [PeerInfo][peer-info] of the peer to add.
#### Notify about a peer disconnected event
- `topology.disconnect(peerInfo)`
**Parameters**
- `peerInfo` is the [PeerInfo][peer-info] of the peer disconnected.
### Multicodec Topology
- `MulticodecTopology`
- `registrar<Registrar>`: The `Registrar` of the topology. This is set by the `Registrar` during registration.
- `peers<Map<string, PeerInfo>>`: The Map of peers that belong to the topology
- `disconnect<function(PeerInfo)>`: Disconnects a peer from the topology.
#### Constructor
```js
const toplogy = new MulticodecTopology({
min: 0,
max: 50,
multicodecs: ['/echo/1.0.0'],
handlers: {
onConnect: (peerInfo, conn) => {},
onDisconnect: (peerInfo) => {}
}
})
```
**Parameters**
- `properties` is an `Object` containing the properties of the topology.
- `min` is a `number` with the minimum needed connections (default: 0)
- `max` is a `number` with the maximum needed connections (default: Infinity)
- `multicodecs` is a `Array<String>` with the multicodecs associated with the topology.
- `handlers` is an optional `Object` containing the handler called when a peer is connected or disconnected.
- `onConnect` is a `function` called everytime a peer is connected in the topology context.
- `onDisconnect` is a `function` called everytime a peer is disconnected in the topology context.

46
src/topology/index.js Normal file
View File

@ -0,0 +1,46 @@
'use strict'
const withIs = require('class-is')
const noop = () => {}
class Topology {
/**
* @param {Object} props
* @param {number} props.min minimum needed connections (default: 0)
* @param {number} props.max maximum needed connections (default: Infinity)
* @param {Object} [props.handlers]
* @param {function} [props.handlers.onConnect] protocol "onConnect" handler
* @param {function} [props.handlers.onDisconnect] protocol "onDisconnect" handler
* @constructor
*/
constructor ({
min = 0,
max = Infinity,
handlers = {}
}) {
this.min = min
this.max = max
// Handlers
this._onConnect = handlers.onConnect || noop
this._onDisconnect = handlers.onDisconnect || noop
this.peers = new Map()
}
set registrar (registrar) {
this._registrar = registrar
}
/**
* Notify about peer disconnected event.
* @param {PeerInfo} peerInfo
* @returns {void}
*/
disconnect (peerInfo) {
this._onDisconnect(peerInfo)
}
}
module.exports = withIs(Topology, { className: 'Topology', symbolName: '@libp2p/js-interfaces/topology' })

View File

@ -0,0 +1,95 @@
'use strict'
const assert = require('assert')
const withIs = require('class-is')
const Topology = require('./index')
class MulticodecTopology extends Topology {
/**
* @param {Object} props
* @param {number} props.min minimum needed connections (default: 0)
* @param {number} props.max maximum needed connections (default: Infinity)
* @param {Array<string>} props.multicodecs protocol multicodecs
* @param {Object} props.handlers
* @param {function} props.handlers.onConnect protocol "onConnect" handler
* @param {function} props.handlers.onDisconnect protocol "onDisconnect" handler
* @constructor
*/
constructor ({
min,
max,
multicodecs,
handlers
}) {
super({ min, max, handlers })
assert(multicodecs, 'one or more multicodec should be provided')
assert(handlers, 'the handlers should be provided')
assert(handlers.onConnect && typeof handlers.onConnect === 'function',
'the \'onConnect\' handler must be provided')
assert(handlers.onDisconnect && typeof handlers.onDisconnect === 'function',
'the \'onDisconnect\' handler must be provided')
this.multicodecs = Array.isArray(multicodecs) ? multicodecs : [multicodecs]
this._registrar = undefined
this._onProtocolChange = this._onProtocolChange.bind(this)
}
set registrar (registrar) {
this._registrar = registrar
this._registrar.peerStore.on('change:protocols', this._onProtocolChange)
// Update topology peers
this._updatePeers(this._registrar.peerStore.peers.values())
}
/**
* Update topology.
* @param {Array<PeerInfo>} peerInfoIterable
* @returns {void}
*/
_updatePeers (peerInfoIterable) {
for (const peerInfo of peerInfoIterable) {
if (this.multicodecs.filter(multicodec => peerInfo.protocols.has(multicodec)).length) {
// Add the peer regardless of whether or not there is currently a connection
this.peers.set(peerInfo.id.toB58String(), peerInfo)
// If there is a connection, call _onConnect
const connection = this._registrar.getConnection(peerInfo)
connection && this._onConnect(peerInfo, connection)
} else {
// Remove any peers we might be tracking that are no longer of value to us
this.peers.delete(peerInfo.id.toB58String())
}
}
}
/**
* Check if a new peer support the multicodecs for this topology.
* @param {Object} props
* @param {PeerInfo} props.peerInfo
* @param {Array<string>} props.protocols
*/
_onProtocolChange ({ peerInfo, protocols }) {
const existingPeer = this.peers.get(peerInfo.id.toB58String())
const hasProtocol = protocols.filter(protocol => this.multicodecs.includes(protocol))
// Not supporting the protocol anymore?
if (existingPeer && hasProtocol.length === 0) {
this._onDisconnect({
peerInfo
})
}
// New to protocol support
for (const protocol of protocols) {
if (this.multicodecs.includes(protocol)) {
this._updatePeers([peerInfo])
return
}
}
}
}
module.exports = withIs(MulticodecTopology, { className: 'MulticodecTopology', symbolName: '@libp2p/js-interfaces/topology/multicodec-topology' })

View File

@ -0,0 +1,91 @@
/* eslint-env mocha */
'use strict'
const chai = require('chai')
const expect = chai.expect
chai.use(require('dirty-chai'))
const sinon = require('sinon')
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const peers = require('../../utils/peers')
module.exports = (test) => {
describe('multicodec topology', () => {
let topology, peer
beforeEach(async () => {
topology = await test.setup()
if (!topology) throw new Error('missing multicodec topology')
const id = await PeerId.createFromJSON(peers[0])
peer = await PeerInfo.create(id)
})
afterEach(async () => {
sinon.restore()
await test.teardown()
})
it('should have properties set', () => {
expect(topology.multicodecs).to.exist()
expect(topology._onConnect).to.exist()
expect(topology._onDisconnect).to.exist()
expect(topology.peers).to.exist()
expect(topology._registrar).to.exist()
})
it('should trigger "onDisconnect" on peer disconnected', () => {
sinon.spy(topology, '_onDisconnect')
topology.disconnect(peer)
expect(topology._onDisconnect.callCount).to.equal(1)
})
it('should update peers on protocol change', async () => {
sinon.spy(topology, '_updatePeers')
expect(topology.peers.size).to.eql(0)
const id2 = await PeerId.createFromJSON(peers[1])
const peer2 = await PeerInfo.create(id2)
topology.multicodecs.forEach((m) => peer2.protocols.add(m))
const peerStore = topology._registrar.peerStore
peerStore.emit('change:protocols', {
peerInfo: peer2,
protocols: Array.from(topology.multicodecs)
})
expect(topology._updatePeers.callCount).to.equal(1)
expect(topology.peers.size).to.eql(1)
})
it('should disconnect if peer no longer supports a protocol', async () => {
sinon.spy(topology, '_onDisconnect')
expect(topology.peers.size).to.eql(0)
const id2 = await PeerId.createFromJSON(peers[1])
const peer2 = await PeerInfo.create(id2)
topology.multicodecs.forEach((m) => peer2.protocols.add(m))
const peerStore = topology._registrar.peerStore
peerStore.emit('change:protocols', {
peerInfo: peer2,
protocols: Array.from(topology.multicodecs)
})
expect(topology.peers.size).to.eql(1)
topology.multicodecs.forEach((m) => peer2.protocols.delete(m))
// Peer does not support the protocol anymore
peerStore.emit('change:protocols', {
peerInfo: peer2,
protocols: []
})
expect(topology.peers.size).to.eql(1)
expect(topology._onDisconnect.callCount).to.equal(1)
})
})
}

View File

@ -0,0 +1,46 @@
/* eslint-env mocha */
'use strict'
const chai = require('chai')
const expect = chai.expect
chai.use(require('dirty-chai'))
const sinon = require('sinon')
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const peers = require('../../utils/peers')
module.exports = (test) => {
describe('topology', () => {
let topology, peer
beforeEach(async () => {
topology = await test.setup()
if (!topology) throw new Error('missing multicodec topology')
const id = await PeerId.createFromJSON(peers[0])
peer = await PeerInfo.create(id)
})
afterEach(async () => {
sinon.restore()
await test.teardown()
})
it('should have properties set', () => {
expect(topology.min).to.exist()
expect(topology.max).to.exist()
expect(topology._onConnect).to.exist()
expect(topology._onDisconnect).to.exist()
expect(topology.peers).to.exist()
})
it('should trigger "onDisconnect" on peer disconnected', () => {
sinon.spy(topology, '_onDisconnect')
topology.disconnect(peer)
expect(topology._onDisconnect.callCount).to.equal(1)
})
})
}

View File

@ -3,7 +3,7 @@
const tests = require('../../src/connection/tests') const tests = require('../../src/connection/tests')
const { Connection } = require('../../src/connection') const { Connection } = require('../../src/connection')
const peers = require('../utils/peers') const peers = require('../../src/utils/peers')
const PeerId = require('peer-id') const PeerId = require('peer-id')
const multiaddr = require('multiaddr') const multiaddr = require('multiaddr')
const pair = require('it-pair') const pair = require('it-pair')

View File

@ -0,0 +1,13 @@
/* eslint-env mocha */
'use strict'
const tests = require('../../src/crypto/tests')
const mockCrypto = require('./mock-crypto')
describe('compliance tests', () => {
tests({
setup () {
return mockCrypto
}
})
})

View File

@ -0,0 +1,75 @@
'use strict'
const PeerId = require('peer-id')
const handshake = require('it-handshake')
const duplexPair = require('it-pair/duplex')
const pipe = require('it-pipe')
const { UnexpectedPeerError } = require('../../src/crypto/errors')
// A basic transform that does nothing to the data
const transform = () => {
return (source) => (async function * () {
for await (const chunk of source) {
yield chunk
}
})()
}
module.exports = {
protocol: 'insecure',
secureInbound: async (localPeer, duplex, expectedPeer) => {
// 1. Perform a basic handshake.
const shake = handshake(duplex)
shake.write(localPeer.id)
const remoteId = await shake.read()
const remotePeer = new PeerId(remoteId.slice())
shake.rest()
if (expectedPeer && expectedPeer.id !== remotePeer.id) {
throw new UnexpectedPeerError()
}
// 2. Create your encryption box/unbox wrapper
const wrapper = duplexPair()
const encrypt = transform() // Use transform iterables to modify data
const decrypt = transform()
pipe(
wrapper[0], // We write to wrapper
encrypt, // The data is encrypted
shake.stream, // It goes to the remote peer
decrypt, // Decrypt the incoming data
wrapper[0] // Pipe to the wrapper
)
return {
conn: wrapper[1],
remotePeer
}
},
secureOutbound: async (localPeer, duplex, remotePeer) => {
// 1. Perform a basic handshake.
const shake = handshake(duplex)
shake.write(localPeer.id)
const remoteId = await shake.read()
shake.rest()
// 2. Create your encryption box/unbox wrapper
const wrapper = duplexPair()
const encrypt = transform()
const decrypt = transform()
pipe(
wrapper[0], // We write to wrapper
encrypt, // The data is encrypted
shake.stream, // It goes to the remote peer
decrypt, // Decrypt the incoming data
wrapper[0] // Pipe to the wrapper
)
return {
conn: wrapper[1],
remotePeer: new PeerId(remoteId.slice())
}
}
}

View File

@ -0,0 +1,12 @@
'use strict'
const { EventEmitter } = require('events')
class MockPeerStore extends EventEmitter {
constructor (peers) {
super()
this.peers = peers
}
}
module.exports = MockPeerStore

View File

@ -0,0 +1,40 @@
/* eslint-env mocha */
'use strict'
const tests = require('../../src/topology/tests/multicodec-topology')
const MulticodecTopology = require('../../src/topology/multicodec-topology')
const MockPeerStore = require('./mock-peer-store')
describe('multicodec topology compliance tests', () => {
tests({
setup (properties, registrar) {
const multicodecs = ['/echo/1.0.0']
const handlers = {
onConnect: () => { },
onDisconnect: () => { }
}
const topology = new MulticodecTopology({
multicodecs,
handlers,
...properties
})
if (!registrar) {
const peerStore = new MockPeerStore([])
registrar = {
peerStore,
getConnection: () => { }
}
}
topology.registrar = registrar
return topology
},
teardown () {
// cleanup resources created by setup()
}
})
})

View File

@ -0,0 +1,26 @@
/* eslint-env mocha */
'use strict'
const tests = require('../../src/topology/tests/topology')
const Topology = require('../../src/topology')
describe('topology compliance tests', () => {
tests({
setup (properties) {
const handlers = {
onConnect: () => { },
onDisconnect: () => { }
}
const topology = new Topology({
handlers,
...properties
})
return topology
},
teardown () {
// cleanup resources created by setup()
}
})
})