diff --git a/.jshintignore b/.jshintignore new file mode 100644 index 00000000..3c3629e6 --- /dev/null +++ b/.jshintignore @@ -0,0 +1 @@ +node_modules diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 00000000..997b3f7d --- /dev/null +++ b/.jshintrc @@ -0,0 +1,10 @@ +{ + "node": true, + + "curly": true, + "latedef": true, + "quotmark": true, + "undef": true, + "unused": true, + "trailing": true +} diff --git a/examples/network-c.js b/examples/network-c.js new file mode 100644 index 00000000..4e411f43 --- /dev/null +++ b/examples/network-c.js @@ -0,0 +1,21 @@ +var swarm = require('./network/swarm') +var Peer = require('./routing/routers/dht/peer') +var Id = require('./routing/routers/dht/peer/id') +var multiaddr = require('multiaddr') + +// create Id +// create multiaddr +// create Peer +// openStream + +var peerId = Id.create() +var mhs = [] +mhs.push(multiaddr('/ip4/127.0.0.1/tcp/4001')) +var p = new Peer(peerId, mhs) + +swarm.openStream(p, '/ipfs/sparkles/1.2.3', function (err, stream) { + if (err) { + return console.log('ERR - ', err) + } + console.log('WoHoo, dialed a stream') +}) diff --git a/examples/network-s.js b/examples/network-s.js new file mode 100644 index 00000000..439f256b --- /dev/null +++ b/examples/network-s.js @@ -0,0 +1,7 @@ +var swarm = require('./network/swarm') + +swarm.listen() + +swarm.registerHandle('/ipfs/sparkles/1.2.3', function (stream) { + console.log('woop got a stream') +}) diff --git a/package.json b/package.json new file mode 100644 index 00000000..7bcae4ba --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "ipfs-swarm", + "version": "0.0.0", + "description": "IPFS swarm implementation in Node.js", + "main": "src/index.js", + "scripts": { + "test": "./node_modules/.bin/lab tests/*-test.js", + "coverage": "./node_modules/.bin/lab -t 100 tests/*-test.js", + "codestyle": "./node_modules/.bin/standard --format" + }, + "repository": { + "type": "git", + "url": "https://github.com/diasdavid/node-ipfs-swarm.git" + }, + "keywords": [ + "IPFS" + ], + "author": "David Dias ", + "license": "MIT", + "bugs": { + "url": "https://github.com/diasdavid/node-ipfs-swarm/issues" + }, + "homepage": "https://github.com/diasdavid/node-ipfs-swarm", + "pre-commit": [ + "codestyle", + "test" + ], + "devDependencies": { + "code": "^1.4.1", + "lab": "^5.13.0", + "precommit-hook": "^3.0.0", + "standard": "^4.5.2" + } +} diff --git a/src/identify.js b/src/identify.js new file mode 100644 index 00000000..34f9792f --- /dev/null +++ b/src/identify.js @@ -0,0 +1,45 @@ +/* + * Identify is one of the protocols swarms speaks in order to broadcast and learn about the ip:port + * pairs a specific peer is available through + */ + +var swarm = require('./../swarm') +var Interactive = require('multistream-select').Interactive + +exports = module.exports + +// peer acting as server, asking whom is talking +exports.inquiry = function (spdyConnection, cb) { + spdyConnection.request({method: 'GET', path: '/', headers: {}}, function (stream) { + var msi = new Interactive() + msi.handle(stream) + msi.select('/ipfs/identify/1.0.0', function (ds) { + var peerId = '' + ds.setEncoding('utf8') + + ds.on('data', function (chunk) { + peerId += chunk + }) + ds.on('end', function () { + cb(null, spdyConnection, peerId) + }) + }) + }) + // 0. open a stream + // 1. negotiate /ipfs/identify/1.0.0 + // 2. check other peerId + // 3. reply back with cb(null, connection, peerId) +} + +// peer asking which pairs ip:port does the other peer see +exports.whoAmI = function () { + +} + +exports.start = function (peerSelf) { + swarm.registerHandle('/ipfs/identify/1.0.0', function (ds) { + ds.setDefaultEncoding('utf8') + ds.write(peerSelf.toB58String()) + ds.end() + }) +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 00000000..7524cd87 --- /dev/null +++ b/src/index.js @@ -0,0 +1,161 @@ +var tcp = require('net') +var Select = require('multistream-select').Select +var Interactive = require('multistream-select').Interactive +var spdy = require('spdy-transport') +var identify = require('./identify') +var log = require('ipfs-logger').group('swarm') +var async = require('async') + +exports = module.exports + +var connections = {} +var handles = [] + +// set the listener + +exports.listen = function () { + + tcp.createServer(function (socket) { + var ms = new Select() + ms.handle(socket) + ms.addHandler('/spdy/3.1.0', function (ds) { + log.info('Negotiated spdy with incoming socket') + log.info('Buffer should be clean - ', ds.read()) + var spdyConnection = spdy.connection.create(ds, { + protocol: 'spdy', + isServer: true + }) + + spdyConnection.start(3.1) + + // attach multistream handlers to incoming streams + spdyConnection.on('stream', function (spdyStream) { + registerHandles(spdyStream) + }) + + // learn about the other peer Identity + /* TODO(daviddias) + identify.inquiry(spdyConnection, function (err, spdyConnection, peerIdB58) { + if (err) { + return log.error(err) + } + if (connections[peerIdB58]) { + return log.error('New connection established with a peer(' + peerIdB58 + ') that we already had a connection') + } + spdyConnection.peerId = peerIdB58 + connections[peerIdB58] = spdyConnection + }) + */ + + // close the connection when all the streams close + spdyConnection.on('close', function () { + delete connections[spdyConnection.peerId] + }) + }) + }).listen(process.env.IPFS_PORT || 4001) +} + +// interface + +exports.openStream = function (peer, protocol, cb) { + // if Connection already open, open a new stream, otherwise, create a new Connection + // then negoatite the protocol and return the opened stream + + // If no connection open yet, open it + if (!connections[peer.id.toB58String()]) { + + // Establish a socket with one of the addresses + var gotOne = false + async.eachSeries(peer.multiaddrs, function (multiaddr, callback) { + if (gotOne) { + return callback() + } + var socket = tcp.connect(multiaddr.toOptions(), function connected () { + gotSocket(socket) + }) + + socket.once('error', function (err) { + log.warn('Could not connect using one of the address of peer - ', peer.id.toB58String(), err) + callback() + }) + + }, function done () { + if (!gotOne) { + cb(new Error('Not able to open a scoket with peer - ', peer.id.toB58String())) + } + }) + + } else { + createStream(peer, protocol, cb) + } + + // do the spdy people dance (multistream-select into spdy) + function gotSocket (socket) { + gotOne = true + var msi = new Interactive() + msi.handle(socket, function () { + msi.select('/spdy/3.1.0', function (err, ds) { + var spdyConnection = spdy.connection.create(ds, { + protocol: 'spdy', + isServer: false + }) + spdyConnection.start(3.1) + connections[peer.id.toB58String()] = spdyConnection + + // attach multistream handlers to incoming streams + spdyConnection.on('stream', function (spdyStream) { + registerHandles(spdyStream) + }) + + createStream(peer, protocol, cb) + }) + }) + } + + function createStream (peer, protocol, cb) { + // 1. to pop a new stream on the connection + // 2. negotiate the requested protocol through multistream + // 3. return back the stream when that is negotiated + var conn = connections[peer.id.toB58String()] + conn.request({path: '/', method: 'GET'}, function (err, stream) { + if (err) { + return cb(err) + } + var msi = new Interactive() + msi.handle(stream, function () { + msi.select(protocol, function (err, ds) { + if (err) { + return cb(err) + } + cb(null, ds) // wohoo we finally delivered the stream the user wanted + }) + }) + }) + + conn.on('close', function () { + // TODO(daviddias) remove it from collections + }) + + } + +} + +exports.registerHandle = function (protocol, cb) { + if (handles[protocol]) { + var err = new Error('Handle for protocol already exists', protocol) + log.error(err) + return cb(err) + } + handles.push({ protocol: protocol, func: cb }) + log.info('Registered handler for protocol:', protocol) +} + +function registerHandles (spdyStream) { + log.info('Preparing stream to handle the registered protocols') + var msH = new Select() + msH.handle(spdyStream) + handles.forEach(function (handle) { + msH.addHandler(handle.protocol, handle.func) + }) +} + diff --git a/tests/swarm-test.js b/tests/swarm-test.js new file mode 100644 index 00000000..e69de29b