From d37b73bf0d2fac0b40367a89e551ae505c456b87 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 18 Apr 2016 23:58:01 +0200 Subject: [PATCH] feat: Add support for ipfs addresses. Closes #15 --- package.json | 5 ++++- src/codec.js | 51 ++++++++++++++++++++++++++++++++------------ src/convert.js | 26 ++++++++++++++++++++++ src/protocols.js | 3 +++ test/convert.spec.js | 19 +++++++++++++++++ test/index.spec.js | 35 +++++++++++++++++++++++++++--- 6 files changed, 121 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 33dd49ca..4d024251 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,12 @@ }, "homepage": "https://github.com/jbenet/js-multiaddr", "dependencies": { + "babel-runtime": "^6.6.1", + "bs58": "^3.0.0", "ip": "^1.0.2", "lodash.filter": "^4.2.1", "lodash.map": "^4.2.1", + "multihashes": "^0.2.1", "varint": "^4.0.0", "xtend": "^4.0.1" }, @@ -52,4 +55,4 @@ "Stephen Whitmore ", "dignifiedquire " ] -} \ No newline at end of file +} diff --git a/src/codec.js b/src/codec.js index 14d07311..52b7f6f6 100644 --- a/src/codec.js +++ b/src/codec.js @@ -41,9 +41,10 @@ function stringToStringTuples (str) { for (var p = 0; p < parts.length; p++) { var part = parts[p] var proto = protocols(part) + if (proto.size === 0) { tuples.push([part]) - return tuples + continue } p++ // advance addr part @@ -53,6 +54,7 @@ function stringToStringTuples (str) { tuples.push([part, parts[p]]) } + return tuples } @@ -66,9 +68,6 @@ function stringTuplesToString (tuples) { parts.push(tup[1]) } }) - if (parts[parts.length - 1] === '') { - parts.pop() - } return '/' + parts.join('/') } @@ -103,32 +102,56 @@ function tuplesToBuffer (tuples) { return fromBuffer(Buffer.concat(map(tuples, function (tup) { var proto = protoFromTuple(tup) var buf = new Buffer(varint.encode(proto.code)) + if (tup.length > 1) { buf = Buffer.concat([buf, tup[1]]) // add address buffer } + return buf }))) } +function sizeForAddr (p, addr) { + if (p.size > 0) { + return p.size / 8 + } else if (p.size === 0) { + return 0 + } else { + const size = varint.decode(addr) + return size + varint.decode.bytes + } +} + // Buffer -> [[int code, Buffer ]... ] function bufferToTuples (buf) { - var tuples = [] - for (var i = 0; i < buf.length;) { - var code = varint.decode(buf, i) - - var proto = protocols(code) - var size = (proto.size / 8) - code = Number(code) - var addr = buf.slice(i + 1, i + 1 + size) - i += 1 + size + const tuples = [] + let i = 0 + while (i < buf.length) { + const code = varint.decode(buf, i) + const n = varint.decode.bytes + + const p = protocols(code) + + const size = sizeForAddr(p, buf.slice(i + n)) + + if (size === 0) { + tuples.push([code]) + i += n + continue + } + + const addr = buf.slice(i + n, i + n + size) + + i += (size + n) + if (i > buf.length) { // did not end _exactly_ at buffer.length throw ParseError('Invalid address buffer: ' + buf.toString('hex')) } // ok, tuple seems good. tuples.push([code, addr]) - i = i + varint.decode.bytes - 1 } + return tuples } diff --git a/src/convert.js b/src/convert.js index 76c44756..10c1091e 100644 --- a/src/convert.js +++ b/src/convert.js @@ -2,6 +2,8 @@ var ip = require('ip') var protocols = require('./protocols') +var bs58 = require('bs58') +var varint = require('varint') module.exports = Convert @@ -26,6 +28,9 @@ Convert.toString = function convertToString (proto, buf) { case 33: // dccp case 132: // sctp return buf2port(buf) + + case 421: // ipfs + return buf2mh(buf) default: return buf.toString('hex') // no clue. convert to hex } @@ -43,6 +48,9 @@ Convert.toBuffer = function convertToBuffer (proto, str) { case 33: // dccp case 132: // sctp return port2buf(parseInt(str, 10)) + + case 421: // ipfs + return mh2buf(str) default: return new Buffer(str, 'hex') // no clue. convert from hex } @@ -57,3 +65,21 @@ function port2buf (port) { function buf2port (buf) { return buf.readUInt16BE(0) } + +function mh2buf (hash) { + // the address is a varint prefixed multihash string representation + const mh = new Buffer(bs58.decode(hash)) + const size = new Buffer(varint.encode(mh.length)) + return Buffer.concat([size, mh]) +} + +function buf2mh (buf) { + const size = varint.decode(buf) + const address = buf.slice(varint.decode.bytes) + + if (address.length !== size) { + throw new Error('inconsistent lengths') + } + + return bs58.encode(address) +} diff --git a/src/protocols.js b/src/protocols.js index 1b8a8bd6..9e6d1030 100644 --- a/src/protocols.js +++ b/src/protocols.js @@ -22,6 +22,8 @@ function Protocols (proto) { throw new Error('invalid protocol id type: ' + proto) } +Protocols.lengthPrefixedVarSize = -1 + // replicating table here to: // 1. avoid parsing the csv // 2. ensuring errors in the csv don't screw up code. @@ -36,6 +38,7 @@ Protocols.table = [ [132, 16, 'sctp'], // these require varint for the protocol code [302, 0, 'utp'], + [421, Protocols.lengthPrefixedVarSize, 'ipfs'], [480, 0, 'http'], [443, 0, 'https'], [477, 0, 'websockets'] diff --git a/test/convert.spec.js b/test/convert.spec.js index 54cc94ed..f6b47edc 100644 --- a/test/convert.spec.js +++ b/test/convert.spec.js @@ -30,4 +30,23 @@ describe('convert', () => { ) }) }) + + describe('.toString', () => { + it('throws on inconsistent ipfs links', () => { + const valid = new Buffer('03221220d52ebb89d85b02a284948203a62ff28389c57c9f42beec4ec20db76a68911c0b', 'hex') + expect( + () => convert.toString('ipfs', valid.slice(0, valid.length - 8)) + ).to.throw( + /inconsistent length/ + ) + }) + + it('defaults to hex conversion', () => { + expect( + convert.toString('websockets', new Buffer([192, 168, 0, 1])) + ).to.be.eql( + 'c0a80001' + ) + }) + }) }) diff --git a/test/index.spec.js b/test/index.spec.js index d104dca7..0eb88710 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -145,6 +145,20 @@ describe('variants', () => { expect(addr.toString()).to.equal(str) }) + it('ip4 + ipfs', () => { + const str = '/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234' + const addr = multiaddr(str) + expect(addr).to.have.property('buffer') + expect(addr.toString()).to.equal(str) + }) + + it('ip6 + ipfs', () => { + const str = '/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234' + const addr = multiaddr(str) + expect(addr).to.have.property('buffer') + expect(addr.toString()).to.equal(str) + }) + it.skip('ip4 + dccp', () => {}) it.skip('ip6 + dccp', () => {}) @@ -207,6 +221,21 @@ describe('variants', () => { expect(addr).to.have.property('buffer') expect(addr.toString()).to.equal(str) }) + + it('ip6 + tcp + websockets + ipfs', () => { + const str = '/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/8000/websockets/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC' + const addr = multiaddr(str) + expect(addr).to.have.property('buffer') + expect(addr.toString()).to.equal(str) + }) + + it('ipfs', () => { + const str = '/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC' + const addr = multiaddr(str) + expect(addr).to.have.property('buffer') + console.log(addr.buffer.toString('hex')) + expect(addr.toString()).to.equal(str) + }) }) describe('helpers', () => { @@ -254,7 +283,7 @@ describe('helpers', () => { multiaddr('/ip4/0.0.0.0/utp').tuples() ).to.be.eql([ [4, new Buffer([0, 0, 0, 0])], - [302, new Buffer([])] + [302] ]) }) }) @@ -265,7 +294,7 @@ describe('helpers', () => { multiaddr('/ip4/0.0.0.0/utp').stringTuples() ).to.be.eql([ [4, '0.0.0.0'], - [302, ''] + [302] ]) }) }) @@ -389,7 +418,7 @@ describe('helpers', () => { ) expect( - multiaddr('/http/0.0.0.0/utp').isThinWaistAddress() + multiaddr('/http/utp').isThinWaistAddress() ).to.be.eql( false )