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

Commit ae08096

Browse files
committed
feat: topology
1 parent b960f29 commit ae08096

File tree

7 files changed

+221
-0
lines changed

7 files changed

+221
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
- [Peer Discovery](./src/peer-discovery)
2020
- [Peer Routing](./src/peer-routing)
2121
- [Stream Muxer](./src/stream-muxer)
22+
- [Topology](./src/topology)
2223
- [Transport](./src/transport)
2324

2425
### Origin Repositories

src/topology/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
WIP

src/topology/index.js

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
'use strict'
2+
3+
const assert = require('assert')
4+
5+
class Topology {
6+
/**
7+
* @param {Object} props
8+
* @param {number} props.min minimum needed connections (default: 0)
9+
* @param {number} props.max maximum needed connections (default: Infinity)
10+
* @param {Array<string>} props.multicodecs protocol multicodecs
11+
* @param {Object} props.handlers
12+
* @param {function} props.handlers.onConnect protocol "onConnect" handler
13+
* @param {function} props.handlers.onDisconnect protocol "onDisconnect" handler
14+
* @constructor
15+
*/
16+
constructor ({
17+
min = 0,
18+
max = Infinity,
19+
multicodecs,
20+
handlers
21+
}) {
22+
assert(multicodecs, 'one or more multicodec should be provided')
23+
assert(handlers, 'the handlers should be provided')
24+
assert(handlers.onConnect && typeof handlers.onConnect === 'function',
25+
'the \'onConnect\' handler must be provided')
26+
assert(handlers.onDisconnect && typeof handlers.onDisconnect === 'function',
27+
'the \'onDisconnect\' handler must be provided')
28+
29+
this.multicodecs = Array.isArray(multicodecs) ? multicodecs : [multicodecs]
30+
this.min = min
31+
this.max = max
32+
33+
// Handlers
34+
this._onConnect = handlers.onConnect
35+
this._onDisconnect = handlers.onDisconnect
36+
37+
this.peers = new Map()
38+
this._registrar = undefined
39+
40+
this._onProtocolChange = this._onProtocolChange.bind(this)
41+
}
42+
43+
set registrar (registrar) {
44+
this._registrar = registrar
45+
this._registrar.peerStore.on('change:protocols', this._onProtocolChange)
46+
47+
// Update topology peers
48+
this._updatePeers(this._registrar.peerStore.peers.values())
49+
}
50+
51+
/**
52+
* Update topology.
53+
* @param {Array<PeerInfo>} peerInfoIterable
54+
* @returns {void}
55+
*/
56+
_updatePeers (peerInfoIterable) {
57+
for (const peerInfo of peerInfoIterable) {
58+
if (this.multicodecs.filter(multicodec => peerInfo.protocols.has(multicodec))) {
59+
// Add the peer regardless of whether or not there is currently a connection
60+
this.peers.set(peerInfo.id.toB58String(), peerInfo)
61+
// If there is a connection, call _onConnect
62+
const connection = this._registrar.getConnection(peerInfo)
63+
connection && this._onConnect(peerInfo, connection)
64+
} else {
65+
// Remove any peers we might be tracking that are no longer of value to us
66+
this.peers.delete(peerInfo.id.toB58String())
67+
}
68+
}
69+
}
70+
71+
/**
72+
* Notify protocol of peer disconnected.
73+
* @param {PeerInfo} peerInfo
74+
* @param {Error} [error]
75+
* @returns {void}
76+
*/
77+
disconnect (peerInfo, error) {
78+
this._onDisconnect(peerInfo, error)
79+
}
80+
81+
/**
82+
* Check if a new peer support the multicodecs for this topology.
83+
* @param {Object} props
84+
* @param {PeerInfo} props.peerInfo
85+
* @param {Array<string>} props.protocols
86+
*/
87+
_onProtocolChange ({ peerInfo, protocols }) {
88+
const existingPeer = this.peers.get(peerInfo.id.toB58String())
89+
const hasProtocol = protocols.filter(protocol => this.multicodecs.includes(protocol))
90+
91+
// Not supporting the protocol anymore?
92+
if (existingPeer && hasProtocol.length === 0) {
93+
this._onDisconnect({
94+
peerInfo
95+
})
96+
}
97+
98+
// New to protocol support
99+
for (const protocol of protocols) {
100+
if (this.multicodecs.includes(protocol)) {
101+
this._updatePeers([peerInfo])
102+
return
103+
}
104+
}
105+
}
106+
}
107+
108+
module.exports = Topology

src/topology/tests/index.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/* eslint-env mocha */
2+
3+
'use strict'
4+
5+
const topologySuite = require('./topology')
6+
7+
module.exports = (test) => {
8+
topologySuite(test)
9+
}

src/topology/tests/topology.js

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/* eslint-env mocha */
2+
3+
'use strict'
4+
5+
const chai = require('chai')
6+
const expect = chai.expect
7+
chai.use(require('dirty-chai'))
8+
const sinon = require('sinon')
9+
10+
const PeerId = require('peer-id')
11+
const PeerInfo = require('peer-info')
12+
const peers = require('../../utils/peers')
13+
14+
module.exports = (test) => {
15+
describe('topology', () => {
16+
let topology, peer
17+
18+
describe('mock', () => {
19+
beforeEach(async () => {
20+
topology = await test.setup()
21+
if (!topology) throw new Error('missing topology')
22+
23+
const id = await PeerId.createFromJSON(peers[0])
24+
peer = await PeerInfo.create(id)
25+
})
26+
27+
afterEach(async () => {
28+
sinon.restore()
29+
await test.teardown()
30+
})
31+
32+
it('should have properties set', () => {
33+
expect(topology.multicodecs).to.exist()
34+
expect(topology.min).to.exist()
35+
expect(topology.max).to.exist()
36+
expect(topology._onConnect).to.exist()
37+
expect(topology._onDisconnect).to.exist()
38+
expect(topology.peers).to.exist()
39+
expect(topology._registrar).to.exist()
40+
})
41+
42+
it('should trigger "onDisconnect" on peer disconnected', () => {
43+
sinon.spy(topology, '_onDisconnect')
44+
topology.disconnect(peer)
45+
46+
expect(topology._onDisconnect.callCount).to.equal(1)
47+
})
48+
})
49+
})
50+
}

test/topology/compliance.spec.js

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/* eslint-env mocha */
2+
'use strict'
3+
4+
const tests = require('../../src/topology/tests')
5+
const Topology = require('../../src/topology')
6+
const MockPeerStore = require('./mock-peer-store')
7+
8+
describe('compliance tests', () => {
9+
tests({
10+
setup (properties, registrar) {
11+
const multicodecs = ['/echo/1.0.0']
12+
const handlers = {
13+
onConnect: () => { },
14+
onDisconnect: () => { }
15+
}
16+
17+
const topology = new Topology({
18+
multicodecs,
19+
handlers,
20+
...properties
21+
})
22+
23+
if (!registrar) {
24+
const peerStore = new MockPeerStore([])
25+
26+
registrar = {
27+
peerStore,
28+
getConnection: () => {}
29+
}
30+
}
31+
32+
topology.registrar = registrar
33+
34+
return topology
35+
},
36+
teardown () {
37+
// cleanup resources created by setup()
38+
}
39+
})
40+
})

test/topology/mock-peer-store.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict'
2+
3+
const { EventEmitter } = require('events')
4+
5+
class MockPeerStore extends EventEmitter {
6+
constructor (peers) {
7+
super()
8+
this.peers = peers
9+
}
10+
}
11+
12+
module.exports = MockPeerStore

0 commit comments

Comments
 (0)