Skip to content
This repository was archived by the owner on Jun 26, 2023. It is now read-only.

Commit a5ad120

Browse files
authored
feat: add upgrader support to transports (#53)
BREAKING CHANGE: 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
1 parent 259fc58 commit a5ad120

File tree

4 files changed

+101
-20
lines changed

4 files changed

+101
-20
lines changed

README.md

+30-12
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ const YourTransport = require('../src')
5353

5454
describe('compliance', () => {
5555
tests({
56-
setup () {
57-
let transport = new YourTransport()
56+
setup (options) {
57+
let transport = new YourTransport(options)
5858

5959
const addrs = [
6060
multiaddr('valid-multiaddr-for-your-transport'),
@@ -84,18 +84,14 @@ describe('compliance', () => {
8484
})
8585
```
8686

87-
## Go
88-
89-
> WIP
90-
9187
# API
9288

9389
A valid transport (one that follows the interface defined) must implement the following API:
9490

9591
**Table of contents:**
9692

9793
- type: `Transport`
98-
- `new Transport([options])`
94+
- `new Transport({ upgrader, ...[options] })`
9995
- `<Promise> transport.dial(multiaddr, [options])`
10096
- `transport.createListener([options], handlerFunction)`
10197
- type: `transport.Listener`
@@ -107,25 +103,47 @@ A valid transport (one that follows the interface defined) must implement the fo
107103
- `listener.getAddrs()`
108104
- `<Promise> listener.close([options])`
109105

106+
### Types
107+
108+
#### Upgrader
109+
Upgraders have 2 methods: `upgradeOutbound` and `upgradeInbound`.
110+
- `upgradeOutbound` must be called and returned by `transport.dial`.
111+
- `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.
112+
113+
```js
114+
const connection = await upgrader.upgradeOutbound(multiaddrConnection)
115+
const connection = await upgrader.upgradeInbound(multiaddrConnection)
116+
```
117+
118+
The `Upgrader` methods take a [MultiaddrConnection](#multiaddrconnection) and will return an `interface-connection` instance.
119+
120+
#### MultiaddrConnection
121+
122+
- `MultiaddrConnection`
123+
- `sink<function(source)>`: A [streaming iterable sink](https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#sink-it)
124+
- `source<AsyncIterator>`: A [streaming iterable source](https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#source-it)
125+
- `conn`: The raw connection of the transport, such as a TCP socket.
126+
- `remoteAddr<Multiaddr>`: The remote `Multiaddr` of the connection.
127+
110128
### Creating a transport instance
111129

112-
- `JavaScript` - `const transport = new Transport([options])`
130+
- `JavaScript` - `const transport = new Transport({ upgrader, ...[options] })`
113131

114-
Creates a new Transport instance. `options` is an optional JavaScript object that should include the necessary parameters for the transport instance.
132+
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`.
115133

116134
**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.
117135

118136
### Dial to another peer
119137

120-
- `JavaScript` - `const conn = await transport.dial(multiaddr, [options])`
138+
- `JavaScript` - `const connection = await transport.dial(multiaddr, [options])`
121139

122140
This method uses a transport to dial a Peer listening on `multiaddr`.
123141

124142
`multiaddr` must be of the type [`multiaddr`](https://www.npmjs.com/multiaddr).
125143

126144
`[options]` the options that may be passed to the dial. Must support the `signal` option (see below)
127145

128-
`conn` must implement the [interface-connection](https://github.com/libp2p/interface-connection) interface.
146+
Dial **MUST** call and return `upgrader.upgradeOutbound(multiaddrConnection)`. The upgrader will return an [interface-connection](https://github.com/libp2p/interface-connection) instance.
129147

130148
The dial may throw an `Error` instance if there was a problem connecting to the `multiaddr`.
131149

@@ -158,7 +176,7 @@ try {
158176

159177
- `JavaScript` - `const listener = transport.createListener([options], handlerFunction)`
160178

161-
This method creates a listener on the transport.
179+
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.
162180

163181
`options` is an optional object that contains the properties the listener must have, in order to properly listen on a given transport/socket.
164182

package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
},
3535
"homepage": "https://github.com/libp2p/interface-transport",
3636
"devDependencies": {
37-
"aegir": "^18.2.2"
37+
"aegir": "^20.0.0"
3838
},
3939
"dependencies": {
4040
"abort-controller": "^3.0.0",
@@ -44,9 +44,10 @@
4444
"interface-connection": "~0.3.3",
4545
"it-goodbye": "^2.0.0",
4646
"it-pipe": "^1.0.0",
47-
"multiaddr": "^6.0.6",
47+
"multiaddr": "^7.0.0",
4848
"pull-stream": "^3.6.9",
49-
"streaming-iterables": "^4.0.2"
49+
"sinon": "^7.4.2",
50+
"streaming-iterables": "^4.1.0"
5051
},
5152
"contributors": [
5253
"Alan Shaw <[email protected]>",

src/dial-test.js

+32-2
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,34 @@ const { collect } = require('streaming-iterables')
1111
const pipe = require('it-pipe')
1212
const AbortController = require('abort-controller')
1313
const AbortError = require('./errors').AbortError
14+
const sinon = require('sinon')
1415

1516
module.exports = (common) => {
17+
const upgrader = {
18+
upgradeOutbound (multiaddrConnection) {
19+
['sink', 'source', 'remoteAddr', 'conn'].forEach(prop => {
20+
expect(multiaddrConnection).to.have.property(prop)
21+
})
22+
23+
return { sink: multiaddrConnection.sink, source: multiaddrConnection.source }
24+
},
25+
upgradeInbound (multiaddrConnection) {
26+
['sink', 'source', 'remoteAddr', 'conn'].forEach(prop => {
27+
expect(multiaddrConnection).to.have.property(prop)
28+
})
29+
30+
return { sink: multiaddrConnection.sink, source: multiaddrConnection.source }
31+
}
32+
}
33+
1634
describe('dial', () => {
1735
let addrs
1836
let transport
1937
let connector
2038
let listener
2139

2240
before(async () => {
23-
({ addrs, transport, connector } = await common.setup())
41+
({ addrs, transport, connector } = await common.setup({ upgrader }))
2442
})
2543

2644
after(() => common.teardown && common.teardown())
@@ -30,37 +48,47 @@ module.exports = (common) => {
3048
return listener.listen(addrs[0])
3149
})
3250

33-
afterEach(() => listener.close())
51+
afterEach(() => {
52+
sinon.restore()
53+
return listener.close()
54+
})
3455

3556
it('simple', async () => {
57+
const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound')
3658
const conn = await transport.dial(addrs[0])
3759

3860
const s = goodbye({ source: ['hey'], sink: collect })
3961

4062
const result = await pipe(s, conn, s)
4163

64+
expect(upgradeSpy.callCount).to.equal(1)
65+
expect(upgradeSpy.returned(conn)).to.equal(true)
4266
expect(result.length).to.equal(1)
4367
expect(result[0].toString()).to.equal('hey')
4468
})
4569

4670
it('to non existent listener', async () => {
71+
const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound')
4772
try {
4873
await transport.dial(addrs[1])
4974
} catch (_) {
75+
expect(upgradeSpy.callCount).to.equal(0)
5076
// Success: expected an error to be throw
5177
return
5278
}
5379
expect.fail('Did not throw error attempting to connect to non-existent listener')
5480
})
5581

5682
it('abort before dialing throws AbortError', async () => {
83+
const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound')
5784
const controller = new AbortController()
5885
controller.abort()
5986
const socket = transport.dial(addrs[0], { signal: controller.signal })
6087

6188
try {
6289
await socket
6390
} catch (err) {
91+
expect(upgradeSpy.callCount).to.equal(0)
6492
expect(err.code).to.eql(AbortError.code)
6593
expect(err.type).to.eql(AbortError.type)
6694
return
@@ -69,6 +97,7 @@ module.exports = (common) => {
6997
})
7098

7199
it('abort while dialing throws AbortError', async () => {
100+
const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound')
72101
// Add a delay to connect() so that we can abort while the dial is in
73102
// progress
74103
connector.delay(100)
@@ -80,6 +109,7 @@ module.exports = (common) => {
80109
try {
81110
await socket
82111
} catch (err) {
112+
expect(upgradeSpy.callCount).to.equal(0)
83113
expect(err.code).to.eql(AbortError.code)
84114
expect(err.type).to.eql(AbortError.type)
85115
return

src/listen-test.js

+35-3
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,59 @@ const chai = require('chai')
66
const dirtyChai = require('dirty-chai')
77
const expect = chai.expect
88
chai.use(dirtyChai)
9+
const sinon = require('sinon')
910

1011
const pipe = require('it-pipe')
1112

1213
module.exports = (common) => {
14+
const upgrader = {
15+
upgradeOutbound (multiaddrConnection) {
16+
['sink', 'source', 'remoteAddr', 'conn'].forEach(prop => {
17+
expect(multiaddrConnection).to.have.property(prop)
18+
})
19+
20+
return { sink: multiaddrConnection.sink, source: multiaddrConnection.source }
21+
},
22+
upgradeInbound (multiaddrConnection) {
23+
['sink', 'source', 'remoteAddr', 'conn'].forEach(prop => {
24+
expect(multiaddrConnection).to.have.property(prop)
25+
})
26+
27+
return { sink: multiaddrConnection.sink, source: multiaddrConnection.source }
28+
}
29+
}
30+
1331
describe('listen', () => {
1432
let addrs
1533
let transport
1634

1735
before(async () => {
18-
({ transport, addrs } = await common.setup())
36+
({ transport, addrs } = await common.setup({ upgrader }))
1937
})
2038

2139
after(() => common.teardown && common.teardown())
2240

41+
afterEach(() => {
42+
sinon.restore()
43+
})
44+
2345
it('simple', async () => {
2446
const listener = transport.createListener((conn) => {})
2547
await listener.listen(addrs[0])
2648
await listener.close()
2749
})
2850

2951
it('close listener with connections, through timeout', async () => {
52+
const upgradeSpy = sinon.spy(upgrader, 'upgradeInbound')
3053
let finish
31-
let done = new Promise((resolve) => {
54+
const done = new Promise((resolve) => {
3255
finish = resolve
3356
})
3457

35-
const listener = transport.createListener((conn) => pipe(conn, conn))
58+
const listener = transport.createListener((conn) => {
59+
expect(upgradeSpy.returned(conn)).to.equal(true)
60+
pipe(conn, conn)
61+
})
3662

3763
// Listen
3864
await listener.listen(addrs[0])
@@ -53,13 +79,19 @@ module.exports = (common) => {
5379

5480
// Pipe should have completed
5581
await done
82+
83+
// 2 dials = 2 connections upgraded
84+
expect(upgradeSpy.callCount).to.equal(2)
5685
})
5786

5887
describe('events', () => {
5988
it('connection', (done) => {
89+
const upgradeSpy = sinon.spy(upgrader, 'upgradeInbound')
6090
const listener = transport.createListener()
6191

6292
listener.on('connection', async (conn) => {
93+
expect(upgradeSpy.returned(conn)).to.equal(true)
94+
expect(upgradeSpy.callCount).to.equal(1)
6395
expect(conn).to.exist()
6496
await listener.close()
6597
done()

0 commit comments

Comments
 (0)