Skip to content
This repository has been archived by the owner on Mar 11, 2020. It is now read-only.

Commit

Permalink
feat: timeline and close checking (#55)
Browse files Browse the repository at this point in the history
* feat: add test to validate timeline presence

* feat: add test and docs for close and timeline

* feat: add filter test

* docs: dont reference go, it's not relevant
  • Loading branch information
jacobheun authored Sep 19, 2019
1 parent 02fe6d9 commit 993ca1c
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 25 deletions.
29 changes: 21 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/)
Expand Down Expand Up @@ -93,6 +91,7 @@ A valid transport (one that follows the interface defined) must implement the fo
- type: `Transport`
- `new Transport({ upgrader, ...[options] })`
- `<Promise> transport.dial(multiaddr, [options])`
- `<Multiaddr[]> transport.filter(multiaddrs)`
- `transport.createListener([options], handlerFunction)`
- type: `transport.Listener`
- event: 'listening'
Expand Down Expand Up @@ -122,20 +121,25 @@ The `Upgrader` methods take a [MultiaddrConnection](#multiaddrconnection) and wi
- `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

- `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`.

**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

- `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`.

Expand Down Expand Up @@ -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.

Expand All @@ -191,21 +204,21 @@ 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.

`multiaddr` is the address that the listener should bind to.

### 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.

Expand Down
28 changes: 19 additions & 9 deletions src/dial-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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)
}
}

Expand Down Expand Up @@ -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 {
Expand Down
37 changes: 37 additions & 0 deletions src/filter-test.js
Original file line number Diff line number Diff line change
@@ -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)
})
})
}
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}

Expand Down
26 changes: 18 additions & 8 deletions src/listen-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand All @@ -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)
})
Expand All @@ -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)
})
Expand Down
16 changes: 16 additions & 0 deletions src/utils/index.js
Original file line number Diff line number Diff line change
@@ -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
}
}

0 comments on commit 993ca1c

Please sign in to comment.