From 211970b11ea37d5253f538720ca665e32c9f54fb Mon Sep 17 00:00:00 2001 From: David Dias Date: Sun, 2 Oct 2016 10:43:34 +0100 Subject: [PATCH] feat: cidV0 and cidV1 support update multibase and multicodec add tests add documentation --- README.md | 16 +++++- package.json | 3 +- src/cid.js | 75 -------------------------- src/codecs.js | 13 +++-- src/index.js | 117 +++++++++++++++++++++++++++++++++++++++- test/helpers/gen-cid.js | 21 ++++++++ test/index.spec.js | 101 ++++++++++++++++++++++++++-------- 7 files changed, 240 insertions(+), 106 deletions(-) delete mode 100644 src/cid.js create mode 100644 test/helpers/gen-cid.js diff --git a/README.md b/README.md index 9a6b0c3..f0eeaf3 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,13 @@ const cid = new CID(base58Multihash) ## API -### `new CID(codec[, version, hash])` +### Constructor + +- `new CID(, , )` +- `new CID()` +- `new CID()` +- `new CID()` +- `new CID()` ### `.codec` @@ -85,7 +91,13 @@ const cid = new CID(base58Multihash) ### `.buffer` -### `.toString()` +### `.toV0()` + +### `.toV1()` + +### `.toBaseEncodedString(base)` + +Defaults to 'base58btc' ### `.toJSON()` diff --git a/package.json b/package.json index bfb5ebb..6a5b4eb 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ }, "dependencies": { "multibase": "^0.2.0", + "multicodec": "0.1.0", "multihashes": "^0.2.2" }, "devDependencies": { @@ -50,4 +51,4 @@ "contributors": [ "Friedel Ziegelmayer " ] -} \ No newline at end of file +} diff --git a/src/cid.js b/src/cid.js deleted file mode 100644 index 126abff..0000000 --- a/src/cid.js +++ /dev/null @@ -1,75 +0,0 @@ -'use strict' - -const mh = require('multihashes') -const multibase = require('multibase') - -const codecs = require('./codecs') - -class CID { - constructor (codec, version, hash) { - if (typeof codec === 'string') { - codec = mh.fromB58String(codec) - } - - if (Buffer.isBuffer(codec)) { - this.codec = codecs.protobuf - this.version = 0 - this.hash = codec - } else if (typeof codec === 'object') { - this.codec = codec.codec - this.version = codec.version - this.hash = codec.hash - } else { - this.codec = codec - this.version = version - this.hash = hash - } - } - - get buffer () { - switch (this.version) { - case 0: - return this.hash - case 1: - return Buffer.concat([ - Buffer(this.version), - Buffer(this.codec), - this.hash - ]) - default: - throw new Error('unsupported version') - } - } - - toString () { - switch (this.version) { - case 0: - return mh.toB58String(this.hash) - case 1: - return multibase.encode('base58btc', this.buffer).toString() - default: - throw new Error('Unsupported version') - } - } - - toJSON () { - return { - codec: this.codec, - version: this.version, - hash: this.hash - } - } - - equals (other) { - return this.codec === other.codec && - this.version === other.version && - this.hash.equals(other.hash) - } -} - -CID.codecs = codecs -CID.isCID = (other) => { - return other.constructor.name === 'CID' -} - -module.exports = CID diff --git a/src/codecs.js b/src/codecs.js index 3ff472b..86a61ca 100644 --- a/src/codecs.js +++ b/src/codecs.js @@ -1,8 +1,13 @@ 'use strict' +/* + * Consult table at: https://github.com/multiformats/multicodec + */ + module.exports = { - protobuf: 0, - raw: 1, - json: 2, - cbor: 3 + raw: new Buffer('00', 'hex'), + 'dag-pb': new Buffer('70', 'hex'), + 'dag-cbor': new Buffer('71', 'hex'), + 'eth-block': new Buffer('90', 'hex'), + 'eth-tx': new Buffer('91', 'hex') } diff --git a/src/index.js b/src/index.js index 008db47..cb92aa1 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,120 @@ 'use strict' -const CID = require('./cid') +const mh = require('multihashes') +const multibase = require('multibase') +const multicodec = require('multicodec') + +const codecs = require('./codecs') + +// CID: + +class CID { + /* + * if (str) + * if (1st char is on multibase table) -> CID String + * else -> bs58 encoded multihash + * else if (Buffer) + * if (0 or 1) -> CID + * else -> multihash + * else if (Number) + * -> construct CID by parts + * + * ..if only JS had traits.. + */ + constructor (version, codec, multihash) { + if (typeof version === 'string') { + if (multibase.isEncoded(version)) { // CID String (encoded with multibase) + const cid = multibase.decode(version) + this.version = parseInt(cid.slice(0, 1).toString('hex'), 16) + this.codec = multicodec.getCodec(cid.slice(1)) + this.multihash = multicodec.rmPrefix(cid.slice(1)) + } else { // bs58 string encoded multihash + this.codec = 'dag-pb' + this.multihash = mh.fromB58String(version) + this.version = 0 + } + } else if (Buffer.isBuffer(version)) { + const firstByte = version.slice(0, 1) + const v = parseInt(firstByte.toString('hex'), 16) + if (v === 0 || v === 1) { // CID + const cid = version + this.version = v + this.codec = multicodec.getCodec(cid.slice(1)) + this.multihash = multicodec.rmPrefix(cid.slice(1)) + } else { // multihash + this.codec = 'dag-pb' + this.multihash = version + this.version = 0 + } + } else if (typeof version === 'number') { + if (typeof codec !== 'string') { + throw new Error('codec must be string') + } + if (!(version === 0 || version === 1)) { + throw new Error('version must be a number equal to 0 or 1') + } + mh.validate(multihash) + this.codec = codec + this.version = version + this.multihash = multihash + } + } + + get buffer () { + switch (this.version) { + case 0: + return this.multihash + case 1: + return Buffer.concat([ + Buffer('01', 'hex'), + Buffer(codecs[this.codec]), + this.multihash + ]) + default: + throw new Error('unsupported version') + } + } + + toV0 () { + return this.multihash + } + + toV1 () { + return this.buffer + } + + /* defaults to base58btc */ + toBaseEncodedString (base) { + base = base || 'base58btc' + + switch (this.version) { + case 0: + return mh.toB58String(this.multihash) + case 1: + return multibase.encode(base, this.buffer).toString() + default: + throw new Error('Unsupported version') + } + } + + toJSON () { + return { + codec: this.codec, + version: this.version, + hash: this.multihash + } + } + + equals (other) { + return this.codec === other.codec && + this.version === other.version && + this.multihash.equals(other.multihash) + } +} + +CID.codecs = codecs +CID.isCID = (other) => { + return other.constructor.name === 'CID' +} module.exports = CID diff --git a/test/helpers/gen-cid.js b/test/helpers/gen-cid.js new file mode 100644 index 0000000..9a3d9c7 --- /dev/null +++ b/test/helpers/gen-cid.js @@ -0,0 +1,21 @@ +'use strict' + +// CID String: + +const multibase = require('multibase') +const codecs = require('../../src').codecs +const multihashing = require('multihashing') + +const mh = multihashing(Buffer('oh, hey!'), 'sha2-256') +const cid = Buffer.concat([ + new Buffer('01', 'hex'), + codecs.dagPB, + mh +]) + +const cidStr = multibase.encode('base58btc', cid).toString() + +console.log('CID String (multibase included)') +console.log(cidStr) +console.log('CID in hex (multibase not included)') +console.log(cid.toString('hex')) diff --git a/test/index.spec.js b/test/index.spec.js index 7c54825..f3b238a 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -2,53 +2,92 @@ 'use strict' const expect = require('chai').expect -const mh = require('multihashes') +const multihash = require('multihashes') const multihashing = require('multihashing') const CID = require('../src') describe('CID', () => { - describe('constructor', () => { - it('simple', () => { - const hash = multihashing(Buffer('hello world'), 'sha2-512') - const cid = new CID(7, 1, hash) + describe('v0', () => { + it('handles B58Str multihash', () => { + const mhStr = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n' + const cid = new CID(mhStr) - expect(cid).to.have.property('codec', 7) - expect(cid).to.have.property('version', 1) - expect(cid).to.have.property('hash').that.eql(hash) + expect(cid).to.have.property('codec', 'dag-pb') + expect(cid).to.have.property('version', 0) + expect(cid).to.have.property('multihash').that.eql(multihash.fromB58String(mhStr)) - expect(cid.toJSON()).to.be.eql(new CID({ - codec: 7, - version: 1, - hash: hash - })) + expect(cid.toBaseEncodedString()).to.be.eql(mhStr) }) - }) - describe('v0', () => { - it('handles base58 multihashes', () => { - const hash = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n' - const cid = new CID(hash) + it('handles Buffer multihash', () => { + const mh = multihashing(Buffer('hello world'), 'sha2-256') + const mhStr = 'QmaozNR7DZHQK1ZcU9p7QdrshMvXqWK6gpu5rmrkPdT3L4' - expect(cid).to.have.property('codec', CID.codecs.protobuf) + const cid = new CID(mh) + + expect(cid).to.have.property('codec', 'dag-pb') expect(cid).to.have.property('version', 0) - expect(cid).to.have.property('hash').that.eql(mh.fromB58String(hash)) + expect(cid).to.have.property('multihash').that.eql(mh) + + expect(cid.toBaseEncodedString()).to.be.eql(mhStr) + }) + + it('create by parts', () => { + const cid = new CID(0, 'dag-pb', multihash.encode(new Buffer('abc'), 'sha2-256')) - expect(cid.toString()).to.be.eql(hash) + expect(cid).to.have.property('codec', 'dag-pb') + expect(cid).to.have.property('version', 0) + expect(cid).to.have.property('multihash') }) - it('throws on invalid hashes', () => { + it('throws on invalid BS58Str multihash ', () => { expect( () => new CID('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zIII') ).to.throw() }) }) + describe('v1', () => { + it('handles CID String (multibase encoded)', () => { + const cidStr = 'zdj7Wd8AMwqnhJGQCbFxBVodGSBG84TM7Hs1rcJuQMwTyfEDS' + + const cid = new CID(cidStr) + + expect(cid).to.have.property('codec', 'dag-pb') + expect(cid).to.have.property('version', 1) + expect(cid).to.have.property('multihash') + + expect(cid.toBaseEncodedString()).to.be.eql(cidStr) + }) + + it('handles CID (no multibase)', () => { + const cidStr = 'zdj7Wd8AMwqnhJGQCbFxBVodGSBG84TM7Hs1rcJuQMwTyfEDS' + const cidBuf = new Buffer('017012207252523e6591fb8fe553d67ff55a86f84044b46a3e4176e10c58fa529a4aabd5', 'hex') + + const cid = new CID(cidBuf) + + expect(cid).to.have.property('codec', 'dag-pb') + expect(cid).to.have.property('version', 1) + expect(cid).to.have.property('multihash') + + expect(cid.toBaseEncodedString()).to.be.eql(cidStr) + }) + + it('create by parts', () => { + const cid = new CID(1, 'dag-cbor', multihash.encode(new Buffer('xyz'), 'sha2-256')) + + expect(cid).to.have.property('codec', 'dag-cbor') + expect(cid).to.have.property('version', 1) + expect(cid).to.have.property('multihash') + }) + }) + describe('utilities', () => { const h1 = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n' const h2 = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1o' - it('.equals', () => { + it('.equals v0 to v0', () => { expect( new CID(h1).equals(new CID(h1)) ).to.be.eql(true) @@ -58,6 +97,22 @@ describe('CID', () => { ).to.be.eql(false) }) + it('.equals v0 to v1 and vice versa', () => { + const cidV1Str = 'zdj7Wd8AMwqnhJGQCbFxBVodGSBG84TM7Hs1rcJuQMwTyfEDS' + const cidV1 = new CID(cidV1Str) + const cidV0 = new CID(cidV1.toV0()) + + expect( + cidV0.equals(cidV1) + ).to.be.eql(false) + + expect( + cidV1.equals(cidV0) + ).to.be.eql(false) + + expect(cidV1.multihash).to.eql(cidV0.multihash) + }) + it('.isCid', () => { expect( CID.isCID(new CID(h1))