UNPKG

multiformats

Version:

Interface for multihash, multicodec, multibase and CID

550 lines (545 loc) 22.6 kB
'use strict'; var OLDCID = require('cids'); var assert = require('assert'); var bytes = require('../src/bytes.js'); require('../src/index.js'); var base58 = require('../src/bases/base58.js'); var base32 = require('../src/bases/base32.js'); var base64Browser = require('../src/bases/base64-browser.js'); var sha2Browser = require('../src/hashes/sha2-browser.js'); var util = require('util'); var buffer = require('buffer'); var invalidMultihash = require('./fixtures/invalid-multihash.js'); var cid = require('../src/cid.js'); var varint = require('../src/varint.js'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var OLDCID__default = /*#__PURE__*/_interopDefaultLegacy(OLDCID); var assert__default = /*#__PURE__*/_interopDefaultLegacy(assert); var util__default = /*#__PURE__*/_interopDefaultLegacy(util); const test = it; const same = (actual, expected) => { if (actual instanceof Uint8Array && expected instanceof Uint8Array) { if (buffer.Buffer.compare(buffer.Buffer.from(actual), buffer.Buffer.from(expected)) === 0) return; } return assert__default['default'].deepStrictEqual(actual, expected); }; const testThrow = async (fn, message) => { try { await fn(); } catch (e) { if (e.message !== message) throw e; return; } throw new Error('Test failed to throw'); }; const testThrowAny = async fn => { try { await fn(); } catch (e) { return; } throw new Error('Test failed to throw'); }; describe('CID', () => { describe('v0', () => { test('handles B58Str multihash', () => { const mhStr = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'; const cid$1 = cid.parse(mhStr); same(cid$1.version, 0); same(cid$1.code, 112); same(cid$1.multihash.bytes, base58.base58btc.baseDecode(mhStr)); same(cid$1.toString(), mhStr); }); test('create by parts', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const cid$1 = cid.create(0, 112, hash); same(cid$1.code, 112); same(cid$1.version, 0); same(cid$1.multihash, hash); same(cid$1.toString(), base58.base58btc.baseEncode(hash.bytes)); }); test('create from multihash', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const cid$1 = cid.decode(hash.bytes); same(cid$1.code, 112); same(cid$1.version, 0); same(cid$1.multihash.digest, hash.digest); same({ ...cid$1.multihash, digest: null }, { ...hash, digest: null }); cid$1.toString(); same(cid$1.toString(), base58.base58btc.baseEncode(hash.bytes)); }); test('throws on invalid BS58Str multihash ', async () => { const msg = 'Non-base58 character'; await testThrow(() => cid.parse('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zIII'), msg); }); test('throws on trying to create a CIDv0 with a codec other than dag-pb', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const msg = 'Version 0 CID must use dag-pb (code: 112) block encoding'; await testThrow(() => cid.create(0, 113, hash), msg); }); test('throws on trying to base encode CIDv0 in other base than base58btc', async () => { const mhStr = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'; const cid$1 = cid.parse(mhStr); const msg = 'Cannot string encode V0 in base32 encoding'; await testThrow(() => cid$1.toString(base32.base32), msg); }); test('.bytes', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const codec = 112; const cid$1 = cid.create(0, codec, hash); const bytes$1 = cid$1.bytes; assert__default['default'].ok(bytes$1); const str = bytes.toHex(bytes$1); same(str, '1220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'); }); test('should construct from an old CID', () => { const cidStr = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'; const oldCid = cid.parse(cidStr); const newCid = cid.asCID(oldCid); same(newCid.toString(), cidStr); }); test('inspect bytes', () => { const byts = bytes.fromHex('1220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'); const inspected = cid.inspectBytes(byts.subarray(0, 10)); same({ version: 0, codec: 112, multihashCode: 18, multihashSize: 34, digestSize: 32, size: 34 }, inspected); }); describe('decodeFirst', () => { test('no remainder', () => { const byts = bytes.fromHex('1220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'); const [cid$1, remainder] = cid.decodeFirst(byts); same(cid$1.toString(), 'QmatYkNGZnELf8cAGdyJpUca2PyY4szai3RHyyWofNY1pY'); same(remainder.byteLength, 0); }); test('remainder', () => { const byts = bytes.fromHex('1220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad0102030405'); const [cid$1, remainder] = cid.decodeFirst(byts); same(cid$1.toString(), 'QmatYkNGZnELf8cAGdyJpUca2PyY4szai3RHyyWofNY1pY'); same(bytes.toHex(remainder), '0102030405'); }); }); }); describe('v1', () => { test('handles CID String (multibase encoded)', () => { const cidStr = 'zdj7Wd8AMwqnhJGQCbFxBVodGSBG84TM7Hs1rcJuQMwTyfEDS'; const cid$1 = cid.parse(cidStr); same(cid$1.code, 112); same(cid$1.version, 1); assert__default['default'].ok(cid$1.multihash); same(cid$1.toString(), base32.base32.encode(cid$1.bytes)); }); test('handles CID (no multibase)', () => { const cidStr = 'bafybeidskjjd4zmr7oh6ku6wp72vvbxyibcli2r6if3ocdcy7jjjusvl2u'; const cidBuf = buffer.Buffer.from('017012207252523e6591fb8fe553d67ff55a86f84044b46a3e4176e10c58fa529a4aabd5', 'hex'); const cid$1 = cid.decode(cidBuf); same(cid$1.code, 112); same(cid$1.version, 1); same(cid$1.toString(), cidStr); }); test('create by parts', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const cid$1 = cid.create(1, 113, hash); same(cid$1.code, 113); same(cid$1.version, 1); assert__default['default'].ok(bytes.equals(cid$1.multihash, hash)); }); test('can roundtrip through cid.toString()', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const cid1 = cid.create(1, 113, hash); const cid2 = cid.parse(cid1.toString()); same(cid1.code, cid2.code); same(cid1.version, cid2.version); same(cid1.multihash.digest, cid2.multihash.digest); same(cid1.multihash.bytes, cid2.multihash.bytes); const clear = { digest: null, bytes: null }; same({ ...cid1.multihash, ...clear }, { ...cid2.multihash, ...clear }); }); test('.bytes', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const code = 113; const cid$1 = cid.create(1, code, hash); const bytes$1 = cid$1.bytes; assert__default['default'].ok(bytes$1); const str = bytes.toHex(bytes$1); same(str, '01711220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'); }); test('should construct from an old CID without a multibaseName', () => { const cidStr = 'bafybeidskjjd4zmr7oh6ku6wp72vvbxyibcli2r6if3ocdcy7jjjusvl2u'; const oldCid = cid.parse(cidStr); const newCid = cid.asCID(oldCid); same(newCid.toString(), cidStr); }); }); describe('utilities', () => { const h1 = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'; const h2 = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1o'; const h3 = 'zdj7Wd8AMwqnhJGQCbFxBVodGSBG84TM7Hs1rcJuQMwTyfEDS'; test('.equals v0 to v0', () => { const cid1 = cid.parse(h1); same(cid1.equals(cid.parse(h1)), true); same(cid1.equals(cid.create(cid1.version, cid1.code, cid1.multihash)), true); const cid2 = cid.parse(h2); same(cid1.equals(cid.parse(h2)), false); same(cid1.equals(cid.create(cid2.version, cid2.code, cid2.multihash)), false); }); test('.equals v0 to v1 and vice versa', () => { const cidV1 = cid.parse(h3); const cidV0 = cidV1.toV0(); same(cidV0.equals(cidV1), false); same(cidV1.equals(cidV0), false); same(cidV1.multihash, cidV0.multihash); }); test('.equals v1 to v1', () => { const cid1 = cid.parse(h3); same(cid1.equals(cid.parse(h3)), true); same(cid1.equals(cid.create(cid1.version, cid1.code, cid1.multihash)), true); }); test('.isCid', () => { assert__default['default'].ok(cid.isCID(cid.parse(h1))); assert__default['default'].ok(!cid.isCID(false)); assert__default['default'].ok(!cid.isCID(buffer.Buffer.from('hello world'))); assert__default['default'].ok(cid.isCID(cid.parse(h1).toV0())); assert__default['default'].ok(cid.isCID(cid.parse(h1).toV1())); }); test('works with deepEquals', () => { const ch1 = cid.parse(h1); ch1._baseCache.set('herp', 'derp'); assert__default['default'].deepStrictEqual(ch1, cid.parse(h1)); assert__default['default'].notDeepStrictEqual(ch1, cid.parse(h2)); }); }); describe('throws on invalid inputs', () => { const parse = [ 'hello world', 'QmaozNR7DZHQK1ZcU9p7QdrshMvXqWK6gpu5rmrkPdT3L' ]; for (const i of parse) { const name = `CID.parse(${ JSON.stringify(i) })`; test(name, async () => await testThrowAny(() => cid.parse(i))); } const decode = [ buffer.Buffer.from('hello world'), buffer.Buffer.from('QmaozNR7DZHQK1ZcU9p7QdrshMvXqWK6gpu5rmrkPdT') ]; for (const i of decode) { const name = `CID.decode(Buffer.from(${ JSON.stringify(i.toString()) }))`; test(name, async () => await testThrowAny(() => cid.decode(i))); } const create = [ ...[ ...parse, ...decode ].map(i => [ 0, 112, i ]), ...[ ...parse, ...decode ].map(i => [ 1, 112, i ]), [ 18, 112, 'QmaozNR7DZHQK1ZcU9p7QdrshMvXqWK6gpu5rmrkPdT3L' ] ]; for (const [version, code, hash] of create) { const form = JSON.stringify(hash.toString()); const mh = buffer.Buffer.isBuffer(hash) ? `Buffer.from(${ form })` : form; const name = `CID.create(${ version }, ${ code }, ${ mh })`; test(name, async () => await testThrowAny(() => cid.create(version, code, hash))); } test('invalid fixtures', async () => { for (const test of invalidMultihash) { const buff = bytes.fromHex(`0171${ test.hex }`); assert__default['default'].throws(() => cid.decode(buff), new RegExp(test.message)); } }); }); describe('idempotence', () => { const h1 = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'; const cid1 = cid.parse(h1); const cid2 = cid.asCID(cid1); test('constructor accept constructed instance', () => { same(cid1 === cid2, true); }); }); describe('conversion v0 <-> v1', () => { test('should convert v0 to v1', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from(`TEST${ Date.now() }`)); const cid$1 = cid.create(0, 112, hash).toV1(); same(cid$1.version, 1); }); test('should convert v1 to v0', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from(`TEST${ Date.now() }`)); const cid$1 = cid.create(1, 112, hash).toV0(); same(cid$1.version, 0); }); test('should not convert v1 to v0 if not dag-pb codec', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from(`TEST${ Date.now() }`)); const cid$1 = cid.create(1, 113, hash); await testThrow(() => cid$1.toV0(), 'Cannot convert a non dag-pb CID to CIDv0'); }); test('should not convert v1 to v0 if not sha2-256 multihash', async () => { const hash = await sha2Browser.sha512.digest(buffer.Buffer.from(`TEST${ Date.now() }`)); const cid$1 = cid.create(1, 112, hash); await testThrow(() => cid$1.toV0(), 'Cannot convert non sha2-256 multihash CID to CIDv0'); }); test('should return same instance when converting v1 to v1', async () => { const hash = await sha2Browser.sha512.digest(buffer.Buffer.from(`TEST${ Date.now() }`)); const cid$1 = cid.create(1, 112, hash); same(cid$1.toV1() === cid$1, true); }); test('should return same instance when converting v0 to v0', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from(`TEST${ Date.now() }`)); const cid$1 = cid.create(0, 112, hash); same(cid$1.toV0() === cid$1, true); }); }); describe('caching', () => { test('should cache CID as buffer', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from(`TEST${ Date.now() }`)); const cid$1 = cid.create(1, 112, hash); assert__default['default'].ok(cid$1.bytes); same(cid$1.bytes, cid$1.bytes); }); test('should cache string representation when it matches the multibaseName it was constructed with', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const cid$1 = cid.create(1, 112, hash); same(cid$1._baseCache.size, 0); same(cid$1.toString(base64Browser.base64), 'mAXASILp4Fr+PAc/qQUFA3l2uIiOwA2Gjlhd6nLQQ/2HyABWt'); same(cid$1._baseCache.get(base64Browser.base64.prefix), 'mAXASILp4Fr+PAc/qQUFA3l2uIiOwA2Gjlhd6nLQQ/2HyABWt'); same(cid$1._baseCache.has(base32.base32.prefix), false); const base32String = 'bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu'; same(cid$1.toString(), base32String); same(cid$1._baseCache.get(base32.base32.prefix), base32String); same(cid$1.toString(base64Browser.base64), 'mAXASILp4Fr+PAc/qQUFA3l2uIiOwA2Gjlhd6nLQQ/2HyABWt'); }); test('should cache string representation when constructed with one', () => { const base32String = 'bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu'; const cid$1 = cid.parse(base32String); same(cid$1._baseCache.get(base32.base32.prefix), base32String); }); }); test('toJSON()', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const cid$1 = cid.create(1, 112, hash); const json = cid$1.toJSON(); same({ ...json, hash: null }, { code: 112, version: 1, hash: null }); assert__default['default'].ok(bytes.equals(json.hash, hash.bytes)); }); test('isCID', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const cid$1 = cid.create(1, 112, hash); assert__default['default'].strictEqual(OLDCID__default['default'].isCID(cid$1), false); }); test('asCID', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); class IncompatibleCID { constructor(version, code, multihash) { this.version = version; this.code = code; this.multihash = multihash; this.asCID = this; } get [Symbol.for('@ipld/js-cid/CID')]() { return true; } } const version = 1; const code = 112; const incompatibleCID = new IncompatibleCID(version, code, hash); assert__default['default'].ok(cid.isCID(incompatibleCID)); assert__default['default'].strictEqual(incompatibleCID.toString(), '[object Object]'); assert__default['default'].strictEqual(typeof incompatibleCID.toV0, 'undefined'); const cid1 = cid.asCID(incompatibleCID); assert__default['default'].ok(cid1 instanceof cid); assert__default['default'].strictEqual(cid1.code, code); assert__default['default'].strictEqual(cid1.version, version); assert__default['default'].ok(bytes.equals(cid1.multihash, hash)); const cid2 = cid.asCID({ version, code, hash }); assert__default['default'].strictEqual(cid2, null); const duckCID = { version, code, multihash: hash }; duckCID.asCID = duckCID; const cid3 = cid.asCID(duckCID); assert__default['default'].ok(cid3 instanceof cid); assert__default['default'].strictEqual(cid3.code, code); assert__default['default'].strictEqual(cid3.version, version); assert__default['default'].ok(bytes.equals(cid3.multihash, hash)); const cid4 = cid.asCID(cid3); assert__default['default'].strictEqual(cid3, cid4); const cid5 = cid.asCID(new OLDCID__default['default'](1, 'raw', buffer.Buffer.from(hash.bytes))); assert__default['default'].ok(cid5 instanceof cid); assert__default['default'].strictEqual(cid5.version, 1); assert__default['default'].ok(bytes.equals(cid5.multihash, hash)); assert__default['default'].strictEqual(cid5.code, 85); }); const digestsame = (x, y) => { same(x.digest, y.digest); same(x.hash, y.hash); same(x.bytes, y.bytes); if (x.multihash) { digestsame(x.multihash, y.multihash); } const empty = { hash: null, bytes: null, digest: null, multihash: null }; same({ ...x, ...empty }, { ...y, ...empty }); }; describe('CID.parse', async () => { test('parse 32 encoded CIDv1', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const cid$1 = cid.create(1, 112, hash); const parsed = cid.parse(cid$1.toString()); digestsame(cid$1, parsed); }); test('parse base58btc encoded CIDv1', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const cid$1 = cid.create(1, 112, hash); const parsed = cid.parse(cid$1.toString(base58.base58btc)); digestsame(cid$1, parsed); }); test('parse base58btc encoded CIDv0', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const cid$1 = cid.create(0, 112, hash); const parsed = cid.parse(cid$1.toString()); digestsame(cid$1, parsed); }); test('fails to parse base64 encoded CIDv1', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const cid$1 = cid.create(1, 112, hash); const msg = 'To parse non base32 or base56btc encoded CID multibase decoder must be provided'; await testThrow(() => cid.parse(cid$1.toString(base64Browser.base64)), msg); }); test('parses base64 encoded CIDv1 if base64 is provided', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const cid$1 = cid.create(1, 112, hash); const parsed = cid.parse(cid$1.toString(base64Browser.base64), base64Browser.base64); digestsame(cid$1, parsed); }); }); test('inspect bytes', () => { const byts = bytes.fromHex('01711220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'); const inspected = cid.inspectBytes(byts.subarray(0, 10)); same({ version: 1, codec: 113, multihashCode: 18, multihashSize: 34, digestSize: 32, size: 36 }, inspected); describe('decodeFirst', () => { test('no remainder', () => { const byts = bytes.fromHex('01711220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'); const [cid$1, remainder] = cid.decodeFirst(byts); same(cid$1.toString(), 'bafyreif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu'); same(remainder.byteLength, 0); }); test('remainder', () => { const byts = bytes.fromHex('01711220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad0102030405'); const [cid$1, remainder] = cid.decodeFirst(byts); same(cid$1.toString(), 'bafyreif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu'); same(bytes.toHex(remainder), '0102030405'); }); }); }); test('new CID from old CID', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const cid$1 = cid.asCID(new OLDCID__default['default'](1, 'raw', buffer.Buffer.from(hash.bytes))); same(cid$1.version, 1); assert__default['default'].ok(bytes.equals(cid$1.multihash, hash)); same(cid$1.code, 85); }); if (!process.browser) { test('util.inspect', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const cid$1 = cid.create(1, 112, hash); same(util__default['default'].inspect(cid$1), 'CID(bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu)'); }); } describe('deprecations', async () => { test('codec', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const cid$1 = cid.create(1, 112, hash); await testThrow(() => cid$1.codec, '"codec" property is deprecated, use integer "code" property instead'); await testThrow(() => cid.create(1, 'dag-pb', hash), 'String codecs are no longer supported'); }); test('multibaseName', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const cid$1 = cid.create(1, 112, hash); await testThrow(() => cid$1.multibaseName, '"multibaseName" property is deprecated'); }); test('prefix', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const cid$1 = cid.create(1, 112, hash); await testThrow(() => cid$1.prefix, '"prefix" property is deprecated'); }); test('toBaseEncodedString()', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const cid$1 = cid.create(1, 112, hash); await testThrow(() => cid$1.toBaseEncodedString(), 'Deprecated, use .toString()'); }); }); test('invalid CID version', async () => { const encoded = varint.encodeTo(2, new Uint8Array(32)); await testThrow(() => cid.decode(encoded), 'Invalid CID version 2'); }); test('buffer', async () => { const hash = await sha2Browser.sha256.digest(buffer.Buffer.from('abc')); const cid$1 = cid.create(1, 112, hash); await testThrow(() => cid$1.buffer, 'Deprecated .buffer property, use .bytes to get Uint8Array instead'); }); });