├── .gitignore ├── LICENSE ├── README.md ├── examples ├── bundle.js ├── explorer.js └── index.html ├── graphs ├── connection-state.jpg ├── overview.jpg ├── routing.jpg ├── signalling.jpg ├── webrtc-explorer-logo-small.png └── webrtc-explorer-logo.pxm ├── package.json ├── src ├── .DS_Store ├── explorer │ ├── channel.js │ ├── config.js │ ├── connection-switch.js │ ├── finger-table.js │ ├── index.js │ └── message-router.js └── sig-server │ ├── .DS_Store │ ├── bin.js │ ├── config.js │ ├── index.js │ ├── resources │ ├── peer-table.js │ └── peer.js │ ├── routes-http │ ├── basic.js │ └── graph.js │ ├── routes-ws │ └── index.js │ └── utils │ ├── finger-best-fit.js │ └── ideal-finger.js └── tests ├── explorer ├── index.js ├── scripts │ └── explorer-peer.js └── test-explorer.js └── sig-server ├── index.js ├── test-finger-best-fit.js ├── test-handshake.js ├── test-http.js ├── test-join.js ├── test-lifecycle.js └── test-update-finger.js /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | # Logs 3 | logs 4 | *.log 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # Compiled binary addons (http://nodejs.org/api/addons.html) 21 | build/Release 22 | 23 | # Dependency directory 24 | # Deployed apps should consider commenting this line out: 25 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 26 | node_modules 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 David Dias 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](/graphs/webrtc-explorer-logo-small.png) 2 | 3 | [![Dependency Status](https://david-dm.org/diasdavid/webrtc-explorer.svg)](https://david-dm.org/diasdavid/webrtc-explorer) 4 | [![](https://img.shields.io/badge/project-WebCompute-blue.svg?style=flat-square)](https://github.com/diasdavid/WebCompute) 5 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard) 6 | 7 | ![](https://raw.githubusercontent.com/diasdavid/interface-connection/master/img/badge.png) 8 | ![](https://raw.githubusercontent.com/diasdavid/interface-transport/master/img/badge.png) 9 | 10 | > **tl;dr** `webrtc-explorer` is a [Chord](http://pdos.csail.mit.edu/papers/chord:sigcomm01/chord_sigcomm.pdf) inspired, P2P Network Routing Overlay designed for the Web platform (browsers), using WebRTC as its layer of transport between peers and WebSockets for the exchange of signalling data (setting up a connection and NAT traversal). Essentially, webrtc-explorer enables your peers (browsers) to communicate between each other without the need to have a server to be the mediator. 11 | 12 | # Usage 13 | 14 | ## Install 15 | 16 | ```sh 17 | > npm install webrtc-explorer 18 | ``` 19 | 20 | If you want to use the Signalling Server that comes with webrtc-explorer, you can use it through your terminal after installing webrtc-explorer globally 21 | 22 | ```sh 23 | > npm install webrtc-explorer --global 24 | # ... 25 | > sig-server 26 | Signalling Server Started 27 | # now the signalling server is running 28 | ``` 29 | 30 | Use [browserify](http://browserify.org) to load transpile your JS code that uses webrtc-explorer, so that all of the dependencies are correctly loaded. 31 | 32 | ## API 33 | 34 | ```javascript 35 | const explorer = require('webrtc-explorer') 36 | ``` 37 | 38 | #### listen 39 | 40 | Connects your explorer node to the signalling server, and listens for incomming connections from other peers. 41 | 42 | ```javascript 43 | const listener = explorer.createListener((socket) => { 44 | // socket with another peer 45 | }) 46 | 47 | listener.listen((err) => { 48 | if (err) { 49 | return console.log('Error listening:', err) 50 | } 51 | console.log('explorer is now listining to incomming connections') 52 | }) 53 | ``` 54 | 55 | #### dial 56 | 57 | Dials into another peer, using the P2P Overlay Routing. 58 | 59 | ```JavaScript 60 | const socket = explorer.dial( [, ]) 61 | ``` 62 | 63 | Note: since an explorer node routes messages for other peers and itself, it needs first to be ready to 'listen', in order to be able to use the network to send. 64 | 65 | #### updateFinger 66 | 67 | _not implemented yet_ 68 | 69 | updates a finger on the finger table (if no finger was present on that row, it is added). 70 | 71 | ```JavaScript 72 | explorer.updateFinger() 73 | ``` 74 | 75 | #### updateFingerTable 76 | 77 | _not implemented yet_ 78 | 79 | updates all the rows on the finger table that already had a peer 80 | 81 | ```JavaScript 82 | explorer.updateFingerTable() 83 | ``` 84 | 85 | # Architecture 86 | 87 | ![](/graphs/overview.jpg) 88 | 89 | ## Signalling 90 | 91 | Currently signalling is performed through a central server. The signalling throught the Chord routing is under development. 92 | 93 | ![](/graphs/signalling.jpg) 94 | 95 | ## Routing 96 | 97 | To understand fully webrtc-explorer's core, it is important to be familiar with the [Chord][chord-paper]. 98 | 99 | I've delivered a talk before about an earlier version of webrtc-explorer, where I explain the routing scheme, you can find it here: https://youtu.be/fNQGGGE__zI?t=13m33s 100 | 101 | ![](/graphs/routing.jpg) 102 | 103 | ## Connection State 104 | 105 | Connections in webrtc-explorer are very similar to typical network socket. Before going to the network, the messages are encasulated with srcId and dstId so that they be routed through the Chord routing (parallel to the encasulation with TCP headers, IP headers, etc) 106 | 107 | ![](/graphs/connection-state.jpg) 108 | 109 | ## Notes and other properties 110 | 111 | - Ids have 48 bits (so that is a multiple of 4 (for hex notation) and doesn't require importing a big-num lib to handle over 53 bits operations) 112 | - The number of fingers of each peer is flexible, however it is recommended to not pass 16 per node (due to browser resource constraints) 113 | - Each peer is responsible for a segment of the hash ring 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | ----------------------------------------------------------------------------- 124 | 125 | ## Initial Development and release was supported by INESC-ID (circa Mar 2015) 126 | 127 | > [David Dias MSc in Peer-to-Peer Networks by Technical University of Lisbon](https://github.com/diasdavid/browserCloudjs#research-and-development) 128 | 129 | [![](https://img.shields.io/badge/INESC-GSD-brightgreen.svg?style=flat-square)](http://www.gsd.inesc-id.pt/) [![](https://img.shields.io/badge/TÉCNICO-LISBOA-blue.svg?style=flat-square)](http://tecnico.ulisboa.pt/) [![](https://img.shields.io/badge/project-browserCloudjs-blue.svg?style=flat-square)](https://github.com/diasdavid/browserCloudjs) 130 | 131 | This work was developed by David Dias with supervision by Luís Veiga, all in INESC-ID Lisboa (Distributed Systems Group), Instituto Superior Técnico, Universidade de Lisboa, with the support of Fundação para a Ciência e Tecnologia. 132 | 133 | More info on the team's work at: 134 | - http://daviddias.me 135 | - http://www.gsd.inesc-id.pt/~lveiga 136 | 137 | If you use this project, please acknowledge it in your work by referencing the following document: 138 | 139 | David Dias and Luís Veiga. browserCloud.js A federated community cloud served by a P2P overlay network on top of the web platform. INESC-ID Tec. Rep. 14/2015, Apr. 2015 140 | -------------------------------------------------------------------------------- /examples/explorer.js: -------------------------------------------------------------------------------- 1 | const explorer = require('../src/explorer') 2 | 3 | global.explorer = explorer 4 | 5 | const listener = explorer.createListener((conn) => { 6 | console.log('received conn') 7 | conn.on('data', function (data) { 8 | console.log('received some data', data) 9 | }) 10 | }) 11 | 12 | listener.listen((err) => { 13 | if (err) { 14 | return console.log('Error listening:', err) 15 | } 16 | console.log('Listening') 17 | }) 18 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /graphs/connection-state.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daviddias/webrtc-explorer/33ea3fd72712d0f356c7f73b5f8828490aa5417d/graphs/connection-state.jpg -------------------------------------------------------------------------------- /graphs/overview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daviddias/webrtc-explorer/33ea3fd72712d0f356c7f73b5f8828490aa5417d/graphs/overview.jpg -------------------------------------------------------------------------------- /graphs/routing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daviddias/webrtc-explorer/33ea3fd72712d0f356c7f73b5f8828490aa5417d/graphs/routing.jpg -------------------------------------------------------------------------------- /graphs/signalling.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daviddias/webrtc-explorer/33ea3fd72712d0f356c7f73b5f8828490aa5417d/graphs/signalling.jpg -------------------------------------------------------------------------------- /graphs/webrtc-explorer-logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daviddias/webrtc-explorer/33ea3fd72712d0f356c7f73b5f8828490aa5417d/graphs/webrtc-explorer-logo-small.png -------------------------------------------------------------------------------- /graphs/webrtc-explorer-logo.pxm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daviddias/webrtc-explorer/33ea3fd72712d0f356c7f73b5f8828490aa5417d/graphs/webrtc-explorer-logo.pxm -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webrtc-explorer", 3 | "version": "2.0.0-alpha-v0", 4 | "description": "P2P Network Routing Overlay designed for the Web platform (browsers) ", 5 | "main": "src/explorer/index.js", 6 | "bin": { 7 | "sig-server": "src/sig-server/bin.js" 8 | }, 9 | "scripts": { 10 | "lint": "standard", 11 | "test": "npm run test:sig-server && npm run test:explorer", 12 | "test:explorer": "mocha tests/explorer/index.js", 13 | "test:sig-server": "mocha tests/sig-server/index.js", 14 | "sig-server": "node src/sig-server" 15 | }, 16 | "standard": { 17 | "ignore": [ 18 | "examples/bundle.js" 19 | ] 20 | }, 21 | "precommit": [ 22 | "lint", 23 | "test" 24 | ], 25 | "keywords": [ 26 | "routing", 27 | "webrtc", 28 | "chord", 29 | "peer", 30 | "p2p", 31 | "thesis", 32 | "cloud", 33 | "distributed", 34 | "hashring" 35 | ], 36 | "repository": { 37 | "type": "git", 38 | "url": "https://github.com/diasdavid/webrtc-explorer" 39 | }, 40 | "author": "David Dias ", 41 | "license": "MIT", 42 | "dependencies": { 43 | "debug": "^2.2.0", 44 | "hapi": "^13.0.0", 45 | "simple-peer": "^6.0.1", 46 | "socket.io": "^1.4.5", 47 | "socket.io-client": "^1.4.5", 48 | "webrtc-explorer-peer-id": "^1.1.0" 49 | }, 50 | "devDependencies": { 51 | "chai": "^3.5.0", 52 | "mocha": "^2.4.5", 53 | "piri-piri": "^0.4.0", 54 | "pre-commit": "^1.1.2", 55 | "standard": "^6.0.5" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daviddias/webrtc-explorer/33ea3fd72712d0f356c7f73b5f8828490aa5417d/src/.DS_Store -------------------------------------------------------------------------------- /src/explorer/channel.js: -------------------------------------------------------------------------------- 1 | const SimplePeer = require('simple-peer') 2 | const config = require('./config') 3 | const router = require('./message-router') 4 | 5 | exports = module.exports 6 | 7 | exports.connect = (io, dstId, callback) => { 8 | const intentId = (~~(Math.random() * 1e9)).toString(36) + Date.now() 9 | 10 | const channel = new SimplePeer({initiator: true, trickle: false}) 11 | 12 | channel.on('signal', function (signal) { 13 | // console.log('send offer (src, dst):', config.peerId.toHex(), dstId) 14 | io.emit('ss-handshake', { 15 | intentId: intentId, 16 | srcId: config.peerId.toHex(), 17 | dstId: dstId, 18 | signal: signal 19 | }) 20 | }) 21 | 22 | io.on('we-handshake', (offer) => { 23 | if (offer.intentId !== intentId || !offer.answer) { 24 | return 25 | } 26 | // console.log('offer was accepted (src, dst):', config.peerId.toHex(), dstId) 27 | 28 | channel.on('connect', function () { 29 | // console.log('channel ready to send') 30 | channel.on('data', function () { 31 | console.log('DEBUG: this channel should be only used to send and not to receive') 32 | }) 33 | callback(null, channel) 34 | }) 35 | 36 | channel.signal(offer.signal) 37 | }) 38 | } 39 | 40 | exports.accept = function (io) { 41 | return (offer) => { 42 | // accept incoming DataChannels request to connect 43 | // pipe the received messages on those sockets to the message router 44 | // 45 | // note: if it says it is an answer, ignore 46 | // 47 | if (offer.answer) { return } 48 | 49 | // console.log('received an offer (src, dst):', offer.srcId, offer.dstId) 50 | const channel = new SimplePeer({trickle: false}) 51 | 52 | channel.on('connect', function () { 53 | // console.log('channel ready to listen') 54 | channel.on('data', router.route) 55 | }) 56 | 57 | channel.on('signal', function (signal) { 58 | // log('sending back my signal data') 59 | offer.signal = signal 60 | offer.answer = true 61 | io.emit('ss-handshake', offer) 62 | }) 63 | 64 | channel.signal(offer.signal) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/explorer/config.js: -------------------------------------------------------------------------------- 1 | const Id = require('webrtc-explorer-peer-id') 2 | const debug = require('debug') 3 | const log = debug('explorer') 4 | log.error = debug('explorer:error') 5 | 6 | module.exports = { 7 | log: log, 8 | peerId: new Id() 9 | } 10 | -------------------------------------------------------------------------------- /src/explorer/connection-switch.js: -------------------------------------------------------------------------------- 1 | const stream = require('stream') 2 | const PassThrough = stream.PassThrough 3 | const Writable = stream.Writable 4 | const router = require('./message-router') 5 | const config = require('./config') 6 | // const log = config.log 7 | const peerId = config.peerId 8 | 9 | exports = module.exports 10 | 11 | var incConnCB = () => {} 12 | 13 | const connections = {} 14 | // list of connections, by connId, each conn is a duplex stream pair 15 | // { connId: { 16 | // inc: duplex stream 17 | // out: duplex stream (pair of inc) 18 | // } 19 | 20 | exports.setIncConnCB = (func) => { 21 | incConnCB = func 22 | } 23 | 24 | exports.receiveMessage = (message) => { 25 | // message struct 26 | // { 27 | // srcId: 28 | // dstId: 29 | // connId: 30 | // leap: 31 | // data: 32 | // } 33 | 34 | // check if message has indeed my Id 35 | // if yes, check if there is already a conn 36 | // if yes write to that conn (inc) 37 | // if not, create a conn and call incConnCB and send a ACK back 38 | // if not, send message back saying that Id doesn't exist 39 | 40 | if (message.dstId === peerId.toHex()) { 41 | if (connections[message.connId]) { 42 | connections[message.connId].inc.write(message.data) 43 | } else { 44 | console.log('received SYN:', message.data) 45 | // we got to invert srcId and dstId so that messages are routed back 46 | const conn = createConn(message.dstId, message.srcId, message.connId) 47 | incConnCB(conn) 48 | } 49 | } else { 50 | const reply = { 51 | srcId: peerId.toHex(), 52 | dstId: message.srcId, 53 | connId: message.connId, 54 | data: message.dstId + ' does not exist' 55 | } 56 | router.send(reply) 57 | } 58 | } 59 | 60 | exports.createConn = createConn 61 | function createConn (srcId, dstId, connId) { 62 | // create a duplex stream pair 63 | // out.on('data') encapsulate chunk with: connId, srcId, dstId and then router.send(message) 64 | // 65 | console.log('creating conn') 66 | 67 | connId = connId || (~~(Math.random() * 1e9)).toString(36) + Date.now() 68 | 69 | const out = new Writable() 70 | const inc = new PassThrough() 71 | 72 | out._write = (data, enc, cb) => { 73 | const message = { 74 | connId: connId, 75 | data: data, 76 | srcId: srcId, 77 | dstId: dstId 78 | } 79 | router.send(message) 80 | cb() 81 | } 82 | 83 | connections[connId] = { 84 | inc: inc, 85 | out: out 86 | } 87 | 88 | return connections[connId] 89 | } 90 | -------------------------------------------------------------------------------- /src/explorer/finger-table.js: -------------------------------------------------------------------------------- 1 | const config = require('./config') 2 | const log = config.log 3 | const channelManager = require('./channel') 4 | const Id = require('webrtc-explorer-peer-id') 5 | 6 | exports = module.exports 7 | 8 | // {row: {peerId: <>, channel: <>}} 9 | const table = {} 10 | exports.table = table 11 | 12 | var predecessorId 13 | 14 | exports.updateFinger = function (io) { 15 | return (update) => { 16 | console.log('received an update', update) 17 | log('received an update finger', update) 18 | if (table[update.row] && table[update.row].peerId === update.id) { 19 | return console.log('update is not new') 20 | } 21 | 22 | channelManager.connect(io, update.id, (err, channel) => { 23 | if (err) { 24 | return console.log('could not create the channel', err) 25 | } 26 | console.log(update.id, 'added to finger table on row:', update.row) 27 | table[update.row] = {peerId: update.id, channel: channel} 28 | }) 29 | } 30 | } 31 | 32 | exports.updatePredecessor = function (io) { 33 | return (pId) => { 34 | predecessorId = pId 35 | } 36 | } 37 | 38 | exports.forMe = (dstId) => { 39 | var forMe = false 40 | 41 | dstId = new Id(dstId).toDec() 42 | 43 | interval(new Id(predecessorId).toDec(), config.peerId.toDec()) 44 | .some((interval) => { 45 | if (isIn(interval, dstId)) { 46 | forMe = true 47 | return true 48 | } 49 | }) 50 | 51 | return forMe 52 | } 53 | 54 | // Identify the best candidate to send when sending to destId 55 | exports.nextHop = (dstId) => { 56 | if (typeof dstId === 'object') { 57 | dstId = dstId.toDec() 58 | } else { 59 | dstId = new Id(dstId).toDec() 60 | } 61 | var lower = config.peerId 62 | var next 63 | 64 | const fingers = Object.keys(table) 65 | if (fingers.length === 1) { 66 | return table['0'].peerId 67 | } 68 | 69 | fingers.some((row) => { 70 | const upper = new Id(table[row].fingerId) 71 | 72 | interval(lower.toDec(), upper.toDec()) 73 | .some((interval) => { 74 | if (isIn(interval, dstId)) { 75 | next = upper.toHex() 76 | return true 77 | } 78 | }) 79 | 80 | // if found 81 | if (next) { return true } 82 | lower = upper 83 | }) 84 | 85 | if (!next) { // if we don't know the best, send to best we can 86 | next = lower.toHex() 87 | } 88 | 89 | return next 90 | } 91 | 92 | function interval (a, b) { 93 | const SPIN = '1000000000000' 94 | if (b < a) { 95 | return [ 96 | [a, new Id(SPIN).toDec()], 97 | [0, b] 98 | ] 99 | } else { 100 | return [[a, b]] 101 | } 102 | } 103 | 104 | function isIn (interval, dstId) { 105 | if (dstId > interval[0] && dstId <= interval[1]) { 106 | return true 107 | } else { 108 | return false 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/explorer/index.js: -------------------------------------------------------------------------------- 1 | const SocketIO = require('socket.io-client') 2 | const config = require('./config') 3 | const log = config.log 4 | const fingerTable = require('./finger-table') 5 | const connSwitch = require('./connection-switch') 6 | const channel = require('./channel') 7 | const stream = require('stream') 8 | const Duplex = stream.Duplex 9 | const peerId = config.peerId 10 | 11 | console.log('My peerId:', peerId.toHex()) 12 | 13 | var io 14 | 15 | exports = module.exports 16 | 17 | exports.dial = (dstId, callback) => { 18 | // create a duplex passthrough stream 19 | // create a conn 20 | // write a SYN to conn.out 21 | // when a ACK arrives 22 | // conn.inc.pipe(ds) 23 | // ds.pipe(conn.out) 24 | // callback(to signal that it is ready) 25 | // 26 | var set = false 27 | var reader 28 | 29 | const conn = connSwitch.createConn(peerId.toHex(), dstId) 30 | console.log('Sending SYN to:', dstId) 31 | conn.out.write('SYN') 32 | 33 | conn.inc.once('data', (data) => { 34 | console.log('received ACK:', data) 35 | conn.inc.on('data', (data) => { 36 | reader.push(data) 37 | }) 38 | if (callback) { 39 | callback(ds) 40 | } 41 | }) 42 | 43 | const ds = new Duplex({ 44 | read: function (n) { 45 | if (set) { 46 | return 47 | } 48 | set = true 49 | reader = this 50 | }, 51 | write: function (chunk, enc, next) { 52 | conn.out.write(chunk) 53 | } 54 | }) 55 | 56 | return ds 57 | } 58 | 59 | exports.createListener = (options, callback) => { 60 | if (typeof options === 'function') { 61 | callback = options 62 | options = {} 63 | } 64 | 65 | connSwitch.setIncConnCB((conn) => { 66 | // ACK 67 | // create duplexStream 68 | // ds.pipe(conn.out) 69 | // conn.inc.pipe(ds) 70 | // callback(ds) 71 | var set = false 72 | const ds = new Duplex({ 73 | read: function (n) { 74 | if (set) { 75 | return 76 | } 77 | set = true 78 | conn.inc.on('data', (data) => { 79 | this.push(data) 80 | }) 81 | }, 82 | write: function (chunk, enc, next) { 83 | conn.out.write(chunk) 84 | } 85 | }) 86 | 87 | conn.out.write('ACK') 88 | callback(ds) 89 | }) 90 | 91 | return { 92 | listen: (callback) => { 93 | // connect and join (gen Id first), wait to be established, then go 94 | connect(options.url || 'http://localhost:9000', (err) => { 95 | if (err) {} 96 | join(callback) 97 | }) 98 | } 99 | } 100 | } 101 | 102 | // update a finger by asking the sig-server what is the new best 103 | exports.updateFinger = (row) => { 104 | // TODO 105 | // 1. send a request for a finger Update on a specific row 106 | } 107 | 108 | // update every row to a new best 109 | exports.updateFingerTable = () => { 110 | // TODO 111 | // 1. send a request by each Finger Row that I'm already using 112 | } 113 | 114 | exports.getFingerTable = () => { 115 | return fingerTable.table 116 | } 117 | 118 | // connect to the sig-server 119 | function connect (url, callback) { 120 | io = SocketIO.connect(url) 121 | io.on('connect', callback) 122 | } 123 | 124 | // join the peerTable of the sig-server 125 | function join (callback) { 126 | log('connected to sig-server') 127 | io.emit('ss-join', { 128 | peerId: config.peerId.toHex(), 129 | notify: true 130 | }) 131 | io.on('we-update-finger', fingerTable.updateFinger(io)) 132 | io.on('we-handshake', channel.accept(io)) 133 | 134 | io.once('we-ready', callback) 135 | } 136 | -------------------------------------------------------------------------------- /src/explorer/message-router.js: -------------------------------------------------------------------------------- 1 | const fingerTable = require('./finger-table') 2 | const Id = require('webrtc-explorer-peer-id') 3 | const config = require('./config') 4 | // const log = config.log 5 | const peerId = config.peerId 6 | const connSwitch = require('./connection-switch') 7 | 8 | exports = module.exports 9 | 10 | exports.route = (message) => { 11 | // message struct 12 | // { 13 | // srcId: 14 | // dstId: 15 | // connId: 16 | // leap: 17 | // data: 18 | // } 19 | 20 | // 1. check if it is for me (by routing criteria or leap flag) 21 | // if yes, connSwitch.receiveMessage 22 | // if not, send 23 | message = JSON.parse(message) 24 | if (message.data.type === 'Buffer') { 25 | message.data = new Buffer(message.data.data) 26 | } 27 | const dstId = new Id(message.dstId) 28 | 29 | console.log('router: route -', message) 30 | 31 | if (message.leap || peerId.toDec() >= dstId.toDec()) { 32 | return connSwitch.receiveMessage(message) 33 | } 34 | 35 | send(message) 36 | } 37 | 38 | exports.send = send 39 | function send (message) { 40 | // send message to best next hop 41 | // note: if the nextHop is on the other side of the loop, add leap flag 42 | 43 | const nextHopId = new Id(fingerTable.nextHop(message.dstId)) 44 | console.log('next hop is:', nextHopId.toHex()) 45 | if (nextHopId.toDec() < peerId.toDec()) { 46 | message.leap = true 47 | } 48 | 49 | var channel 50 | 51 | Object.keys(fingerTable.table).forEach((row) => { 52 | if (fingerTable.table[row].peerId === nextHopId.toHex()) { 53 | channel = fingerTable.table[row].channel 54 | } 55 | }) 56 | 57 | console.log('router: send -', message) 58 | 59 | channel.send(JSON.stringify(message)) 60 | } 61 | -------------------------------------------------------------------------------- /src/sig-server/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daviddias/webrtc-explorer/33ea3fd72712d0f356c7f73b5f8828490aa5417d/src/sig-server/.DS_Store -------------------------------------------------------------------------------- /src/sig-server/bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/node 2 | 3 | const sigServer = require('./index') 4 | 5 | sigServer.start(() => { 6 | console.log('Signalling Server Started') 7 | }) 8 | 9 | process.on('SIGINT', () => { 10 | sigServer.stop(() => { 11 | console.log('Signalling Server Stopped') 12 | process.exit() 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /src/sig-server/config.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug') 2 | const log = debug('sig-server') 3 | log.error = debug('sig-server:error') 4 | 5 | module.exports = { 6 | log: log, 7 | hapi: { 8 | port: process.env.PORT || 9000, 9 | options: { 10 | connections: { 11 | routes: { 12 | cors: true 13 | } 14 | } 15 | } 16 | }, 17 | explorer: { 18 | fingers: [ 19 | 0, 20 | 10, 21 | 12, 22 | 20, 23 | 30, 24 | 47 25 | ], 26 | 'min-peers': 4 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/sig-server/index.js: -------------------------------------------------------------------------------- 1 | const Hapi = require('Hapi') 2 | const config = require('./config') 3 | const log = config.log 4 | 5 | exports = module.exports 6 | 7 | exports.start = (callback) => { 8 | exports.http = new Hapi.Server(config.hapi.options) 9 | 10 | exports.http.connection({ 11 | port: config.hapi.port 12 | }) 13 | 14 | require('./routes-http/basic') 15 | // require('./routes-http/graph') 16 | 17 | exports.http.start(() => { 18 | log('signaling server has started on: ' + exports.http.info.uri) 19 | require('./routes-ws') 20 | callback() 21 | }) 22 | } 23 | 24 | exports.stop = (callback) => { 25 | exports.http.stop(callback) 26 | } 27 | -------------------------------------------------------------------------------- /src/sig-server/resources/peer-table.js: -------------------------------------------------------------------------------- 1 | // id : { 2 | // socket: 3 | // fingerTable: { 4 | // row : { 5 | // ideal: 6 | // current: 7 | // } 8 | // } 9 | // predecessorId 10 | // } 11 | const peerTable = {} 12 | 13 | module.exports = peerTable 14 | -------------------------------------------------------------------------------- /src/sig-server/resources/peer.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daviddias/webrtc-explorer/33ea3fd72712d0f356c7f73b5f8828490aa5417d/src/sig-server/resources/peer.js -------------------------------------------------------------------------------- /src/sig-server/routes-http/basic.js: -------------------------------------------------------------------------------- 1 | const server = require('../').http 2 | 3 | server.route({ 4 | method: 'GET', 5 | path: '/', 6 | handler: function (request, reply) { 7 | reply('signaling server') 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /src/sig-server/routes-http/graph.js: -------------------------------------------------------------------------------- 1 | const server = require('../').http 2 | const peerTable = require('../resources/peer-table') 3 | 4 | server.route({ 5 | method: 'get', 6 | path: '/dht', 7 | handler: function (request, reply) { 8 | reply(peerTable) 9 | } 10 | }) 11 | 12 | -------------------------------------------------------------------------------- /src/sig-server/routes-ws/index.js: -------------------------------------------------------------------------------- 1 | const config = require('../config') 2 | const log = config.log 3 | const peerTable = require('../resources/peer-table') 4 | const http = require('../index').http 5 | const SocketIO = require('socket.io') 6 | const Id = require('webrtc-explorer-peer-id') 7 | const idealFinger = require('../utils/ideal-finger') 8 | const fingerBestFit = require('../utils/finger-best-fit') 9 | 10 | const io = new SocketIO(http.listener) 11 | io.on('connection', handle) 12 | 13 | function handle (socket) { 14 | log('received inbound ws conn') 15 | 16 | socket.on('ss-join', join.bind(socket)) 17 | socket.on('disconnect', remove.bind(socket)) // socket.io own event 18 | socket.on('ss-handshake', forward.bind(socket)) 19 | socket.on('ss-update-finger', (request) => { 20 | updateFinger(request.peerId, request.row) 21 | }) 22 | } 23 | 24 | // join this signaling server network 25 | function join (options) { 26 | if (options.peerId.length !== 12) { 27 | return this.emit('we-ready', new Error('Unvalid peerId length, must be 48 bits, received: ' + options.peerId).toString()) 28 | } 29 | 30 | if (peerTable[options.peerId]) { 31 | return this.emit('we-ready', new Error('peerId already exists').toString()) 32 | } 33 | 34 | peerTable[options.peerId] = { 35 | socket: this, 36 | notify: typeof options.notify === 'boolean' && options.notify === true, 37 | fingers: options.fingers || { 38 | '0': { 39 | ideal: idealFinger(new Id(options.peerId), '0').toHex(), 40 | current: undefined 41 | }} 42 | } 43 | 44 | log('peer joined: ' + options.peerId) 45 | this.emit('we-ready') 46 | if (Object.keys(peerTable).length === 1) { 47 | return log('This was the first peer join, do nothing') 48 | } 49 | notify() 50 | if (peerTable[options.peerId].fingers['0'].current === undefined) { 51 | if (!peerTable[options.peerId].notify) { 52 | return 53 | } 54 | updateFinger(options.peerId, '0') 55 | } 56 | 57 | // notify if to other peers if this new Peer is a best finger for them 58 | function notify () { 59 | const newId = options.peerId 60 | const peerIds = Object.keys(peerTable) 61 | // check for all the peers 62 | // if same id skip 63 | // if notify === false skip 64 | // check the first finger that matches the criteria for ideal or next to ideal 65 | peerIds.forEach((peerId) => { 66 | if (newId === peerId) { 67 | return // skip ourselves 68 | } 69 | if (!peerTable[peerId].notify) { 70 | return // skip if it doesn't want to be notified of available peers 71 | } 72 | 73 | // if it had none, notify to get a successor 74 | if (peerTable[peerId].fingers['0'].current === undefined) { 75 | peerTable[peerId].fingers['0'].current = newId 76 | peerTable[peerId].socket.emit('we-update-finger', { 77 | row: '0', 78 | id: newId 79 | }) 80 | return 81 | } 82 | 83 | const rows = Object.keys(peerTable[peerId].fingers) 84 | // find the first row that could use this finger 85 | rows.some((row) => { 86 | const finger = peerTable[peerId].fingers[row] 87 | const bestCandidate = fingerBestFit( 88 | new Id(peerId), 89 | new Id(finger.ideal), 90 | new Id(finger.current), 91 | new Id(newId)) 92 | if (bestCandidate) { 93 | peerTable[peerId].fingers[row].current = newId 94 | peerTable[peerId].socket.emit('we-update-finger', { 95 | row: row, 96 | id: newId 97 | }) 98 | return true 99 | } 100 | }) 101 | }) 102 | } 103 | } 104 | 105 | // finds the best new Finger for the peerId's row 'row') 106 | function updateFinger (peerId, row) { 107 | var availablePeers = Object.keys(peerTable) 108 | availablePeers.splice(availablePeers.indexOf(peerId), 1) 109 | 110 | // if row hasn't been checked before 111 | if (!peerTable[peerId].fingers[row]) { 112 | peerTable[peerId].fingers[row] = { 113 | ideal: idealFinger(new Id(peerId), row).toHex(), 114 | current: undefined 115 | } 116 | } 117 | 118 | var best = availablePeers.shift() 119 | availablePeers.forEach((otherId) => { 120 | const isFBT = fingerBestFit( 121 | new Id(peerId), 122 | new Id(peerTable[peerId].fingers[row].ideal), 123 | new Id(best), 124 | new Id(otherId)) 125 | if (isFBT) { 126 | best = otherId 127 | } 128 | }) 129 | if (best === peerTable[peerId].fingers[row].current) { 130 | return // nevermind then 131 | } 132 | peerTable[peerId].fingers[row].current = best 133 | peerTable[peerId].socket.emit('we-update-finger', { 134 | row: row, 135 | id: best 136 | }) 137 | } 138 | 139 | function remove () { 140 | Object.keys(peerTable).forEach((peerId) => { 141 | if (peerTable[peerId].socket.id === this.id) { 142 | delete peerTable[peerId] 143 | log('peer disconnected: ' + peerId) 144 | } 145 | }) 146 | } 147 | 148 | // forward an WebRTC offer to another peer 149 | function forward (offer) { 150 | if (offer.answer) { 151 | peerTable[offer.srcId].socket 152 | .emit('we-handshake', offer) 153 | return 154 | } 155 | peerTable[offer.dstId].socket 156 | .emit('we-handshake', offer) 157 | } 158 | 159 | /* 160 | function handle (socket) { 161 | log('received incoming WebSockets conn') 162 | 163 | socket.on('s-register', registerPeer) 164 | socket.on('disconnect', peerRemove) // socket.io own event 165 | socket.on('s-send-offer', sendOffer) 166 | socket.on('s-offer-accepted', offerAccepted) 167 | 168 | function registerPeer () { 169 | console.log('registerPeer') 170 | var peerId = new Id() 171 | peers[peerId.toHex()] = { 172 | socketId: socket.id, 173 | fingerTable: {} 174 | } 175 | 176 | console.log('->', peers) 177 | 178 | sockets[socket.id] = socket 179 | 180 | socket.emit('c-registered', {peerId: peerId.toHex()}) 181 | 182 | console.log('registered new peer: ', peerId.toHex()) 183 | 184 | calculateIdealFingers(peerId) 185 | updateFingers() 186 | } 187 | 188 | function calculateIdealFingers (peerId) { 189 | var fingers = config.explorer.fingers 190 | var k = 1 191 | while (k <= fingers.length) { 192 | var ideal = (peerId.toDec() + Math.pow(2, fingers[k - 1])) % 193 | Math.pow(2, 48) 194 | peers[peerId.toHex()].fingerTable[k] = { 195 | ideal: new Id(ideal).toHex(), 196 | current: undefined 197 | } 198 | k++ 199 | } 200 | } 201 | 202 | function updateFingers () { 203 | if (Object.keys(peers).length < 2) { 204 | return 205 | } 206 | 207 | var sortedPeersId = Object.keys(peers).sort(function (a, b) { 208 | var aId = new Id(a) 209 | var bId = new Id(b) 210 | if (aId.toDec() > bId.toDec()) { 211 | return 1 212 | } 213 | if (aId.toDec() < bId.toDec()) { 214 | return -1 215 | } 216 | if (aId.toDec() === bId.toDec()) { 217 | console.log('error - There should never two identical ids') 218 | process.exit(1) 219 | } 220 | }) 221 | 222 | sortedPeersId.forEach(function (peerId) { 223 | // predecessor 224 | var predecessorId = predecessorTo(peerId, sortedPeersId) 225 | 226 | if (peers[peerId].predecessorId !== predecessorId) { 227 | sockets[peers[peerId].socketId].emit('c-predecessor', { 228 | predecessorId: predecessorId 229 | }) 230 | 231 | peers[peerId].predecessorId = predecessorId 232 | } 233 | 234 | // sucessors 235 | 236 | Object.keys(peers[peerId].fingerTable).some(function (rowIndex) { 237 | var fingerId = sucessorTo(peers[peerId] 238 | .fingerTable[rowIndex] 239 | .ideal, sortedPeersId) 240 | 241 | if (peers[peerId].fingerTable[rowIndex].current !== 242 | fingerId) { 243 | peers[peerId].fingerTable[rowIndex].current = fingerId 244 | 245 | sockets[peers[peerId].socketId].emit('c-finger-update', { 246 | rowIndex: rowIndex, 247 | fingerId: fingerId 248 | }) 249 | } 250 | 251 | if (Object.keys(peers).length < config.explorer['min-peers']) { 252 | return true // stops the loop, calculates only 253 | // for the first position (aka sucessor of the node 254 | } 255 | }) 256 | }) 257 | 258 | function sucessorTo (pretendedId, sortedIdList) { 259 | pretendedId = new Id(pretendedId).toDec() 260 | sortedIdList = sortedIdList.map(function (inHex) { 261 | return new Id(inHex).toDec() 262 | }) 263 | 264 | var sucessorId 265 | sortedIdList.some(function (value, index) { 266 | if (pretendedId === value) { 267 | sucessorId = value 268 | return true 269 | } 270 | 271 | if (pretendedId < value) { 272 | sucessorId = value 273 | return true 274 | } 275 | 276 | if (index + 1 === sortedIdList.length) { 277 | sucessorId = sortedIdList[0] 278 | return true 279 | } 280 | }) 281 | 282 | return new Id(sucessorId).toHex() 283 | } 284 | 285 | function predecessorTo (peerId, sortedIdList) { 286 | var index = sortedIdList.indexOf(peerId) 287 | 288 | var predecessorId 289 | 290 | if (index === 0) { 291 | predecessorId = sortedIdList[sortedIdList.length - 1] 292 | } else { 293 | predecessorId = sortedIdList[index - 1] 294 | } 295 | 296 | return new Id(predecessorId).toHex() 297 | } 298 | } 299 | 300 | function peerRemove () { 301 | Object.keys(peers).map(function (peerId) { 302 | if (peers[peerId].socketId === socket.id) { 303 | delete peers[peerId] 304 | delete sockets[socket.id] 305 | console.log('peer with Id: %s has disconnected', peerId) 306 | } 307 | }) 308 | } 309 | 310 | // signalling mediation between two peers 311 | 312 | function sendOffer (data) { 313 | sockets[peers[data.offer.dstId].socketId] 314 | .emit('c-accept-offer', data) 315 | } 316 | 317 | function offerAccepted (data) { 318 | sockets[peers[data.offer.srcId].socketId] 319 | .emit('c-offer-accepted', data) 320 | } 321 | }*/ 322 | -------------------------------------------------------------------------------- /src/sig-server/utils/finger-best-fit.js: -------------------------------------------------------------------------------- 1 | const Id = require('webrtc-explorer-peer-id') 2 | 3 | module.exports = (peerId, idealFingerId, currentFingerId, newFingerId) => { 4 | const p = peerId.toDec() 5 | const i = idealFingerId.toDec() 6 | const c = currentFingerId.toDec() 7 | const n = newFingerId.toDec() 8 | 9 | // does it leap 10 | if (i < p) { 11 | // is it on the left 12 | if (n <= new Id('ffffffffffff').toDec() && n > p && n > c) { 13 | return true 14 | } 15 | // is it on the right 16 | if (n > 0 && n < c) { 17 | return true 18 | } 19 | } else { 20 | if (c < p && n > p) { 21 | return true 22 | } 23 | if (n >= i && n < c) { 24 | return true 25 | } 26 | } 27 | return false 28 | } 29 | -------------------------------------------------------------------------------- /src/sig-server/utils/ideal-finger.js: -------------------------------------------------------------------------------- 1 | const Id = require('webrtc-explorer-peer-id') 2 | 3 | module.exports = (peerId, row) => { 4 | const k = Number(row) + 1 5 | const ideal = (peerId.toDec() + Math.pow(2, k - 1)) % Math.pow(2, 48) 6 | return new Id(ideal) 7 | } 8 | -------------------------------------------------------------------------------- /tests/explorer/index.js: -------------------------------------------------------------------------------- 1 | /* globals describe, before, after */ 2 | 3 | const sigServer = require('../../src/sig-server') 4 | const fs = require('fs') 5 | 6 | describe('explorer', () => { 7 | before((done) => { 8 | sigServer.start(done) 9 | }) 10 | 11 | after((done) => { 12 | sigServer.stop(done) 13 | }) 14 | 15 | const tests = fs.readdirSync(__dirname) 16 | tests.filter((file) => { 17 | if (file === 'index.js' || file === 'scripts') { return false } 18 | return true 19 | }).forEach((file) => { 20 | require('./' + file) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /tests/explorer/scripts/explorer-peer.js: -------------------------------------------------------------------------------- 1 | const explorer = require('./../../../src/explorer/index.js') 2 | const ppc = require('piri-piri').client 3 | 4 | module.exports = function (args) { 5 | ppc.handle('listen', () => { 6 | const listener = explorer.createListener((conn) => { 7 | console.log('received conn') 8 | }) 9 | 10 | listener.listen((err) => { 11 | if (err) { 12 | return console.log('Error listening:', err) 13 | } 14 | ppc.send('listening') 15 | }) 16 | }) 17 | 18 | ppc.handle('get-finger-table', () => { 19 | var ft = explorer.getFingerTable() 20 | ft = Object.keys(ft).map((row) => { 21 | var el = {} 22 | el[row] = ft[row].peerId 23 | return el 24 | }) 25 | ppc.send(ft) 26 | }) 27 | 28 | // only connect after registering all the handles 29 | ppc.connect((err, socket) => { 30 | if (err) { 31 | return console.log(err) 32 | } 33 | console.log('### connected to piri-piri') 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /tests/explorer/test-explorer.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it, before, after */ 2 | 3 | const pp = require('piri-piri') 4 | // const Id = require('webrtc-explorer-peer-id') 5 | const expect = require('chai').expect 6 | 7 | describe('explorer', () => { 8 | var ppId0 9 | var ppId1 10 | 11 | before((done) => { 12 | pp.start((err) => { 13 | expect(err).to.not.exist 14 | done() 15 | }) 16 | }) 17 | 18 | after((done) => { 19 | Object.keys(pp.clients).forEach((id) => { 20 | pp.browser.send(id, 'exit') 21 | }) 22 | done() 23 | }) 24 | 25 | it('spawn first browser', (done) => { 26 | expect(Object.keys(pp.clients).length).to.equal(0) 27 | pp.browser.spawn('./tests/explorer/scripts/explorer-peer.js', 1, (err) => { 28 | // this only happens on the after, when browsers are told to exit 29 | expect(err).to.not.exist 30 | }) 31 | setTimeout(() => { 32 | expect(Object.keys(pp.clients)[0]).to.exist 33 | ppId0 = Object.keys(pp.clients)[0] 34 | done() 35 | }, 500) 36 | }) 37 | 38 | it('browser 0 - join (listen)', (done) => { 39 | pp.browser.send(ppId0, 'listen') 40 | setTimeout(() => { 41 | expect(pp.clients[ppId0].msgs.length).to.equal(1) 42 | const msg = pp.clients[ppId0].msgs.shift() 43 | expect(msg).to.equal('listening') 44 | done() 45 | }, 500) 46 | }) 47 | 48 | it('spawn another browser', (done) => { 49 | expect(Object.keys(pp.clients).length).to.equal(1) 50 | pp.browser.spawn('./tests/explorer/scripts/explorer-peer.js', 1, (err) => { 51 | // this only happens on the after, when browsers are told to exit 52 | expect(err).to.not.exist 53 | }) 54 | setTimeout(() => { 55 | expect(Object.keys(pp.clients)[1]).to.exist 56 | ppId1 = Object.keys(pp.clients)[1] 57 | done() 58 | }, 500) 59 | }) 60 | 61 | it('browser 1 - join (listen)', function (done) { 62 | this.timeout(50000) 63 | pp.browser.send(ppId1, 'listen') 64 | setTimeout(() => { 65 | expect(pp.clients[ppId1].msgs.length).to.equal(1) 66 | var msg = pp.clients[ppId1].msgs.shift() 67 | setTimeout(() => { 68 | pp.browser.send(ppId1, 'get-finger-table') 69 | setTimeout(() => { 70 | expect(pp.clients[ppId1].msgs.length).to.equal(1) 71 | msg = pp.clients[ppId1].msgs.shift() 72 | expect(Object.keys(msg[0])[0]).to.equal('0') 73 | done() 74 | }, 200) 75 | }, 200) 76 | }, 500) 77 | }) 78 | }) 79 | -------------------------------------------------------------------------------- /tests/sig-server/index.js: -------------------------------------------------------------------------------- 1 | /* globals describe, before, after */ 2 | 3 | const sigServer = require('../../src/sig-server') 4 | const fs = require('fs') 5 | 6 | describe('sig-server', () => { 7 | before((done) => { 8 | sigServer.start(done) 9 | }) 10 | 11 | after((done) => { 12 | sigServer.stop(done) 13 | }) 14 | 15 | const tests = fs.readdirSync(__dirname) 16 | tests.filter((file) => { 17 | if (file !== 'index.js') { return true } 18 | return false 19 | }).forEach((file) => { 20 | require('./' + file) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /tests/sig-server/test-finger-best-fit.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it*/ 2 | 3 | const expect = require('chai').expect 4 | const Id = require('webrtc-explorer-peer-id') 5 | const fingerBestFit = require('../../src/sig-server/utils/finger-best-fit') 6 | 7 | describe('finger-best-fit', () => { 8 | it('peer id > 0 && < MAX', (done) => { 9 | const randomId = new Id('ffffff000000') 10 | 11 | const idealFingerId = new Id(randomId.toDec() + 200) 12 | 13 | const currentFingerId = new Id(randomId.toDec() + 250) 14 | 15 | const newFingerIdA = new Id(randomId.toDec() + 100) 16 | const newFingerIdB = new Id(randomId.toDec() + 230) 17 | const newFingerIdC = new Id(randomId.toDec() + 280) 18 | 19 | expect(fingerBestFit(randomId, idealFingerId, currentFingerId, newFingerIdA)).to.equal(false) 20 | expect(fingerBestFit(randomId, idealFingerId, currentFingerId, newFingerIdB)).to.equal(true) 21 | expect(fingerBestFit(randomId, idealFingerId, currentFingerId, newFingerIdC)).to.equal(false) 22 | done() 23 | }) 24 | 25 | it('when a smaller was in place', (done) => { 26 | const peerId = new Id(366279746573) 27 | const currentId = new Id(343862697473) 28 | const idealId = new Id(366279746574) 29 | const newId = new Id(616263646566) 30 | expect(fingerBestFit(peerId, idealId, currentId, newId)).to.equal(true) 31 | done() 32 | }) 33 | 34 | it.skip('peer id === 0', (done) => { 35 | done() 36 | }) 37 | 38 | it.skip('peer id === MAX', (done) => { 39 | done() 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /tests/sig-server/test-handshake.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it, after */ 2 | 3 | const expect = require('chai').expect 4 | const io = require('socket.io-client') 5 | 6 | describe('handshake', () => { 7 | const options = { 8 | transports: ['websocket'], 9 | 'force new connection': true 10 | } 11 | 12 | const url = 'http://localhost:9000' 13 | 14 | var c1 15 | var c2 16 | const c1Id = new Buffer('6bytes').toString('hex') 17 | const c2Id = new Buffer('48bits').toString('hex') 18 | 19 | after((done) => { 20 | c1.disconnect() 21 | c2.disconnect() 22 | 23 | done() 24 | }) 25 | 26 | it('io connect', (done) => { 27 | var count = 0 28 | 29 | c1 = io.connect(url, options) 30 | c2 = io.connect(url, options) 31 | 32 | c1.on('connect', connected) 33 | c2.on('connect', connected) 34 | 35 | function connected () { 36 | if (++count === 2) { 37 | done() 38 | } 39 | } 40 | }) 41 | 42 | it('2 peers join', (done) => { 43 | var count = 0 44 | 45 | c1.once('we-ready', completed) 46 | c2.once('we-ready', completed) 47 | 48 | c1.emit('ss-join', { 49 | peerId: c1Id 50 | }) 51 | c2.emit('ss-join', { 52 | peerId: c2Id 53 | }) 54 | 55 | function completed (err) { 56 | expect(err).to.not.exist 57 | 58 | if (++count === 2) { 59 | done() 60 | } 61 | } 62 | }) 63 | 64 | it('perform pseudo WebRTC handshake', (done) => { 65 | const originalOffer = { 66 | srcId: c1Id, 67 | dstId: c2Id, 68 | intentId: '1234', 69 | webrtc: 'chicken' 70 | } 71 | c1.once('we-handshake', (offer) => { 72 | expect(offer.webrtc).to.equal('pineapple') 73 | done() 74 | }) 75 | c2.once('we-handshake', (offer) => { 76 | expect(offer.webrtc).to.equal('chicken') 77 | offer.webrtc = 'pineapple' 78 | offer.answer = true 79 | c2.emit('ss-handshake', offer) 80 | }) 81 | 82 | c1.emit('ss-handshake', originalOffer) 83 | }) 84 | }) 85 | -------------------------------------------------------------------------------- /tests/sig-server/test-http.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it*/ 2 | 3 | const expect = require('chai').expect 4 | const sigServer = require('../../src/sig-server') 5 | 6 | describe('http routes', () => { 7 | it('/', (done) => { 8 | sigServer.http.inject({ 9 | method: 'GET', 10 | url: '/' 11 | }, (res) => { 12 | expect(res.payload).to.equal('signaling server') 13 | done() 14 | }) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /tests/sig-server/test-join.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it, after */ 2 | 3 | const expect = require('chai').expect 4 | const io = require('socket.io-client') 5 | 6 | describe('join', () => { 7 | const options = { 8 | transports: ['websocket'], 9 | 'force new connection': true 10 | } 11 | 12 | const url = 'http://localhost:9000' 13 | 14 | var c1 15 | var c2 16 | var c3 17 | 18 | after((done) => { 19 | c1.disconnect() 20 | c2.disconnect() 21 | c3.disconnect() 22 | 23 | done() 24 | }) 25 | 26 | it('io connect', (done) => { 27 | var count = 0 28 | 29 | c1 = io.connect(url, options) 30 | c2 = io.connect(url, options) 31 | c3 = io.connect(url, options) 32 | 33 | c1.on('connect', connected) 34 | c2.on('connect', connected) 35 | c3.on('connect', connected) 36 | 37 | function connected () { 38 | if (++count === 3) { 39 | done() 40 | } 41 | } 42 | }) 43 | 44 | it('join with notify off', (done) => { 45 | var count = 0 46 | 47 | c1.once('we-ready', completed) 48 | c2.once('we-ready', completed) 49 | 50 | c1.emit('ss-join', { 51 | peerId: new Buffer('6bytes').toString('hex'), 52 | notify: false 53 | }) 54 | c2.emit('ss-join', { 55 | peerId: new Buffer('48bits').toString('hex'), 56 | notify: false 57 | }) 58 | 59 | function completed (err) { 60 | expect(err).to.not.exist 61 | 62 | if (++count === 2) { 63 | done() 64 | } 65 | } 66 | }) 67 | 68 | it('join with incorrect Id', (done) => { 69 | c3.once('we-ready', completed) 70 | 71 | c3.emit('ss-join', { 72 | peerId: new Buffer('incorrect').toString('hex'), 73 | notify: false 74 | }) 75 | 76 | function completed (err) { 77 | expect(err).to.exist 78 | done() 79 | } 80 | }) 81 | 82 | it('io disconnect and connect again', (done) => { 83 | var count = 0 84 | 85 | c1.disconnect() 86 | c2.disconnect() 87 | c3.disconnect() 88 | 89 | c1 = io.connect(url, options) 90 | c2 = io.connect(url, options) 91 | c3 = io.connect(url, options) 92 | 93 | c1.on('connect', connected) 94 | c2.on('connect', connected) 95 | c3.on('connect', connected) 96 | 97 | function connected () { 98 | if (++count === 3) { 99 | done() 100 | } 101 | } 102 | }) 103 | 104 | it('2 join with notify on', (done) => { 105 | // join with 1 and 2, check that only after 2 joins, one gets the message to connect to it, this way we avoid double call collision 106 | 107 | const c1Id = new Buffer('6bytes').toString('hex') 108 | const c2Id = new Buffer('48bits').toString('hex') 109 | 110 | // console.log('c1', c1Id) 111 | // console.log('c2', c2Id) 112 | 113 | c1.once('we-update-finger', (update) => { 114 | expect(update.id).to.equal(c2Id) 115 | expect(update.row).to.equal('0') 116 | c2.removeListener('we-update-finger') 117 | done() 118 | }) 119 | c2.on('we-update-finger', () => { 120 | throw new Error('should not happen') 121 | }) 122 | 123 | c1.emit('ss-join', { 124 | peerId: c1Id, 125 | notify: true 126 | }) 127 | 128 | c2.emit('ss-join', { 129 | peerId: c2Id 130 | }) 131 | }) 132 | 133 | it('3rd joins with notify on', (done) => { 134 | var count = 0 135 | 136 | const c3Id = new Buffer('abcdef').toString('hex') 137 | // console.log('c3', c3Id) 138 | 139 | c1.once('we-update-finger', receivedUpdate) 140 | c3.once('we-update-finger', receivedUpdate) 141 | 142 | c3.emit('ss-join', { 143 | peerId: c3Id, 144 | notify: true 145 | }) 146 | 147 | function receivedUpdate (update) { 148 | expect(update).to.exist 149 | expect(update.id).to.exist 150 | expect(update.row).to.equal('0') 151 | 152 | if (++count === 2) { 153 | done() 154 | } 155 | } 156 | }) 157 | }) 158 | -------------------------------------------------------------------------------- /tests/sig-server/test-lifecycle.js: -------------------------------------------------------------------------------- 1 | // TODO test 10 peers joining, registering, sending the handshakes and all 2 | -------------------------------------------------------------------------------- /tests/sig-server/test-update-finger.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it, after */ 2 | 3 | const expect = require('chai').expect 4 | const io = require('socket.io-client') 5 | 6 | describe('update-finger', () => { 7 | const options = { 8 | transports: ['websocket'], 9 | 'force new connection': true 10 | } 11 | 12 | const url = 'http://localhost:9000' 13 | 14 | var c1 15 | var c2 16 | var c3 17 | var c4 18 | var c5 19 | const c1Id = new Buffer('48bits').toString('hex') 20 | const c2Id = new Buffer('6bytes').toString('hex') 21 | const c3Id = new Buffer('batata').toString('hex') 22 | const c4Id = new Buffer('cebola').toString('hex') 23 | 24 | // console.log('c1', c1Id) 25 | // console.log('c2', c2Id) 26 | // console.log('c3', c3Id) 27 | // console.log('c4', c4Id) 28 | 29 | after((done) => { 30 | c1.disconnect() 31 | c2.disconnect() 32 | c3.disconnect() 33 | c4.disconnect() 34 | c5.disconnect() 35 | 36 | done() 37 | }) 38 | 39 | it('io connect', (done) => { 40 | var count = 0 41 | 42 | c1 = io.connect(url, options) 43 | c2 = io.connect(url, options) 44 | c3 = io.connect(url, options) 45 | c4 = io.connect(url, options) 46 | 47 | c1.on('connect', connected) 48 | c2.on('connect', connected) 49 | c3.on('connect', connected) 50 | c4.on('connect', connected) 51 | 52 | function connected () { 53 | if (++count === 4) { 54 | done() 55 | } 56 | } 57 | }) 58 | 59 | it('join four', function (done) { 60 | this.timeout(50000) 61 | var count = 0 62 | 63 | c1.once('we-ready', tick) 64 | c2.once('we-ready', tick) 65 | c3.once('we-ready', tick) 66 | c4.once('we-ready', tick) 67 | 68 | c1.once('we-update-finger', (update) => { 69 | expect(update.row).to.equal('0') 70 | expect(update.id).to.equal(c2Id) 71 | tick() 72 | }) 73 | c2.once('we-update-finger', (update) => { 74 | expect(update.row).to.equal('0') 75 | expect(update.id).to.equal(c1Id) 76 | tick() 77 | c2.once('we-update-finger', (update) => { 78 | expect(update.row).to.equal('0') 79 | expect(update.id).to.equal(c3Id) 80 | tick() 81 | }) 82 | }) 83 | c3.once('we-update-finger', (update) => { 84 | expect(update.row).to.equal('0') 85 | expect(update.id).to.equal(c1Id) 86 | tick() 87 | c3.once('we-update-finger', (update) => { 88 | expect(update.row).to.equal('0') 89 | expect(update.id).to.equal(c4Id) 90 | tick() 91 | }) 92 | }) 93 | c4.once('we-update-finger', (update) => { 94 | expect(update.row).to.equal('0') 95 | expect(update.id).to.equal(c1Id) 96 | tick() 97 | }) 98 | 99 | function tick () { 100 | if (++count === 10) { 101 | done() 102 | // setTimeout(done, 800) 103 | } 104 | } 105 | 106 | c1.emit('ss-join', { peerId: c1Id, notify: true }) 107 | c2.emit('ss-join', { peerId: c2Id, notify: true }) 108 | c3.emit('ss-join', { peerId: c3Id, notify: true }) 109 | c4.emit('ss-join', { peerId: c4Id, notify: true }) 110 | }) 111 | 112 | it('update-finger c1 row 2', (done) => { 113 | c1.once('we-update-finger', (update) => { 114 | expect(update.row).to.equal('2') 115 | done() 116 | }) 117 | c1.emit('ss-update-finger', {peerId: c1Id, row: '2'}) 118 | }) 119 | 120 | it('update-finger c2 row 10', (done) => { 121 | c2.once('we-update-finger', (update) => { 122 | expect(update.row).to.equal('10') 123 | done() 124 | }) 125 | c2.emit('ss-update-finger', {peerId: c2Id, row: '10'}) 126 | }) 127 | 128 | it('update-finger c3 row 25 and 30', (done) => { 129 | c3.once('we-update-finger', (update) => { 130 | expect(update.row).to.equal('25') 131 | c3.emit('ss-update-finger', {peerId: c3Id, row: '30'}) 132 | c3.once('we-update-finger', (update) => { 133 | expect(update.row).to.equal('30') 134 | done() 135 | }) 136 | }) 137 | c3.emit('ss-update-finger', {peerId: c3Id, row: '25'}) 138 | }) 139 | 140 | it('join peer c5', (done) => { 141 | const c5Id = new Buffer('fifthe').toString('hex') 142 | // console.log(c5Id) 143 | c5 = io.connect(url, options) 144 | c5.on('connect', () => { 145 | c5.emit('ss-join', { peerId: c5Id, notify: true }) 146 | }) 147 | 148 | var count = 0 149 | 150 | c4.once('we-update-finger', tick) 151 | c5.once('we-update-finger', tick) 152 | 153 | function tick (update) { 154 | if (++count === 2) { 155 | done() 156 | } 157 | } 158 | }) 159 | 160 | it.skip('make a test with even more versatility', (done) => {}) 161 | }) 162 | --------------------------------------------------------------------------------