Skip to content
This repository was archived by the owner on Aug 23, 2019. It is now read-only.

Commit 30dbb5f

Browse files
dmitriy ryajovdmitriy ryajov
dmitriy ryajov
authored and
dmitriy ryajov
committed
feat: initial implementation of circuit relaying
1 parent 67962a5 commit 30dbb5f

File tree

8 files changed

+564
-19
lines changed

8 files changed

+564
-19
lines changed

package.json

+25-19
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,51 @@
11
{
22
"name": "libp2p-circuit",
3-
"version": "0.9.2",
3+
"version": "0.0.1",
44
"description": "JavaScript implementation of circuit/switch relaying",
55
"main": "src/index.js",
66
"scripts": {
77
"lint": "aegir-lint",
8-
"test": "gulp test",
9-
"test:node": "gulp test:node",
10-
"test:browser": "gulp test:browser",
11-
"build": "gulp build",
12-
"release": "gulp release",
13-
"release-minor": "gulp release --type minor",
14-
"release-major": "gulp release --type major",
15-
"coverage": "gulp coverage",
8+
"build": "aegir-build",
9+
"test": "aegir-test --env node",
10+
"release": "aegir-release",
11+
"release-minor": "aegir-release --type minor",
12+
"release-major": "aegir-release --type major",
13+
"coverage": "aegir-coverage",
1614
"coverage-publish": "aegir-coverage publish"
1715
},
18-
"browser": {
19-
"pull-ws/server": false
20-
},
2116
"pre-commit": [
2217
"lint",
2318
"test"
2419
],
2520
"repository": {
2621
"type": "git",
27-
"url": "git+https://github.com/libp2p/js-libp2p-websockets.git"
22+
"url": "git+https://github.com/libp2p/js-libp2p-circuit.git"
2823
},
2924
"keywords": [
3025
"IPFS"
3126
],
32-
"author": "David Dias <[email protected]>",
27+
"author": "Dmitriy Ryajov <[email protected]>",
3328
"license": "MIT",
3429
"bugs": {
35-
"url": "https://github.com/dryajov/js-libp2p-circuit/issues"
30+
"url": "https://github.com/libp2p/js-libp2p-circuit/issues"
3631
},
37-
"homepage": "https://github.com/dryajov/js-libp2p-circuit#readme",
32+
"homepage": "https://github.com/libp2p/js-libp2p-circuit#readme",
3833
"devDependencies": {
3934
"aegir": "^10.0.0",
35+
"libp2p-ipfs-nodejs": "^0.19.0",
36+
"peer-id": "^0.8.2",
37+
"peer-info": "^0.8.3",
4038
"pre-commit": "^1.2.2"
4139
},
42-
"contributors": [
43-
"Dmitriy Ryajov <[email protected]>"
44-
]
40+
"contributors": [],
41+
"dependencies": {
42+
"async": "^2.1.5",
43+
"debug": "^2.6.1",
44+
"interface-connection": "^0.3.1",
45+
"lodash": "^4.17.4",
46+
"multiaddr": "^2.2.1",
47+
"multistream-select": "^0.13.4",
48+
"pull-pushable": "^2.0.1",
49+
"pull-stream": "^3.5.0"
50+
}
4551
}

src/config.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use strict'
2+
3+
const debug = require('debug')
4+
5+
const log = debug('libp2p:circuit')
6+
log.err = debug('libp2p:circuit:error')
7+
8+
module.exports = {
9+
log: log,
10+
multicodec: '/ipfs/relay/circuit/1.0.0'
11+
}

src/dialer.js

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
'use strict'
2+
3+
const config = require('./config')
4+
const mss = require('multistream-select')
5+
const pull = require('pull-stream')
6+
const Peer = require('./peer')
7+
8+
const log = config.log
9+
10+
class Dialer {
11+
constructor (libp2p, relayPeers) {
12+
this.libp2p = libp2p
13+
this.relayPeers = relayPeers || new Map()
14+
this.peers = new Map()
15+
16+
// Speed up any new peer that comes in my way
17+
// this.libp2p.swarm.on('peer-mux-established', this._isRelayPeer)
18+
// this.libp2p.swarm.on('peer-mux-closed', () => {
19+
// // TODO: detach relay connection
20+
// })
21+
}
22+
23+
relay (peerInfo, callback) {
24+
if (this.peers.has(peerInfo.id.toB58String())) {
25+
return callback(null,
26+
this.peers.get(peerInfo.id.toB58String()).conn)
27+
}
28+
29+
let next = (relayPeer) => {
30+
if (!relayPeer) {
31+
return callback(`no relay peers were found!`)
32+
}
33+
34+
log(`Trying relay peer ${relayPeer.id.toB58String()}`)
35+
this._dialRelay(relayPeer, (err, conn) => {
36+
if (err) {
37+
if (relays.length > 0) {
38+
return next(relays.shift())
39+
}
40+
return callback(err)
41+
}
42+
43+
this._negotiateRelay(conn, peerInfo, (err, conn) => {
44+
if (err) {
45+
log.err(`An error has occurred negotiating the relay connection`, err)
46+
return callback(err)
47+
}
48+
49+
this.peers.set(peerInfo.id.toB58String(), new Peer(conn))
50+
callback(null, conn)
51+
})
52+
})
53+
}
54+
55+
let relays = Array.from(this.relayPeers.values())
56+
next(relays.shift())
57+
}
58+
59+
_negotiateRelay (conn, peerInfo, callback) {
60+
let addr = this.libp2p.peerInfo.distinctMultiaddr()
61+
let destAddrs = peerInfo.distinctMultiaddr()
62+
63+
if (!(addr && addr.length > 0) || !(destAddrs && destAddrs.length > 0)) {
64+
log.err(`No valid multiaddress for peer!`)
65+
callback(`No valid multiaddress for peer!`)
66+
}
67+
68+
log(`negotiating relay for peer ${peerInfo.id.toB58String()}`)
69+
mss.util.encode(new Buffer(`${destAddrs[0].toString()}/ipfs/${peerInfo.id.toB58String()}`), (err, encoded) => {
70+
pull(
71+
pull.values([encoded]),
72+
conn,
73+
pull.collect((err) => {
74+
callback(err, conn)
75+
})
76+
)
77+
})
78+
}
79+
80+
_isRelayPeer (peerInfo) {
81+
this._dialRelay(peerInfo, (peerInfo, conn) => {
82+
// TODO: implement relay peer discovery here
83+
})
84+
}
85+
86+
_dialRelay (relayPeer, callback) {
87+
const idB58Str = relayPeer.id.toB58String()
88+
log('dialing %s', idB58Str)
89+
90+
if (this.peers.has(idB58Str)) {
91+
return callback(null, this.peers.get(idB58Str))
92+
}
93+
94+
this.libp2p.dialByPeerInfo(relayPeer, config.multicodec, (err, conn) => {
95+
if (err) {
96+
return callback(err)
97+
}
98+
99+
callback(null, conn)
100+
})
101+
}
102+
}
103+
104+
module.exports = Dialer

src/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
exports.Dialer = require('./dialer')
2+
exports.Peer = require('./peer')
3+
exports.Relay = require('./relay')
4+
exports.Listener = require('./listener')

src/listener.js

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
const config = require('./config')
2+
const Peer = require('./peer')
3+
const pull = require('pull-stream')
4+
const multiaddr = require('multiaddr')
5+
const PeerInfo = require('peer-info')
6+
const includes = require('lodash/includes')
7+
const lp = require('pull-length-prefixed')
8+
9+
const multicodec = config.multicodec
10+
11+
const log = config.log
12+
13+
class Listener {
14+
constructor (libp2p) {
15+
this.libp2p = libp2p
16+
this.peers = new Map()
17+
18+
this._onConnection = this._onConnection.bind(this)
19+
}
20+
21+
listen (cb) {
22+
this.libp2p.handle(multicodec, this._onConnection)
23+
cb()
24+
}
25+
26+
close (cb) {
27+
this.libp2p.unhandle(multicodec)
28+
cb()
29+
}
30+
31+
_onConnection (protocol, conn) {
32+
conn.getPeerInfo((err, peerInfo) => {
33+
if (err) {
34+
log.err('Failed to identify incomming conn', err)
35+
return pull(pull.empty(), conn)
36+
}
37+
38+
const idB58Str = peerInfo.id.toB58String()
39+
let relayPeer = this.peers.get(idB58Str)
40+
if (!relayPeer) {
41+
log('new peer', idB58Str)
42+
relayPeer = new Peer(peerInfo)
43+
this.peers.set(idB58Str, relayPeer)
44+
}
45+
relayPeer.attachConnection(conn)
46+
this._processConnection(relayPeer, conn)
47+
})
48+
}
49+
50+
_processConnection (relayPeer, conn) {
51+
return pull(
52+
conn,
53+
lp.decode(),
54+
pull.collect((err, val) => {
55+
if (err) {
56+
err(err)
57+
return err
58+
}
59+
60+
let addr = multiaddr(val[0].toString())
61+
let sourcePeer
62+
try {
63+
PeerInfo.create(addr.peerId(), (err, peerId) => {
64+
let peerInfo = new PeerInfo(peerId)
65+
66+
if (includes(addr.protoNames(), 'ipfs')) {
67+
addr = addr.decapsulate('ipfs')
68+
}
69+
70+
peerInfo.multiaddr.add(addr)
71+
sourcePeer = new Peer(peerInfo)
72+
sourcePeer.attachConnection(conn) // attach relay connection
73+
74+
pull(
75+
pull.values(['Hello']),
76+
conn
77+
)
78+
})
79+
} catch (err) {
80+
log.err(err)
81+
}
82+
})
83+
)
84+
}
85+
86+
}
87+
88+
module.exports = Listener

src/peer.js

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
'use strict'
2+
3+
const pull = require('pull-stream')
4+
5+
/**
6+
* The known state of a connected peer.
7+
*/
8+
class Peer {
9+
/**
10+
* @param {PeerInfo} this peer
11+
*/
12+
constructor (peerInfo) {
13+
/**
14+
* @type {PeerInfo}
15+
*/
16+
this.peer = peerInfo
17+
/**
18+
* @type {Map<Peer>} map of relayed peers
19+
*/
20+
this.peers = new Map()
21+
/**
22+
* @type {Connection}
23+
*/
24+
this.conn = null
25+
}
26+
27+
/**
28+
* Is the peer connected currently?
29+
*
30+
* @type {boolean}
31+
*/
32+
get isConnected () {
33+
return Boolean(this.conn)
34+
}
35+
36+
/**
37+
* Circuit this connection with the dest peer
38+
*
39+
* @param dest {Peer} the destination peer to be short circuited
40+
*/
41+
circuit (dest, callback) {
42+
if (this.peers.has(dest.peer.id.toB58String())) {
43+
callback(null)
44+
}
45+
46+
pull(this.conn, dest.conn)
47+
this.peers.set(dest.peer.id.toB58String(), dest)
48+
49+
callback(null)
50+
}
51+
52+
/**
53+
* Attach the peer to a connection and setup a write stream
54+
*
55+
* @param {Connection} conn
56+
* @returns {undefined}
57+
*/
58+
attachConnection (conn) {
59+
this.conn = conn
60+
}
61+
62+
/**
63+
* Closes the open connection to peer
64+
*
65+
* @param {Function} callback
66+
* @returns {undefined}
67+
*/
68+
close (callback) {
69+
if (!this.conn || !this.stream) {
70+
// no connection to close
71+
}
72+
// end the pushable pull-stream
73+
if (this.stream) {
74+
this.stream.end()
75+
}
76+
setImmediate(() => {
77+
this.conn = null
78+
this.stream = null
79+
callback()
80+
})
81+
}
82+
}
83+
84+
module.exports = Peer

0 commit comments

Comments
 (0)