diff --git a/README.md b/README.md index 2c44362..5c7fe49 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,6 @@ Publishing a test suite as a module lets multiple modules all ensure compatibili 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. -The API is presented with both Node.js and Go primitives, however there are no actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks. - ## Lead Maintainer [Jacob Heun](https://github.com/jacobheun/) @@ -93,6 +91,7 @@ A valid transport (one that follows the interface defined) must implement the fo - type: `Transport` - `new Transport({ upgrader, ...[options] })` - ` transport.dial(multiaddr, [options])` + - ` transport.filter(multiaddrs)` - `transport.createListener([options], handlerFunction)` - type: `transport.Listener` - event: 'listening' @@ -122,12 +121,17 @@ The `Upgrader` methods take a [MultiaddrConnection](#multiaddrconnection) and wi - `MultiaddrConnection` - `sink`: A [streaming iterable sink](https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#sink-it) - `source`: A [streaming iterable source](https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#source-it) + - `close`: A method for closing the connection - `conn`: The raw connection of the transport, such as a TCP socket. - `remoteAddr`: The remote `Multiaddr` of the connection. + - `[localAddr]`: An optional local `Multiaddr` of the connection. + - `timeline`: A hash map of connection time events + - `open`: The time in ticks the connection was opened + - `close`: The time in ticks the connection was closed ### Creating a transport instance -- `JavaScript` - `const transport = new Transport({ upgrader, ...[options] })` +- `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`. @@ -135,7 +139,7 @@ Creates a new Transport instance. `options` is an JavaScript object that should ### Dial to another peer -- `JavaScript` - `const connection = await transport.dial(multiaddr, [options])` +- `const connection = await transport.dial(multiaddr, [options])` This method uses a transport to dial a Peer listening on `multiaddr`. @@ -172,9 +176,18 @@ try { // ---- ``` +### 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 -- `JavaScript` - `const listener = transport.createListener([options], handlerFunction)` +- `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. @@ -191,7 +204,7 @@ The listener object created may emit the following events: ### Start a listener -- `JavaScript` - `await listener.listen(multiaddr)` +- `await listener.listen(multiaddr)` This method puts the listener in `listening` mode, waiting for incoming connections. @@ -199,13 +212,13 @@ This method puts the listener in `listening` mode, waiting for incoming connecti ### Get listener addrs -- `JavaScript` - `listener.getAddrs()` +- `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 -- `JavaScript` - `await listener.close([options])` +- `await listener.close([options])` This method closes the listener so that no more connections can be opened on this transport instance. diff --git a/src/dial-test.js b/src/dial-test.js index 80d29b6..d6f919c 100644 --- a/src/dial-test.js +++ b/src/dial-test.js @@ -6,6 +6,7 @@ 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') @@ -15,19 +16,18 @@ const sinon = require('sinon') module.exports = (common) => { const upgrader = { - upgradeOutbound (multiaddrConnection) { - ['sink', 'source', 'remoteAddr', 'conn'].forEach(prop => { + _upgrade (multiaddrConnection) { + ['sink', 'source', 'remoteAddr', 'conn', 'timeline', 'close'].forEach(prop => { expect(multiaddrConnection).to.have.property(prop) }) - - return { sink: multiaddrConnection.sink, source: multiaddrConnection.source } + expect(isValidTick(multiaddrConnection.timeline.open)).to.equal(true) + return multiaddrConnection + }, + upgradeOutbound (multiaddrConnection) { + return upgrader._upgrade(multiaddrConnection) }, upgradeInbound (multiaddrConnection) { - ['sink', 'source', 'remoteAddr', 'conn'].forEach(prop => { - expect(multiaddrConnection).to.have.property(prop) - }) - - return { sink: multiaddrConnection.sink, source: multiaddrConnection.source } + return upgrader._upgrade(multiaddrConnection) } } @@ -67,6 +67,16 @@ module.exports = (common) => { 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 { diff --git a/src/filter-test.js b/src/filter-test.js new file mode 100644 index 0000000..09c53a3 --- /dev/null +++ b/src/filter-test.js @@ -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) + }) + }) +} diff --git a/src/index.js b/src/index.js index 73f1373..ba9ee98 100644 --- a/src/index.js +++ b/src/index.js @@ -3,11 +3,13 @@ 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) }) } diff --git a/src/listen-test.js b/src/listen-test.js index 9754cbd..a455195 100644 --- a/src/listen-test.js +++ b/src/listen-test.js @@ -9,22 +9,23 @@ chai.use(dirtyChai) const sinon = require('sinon') const pipe = require('it-pipe') +const { isValidTick } = require('./utils') module.exports = (common) => { const upgrader = { - upgradeOutbound (multiaddrConnection) { - ['sink', 'source', 'remoteAddr', 'conn'].forEach(prop => { + _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 { sink: multiaddrConnection.sink, source: multiaddrConnection.source } + return multiaddrConnection + }, + upgradeOutbound (multiaddrConnection) { + return upgrader._upgrade(multiaddrConnection) }, upgradeInbound (multiaddrConnection) { - ['sink', 'source', 'remoteAddr', 'conn'].forEach(prop => { - expect(multiaddrConnection).to.have.property(prop) - }) - - return { sink: multiaddrConnection.sink, source: multiaddrConnection.source } + return upgrader._upgrade(multiaddrConnection) } } @@ -50,8 +51,10 @@ module.exports = (common) => { 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) }) @@ -78,6 +81,13 @@ module.exports = (common) => { 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) }) diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000..b0f3bf1 --- /dev/null +++ b/src/utils/index.js @@ -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 + } +}