741 lines
24 KiB
JavaScript
741 lines
24 KiB
JavaScript
/* globals describe, it */
|
|
|
|
import { fromHex, toHex, equals } from '../src/bytes.js'
|
|
import { varint, CID } from '../src/index.js'
|
|
import { base58btc } from '../src/bases/base58.js'
|
|
import { base32 } from '../src/bases/base32.js'
|
|
import { base64 } from '../src/bases/base64.js'
|
|
import { sha256, sha512 } from '../src/hashes/sha2.js'
|
|
import invalidMultihash from './fixtures/invalid-multihash.js'
|
|
import OLDCID from 'cids'
|
|
import { assert } from 'aegir/chai'
|
|
// Linter can see that API is used in types.
|
|
// eslint-disable-next-line
|
|
import * as API from 'multiformats'
|
|
|
|
const textEncoder = new TextEncoder()
|
|
|
|
describe('CID', () => {
|
|
describe('v0', () => {
|
|
it('handles B58Str multihash', () => {
|
|
const mhStr = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
|
|
const cid = CID.parse(mhStr)
|
|
|
|
assert.deepStrictEqual(cid.version, 0)
|
|
assert.deepStrictEqual(cid.code, 112)
|
|
assert.deepStrictEqual(cid.multihash.bytes, base58btc.baseDecode(mhStr))
|
|
|
|
assert.deepStrictEqual(cid.toString(), mhStr)
|
|
})
|
|
|
|
it('create by parts', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode('abc'))
|
|
const cid = CID.create(0, 112, hash)
|
|
|
|
assert.deepStrictEqual(cid.code, 112)
|
|
assert.deepStrictEqual(cid.version, 0)
|
|
assert.deepStrictEqual(cid.multihash, hash)
|
|
assert.deepStrictEqual(cid.toString(), base58btc.baseEncode(hash.bytes))
|
|
})
|
|
|
|
it('CID.createV0', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode('abc'))
|
|
const cid = CID.createV0(hash)
|
|
|
|
assert.deepStrictEqual(cid.code, 112)
|
|
assert.deepStrictEqual(cid.version, 0)
|
|
assert.deepStrictEqual(cid.multihash, hash)
|
|
assert.deepStrictEqual(cid.toString(), base58btc.baseEncode(hash.bytes))
|
|
})
|
|
|
|
it('create from multihash', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode('abc'))
|
|
|
|
const cid = CID.decode(hash.bytes)
|
|
|
|
assert.deepStrictEqual(cid.code, 112)
|
|
assert.deepStrictEqual(cid.version, 0)
|
|
assert.deepStrictEqual(cid.multihash.digest, hash.digest)
|
|
assert.deepStrictEqual(
|
|
{ ...cid.multihash, digest: null },
|
|
{ ...hash, digest: null }
|
|
)
|
|
cid.toString()
|
|
assert.deepStrictEqual(cid.toString(), base58btc.baseEncode(hash.bytes))
|
|
})
|
|
|
|
it('throws on invalid BS58Str multihash ', async () => {
|
|
const msg = 'Non-base58btc character'
|
|
assert.throws(
|
|
() => CID.parse('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zIII'),
|
|
msg
|
|
)
|
|
})
|
|
|
|
it('throws on trying to create a CIDv0 with a codec other than dag-pb', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode('abc'))
|
|
const msg = 'Version 0 CID must use dag-pb (code: 112) block encoding'
|
|
assert.throws(() => CID.create(0, 113, hash), msg)
|
|
})
|
|
|
|
it('throws on trying to base encode CIDv0 in other base than base58btc', async () => {
|
|
const mhStr = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
|
|
const cid = CID.parse(mhStr)
|
|
const msg = 'Cannot string encode V0 in base32 encoding'
|
|
assert.throws(() => cid.toString(base32), msg)
|
|
})
|
|
|
|
it('throws on CIDv0 string with explicit multibase prefix', async () => {
|
|
const str = 'zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
|
|
const msg = 'Version 0 CID string must not include multibase prefix'
|
|
assert.throws(() => CID.parse(str), msg)
|
|
})
|
|
|
|
it('.bytes', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode('abc'))
|
|
const codec = 112
|
|
const cid = CID.create(0, codec, hash)
|
|
const bytes = cid.bytes
|
|
assert.ok(bytes)
|
|
const str = toHex(bytes)
|
|
assert.deepStrictEqual(
|
|
str,
|
|
'1220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
|
|
)
|
|
})
|
|
|
|
it('should construct from an old CID', () => {
|
|
const cidStr = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
|
|
const oldCid = CID.parse(cidStr)
|
|
const newCid = /** @type {CID} */ (CID.asCID(oldCid))
|
|
assert.deepStrictEqual(newCid.toString(), cidStr)
|
|
})
|
|
|
|
it('inspect bytes', () => {
|
|
const byts = fromHex(
|
|
'1220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
|
|
)
|
|
const inspected = CID.inspectBytes(byts.subarray(0, 10)) // should only need the first few bytes
|
|
assert.deepStrictEqual(
|
|
{
|
|
version: 0,
|
|
codec: 0x70,
|
|
multihashCode: 0x12,
|
|
multihashSize: 34,
|
|
digestSize: 32,
|
|
size: 34
|
|
},
|
|
inspected
|
|
)
|
|
})
|
|
|
|
describe('decodeFirst', () => {
|
|
it('no remainder', () => {
|
|
const byts = fromHex(
|
|
'1220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
|
|
)
|
|
const [cid, remainder] = CID.decodeFirst(byts)
|
|
assert.deepStrictEqual(
|
|
cid.toString(),
|
|
'QmatYkNGZnELf8cAGdyJpUca2PyY4szai3RHyyWofNY1pY'
|
|
)
|
|
assert.deepStrictEqual(remainder.byteLength, 0)
|
|
})
|
|
|
|
it('remainder', () => {
|
|
const byts = fromHex(
|
|
'1220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad0102030405'
|
|
)
|
|
const [cid, remainder] = CID.decodeFirst(byts)
|
|
assert.deepStrictEqual(
|
|
cid.toString(),
|
|
'QmatYkNGZnELf8cAGdyJpUca2PyY4szai3RHyyWofNY1pY'
|
|
)
|
|
assert.deepStrictEqual(toHex(remainder), '0102030405')
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('v1', () => {
|
|
it('handles CID String (multibase encoded)', () => {
|
|
const cidStr = 'zdj7Wd8AMwqnhJGQCbFxBVodGSBG84TM7Hs1rcJuQMwTyfEDS'
|
|
const cid = CID.parse(cidStr)
|
|
assert.deepStrictEqual(cid.code, 112)
|
|
assert.deepStrictEqual(cid.version, 1)
|
|
assert.ok(cid.multihash)
|
|
assert.deepStrictEqual(cid.toString(), base32.encode(cid.bytes))
|
|
})
|
|
|
|
it('handles CID (no multibase)', () => {
|
|
const cidStr =
|
|
'bafybeidskjjd4zmr7oh6ku6wp72vvbxyibcli2r6if3ocdcy7jjjusvl2u'
|
|
const cidBuf = fromHex(
|
|
'017012207252523e6591fb8fe553d67ff55a86f84044b46a3e4176e10c58fa529a4aabd5'
|
|
)
|
|
const cid = CID.decode(cidBuf)
|
|
assert.deepStrictEqual(cid.code, 112)
|
|
assert.deepStrictEqual(cid.version, 1)
|
|
assert.deepStrictEqual(cid.toString(), cidStr)
|
|
})
|
|
|
|
it('create by parts', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode('abc'))
|
|
const cid = CID.create(1, 0x71, hash)
|
|
assert.deepStrictEqual(cid.code, 0x71)
|
|
assert.deepStrictEqual(cid.version, 1)
|
|
equalDigest(cid.multihash, hash)
|
|
})
|
|
|
|
it('CID.createV1', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode('abc'))
|
|
const cid = CID.createV1(0x71, hash)
|
|
|
|
assert.deepStrictEqual(cid.code, 0x71)
|
|
assert.deepStrictEqual(cid.version, 1)
|
|
equalDigest(cid.multihash, hash)
|
|
})
|
|
|
|
it('can roundtrip through cid.toString()', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode('abc'))
|
|
const cid1 = CID.create(1, 0x71, hash)
|
|
const cid2 = CID.parse(cid1.toString())
|
|
|
|
assert.deepStrictEqual(cid1.code, cid2.code)
|
|
assert.deepStrictEqual(cid1.version, cid2.version)
|
|
assert.deepStrictEqual(cid1.multihash.digest, cid2.multihash.digest)
|
|
assert.deepStrictEqual(cid1.multihash.bytes, cid2.multihash.bytes)
|
|
const clear = { digest: null, bytes: null }
|
|
assert.deepStrictEqual(
|
|
{ ...cid1.multihash, ...clear },
|
|
{ ...cid2.multihash, ...clear }
|
|
)
|
|
})
|
|
|
|
it('.bytes', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode('abc'))
|
|
const code = 0x71
|
|
const cid = CID.create(1, code, hash)
|
|
const bytes = cid.bytes
|
|
assert.ok(bytes)
|
|
const str = toHex(bytes)
|
|
assert.deepStrictEqual(
|
|
str,
|
|
'01711220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
|
|
)
|
|
})
|
|
|
|
it('should construct from an old CID without a multibaseName', () => {
|
|
const cidStr =
|
|
'bafybeidskjjd4zmr7oh6ku6wp72vvbxyibcli2r6if3ocdcy7jjjusvl2u'
|
|
const oldCid = CID.parse(cidStr)
|
|
const newCid = /** @type {CID} */ (CID.asCID(oldCid))
|
|
assert.deepStrictEqual(newCid.toString(), cidStr)
|
|
})
|
|
|
|
it('.link() should return this CID', () => {
|
|
const cid = CID.parse('bafybeidskjjd4zmr7oh6ku6wp72vvbxyibcli2r6if3ocdcy7jjjusvl2u')
|
|
assert.equal(cid, cid.link())
|
|
})
|
|
})
|
|
|
|
describe('utilities', () => {
|
|
const h1 = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
|
|
const h2 = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1o'
|
|
const h3 = 'zdj7Wd8AMwqnhJGQCbFxBVodGSBG84TM7Hs1rcJuQMwTyfEDS'
|
|
|
|
it('.equals v0 to v0', () => {
|
|
const cid1 = CID.parse(h1)
|
|
assert.deepStrictEqual(cid1.equals(CID.parse(h1)), true)
|
|
assert.deepStrictEqual(
|
|
cid1.equals(CID.create(cid1.version, cid1.code, cid1.multihash)),
|
|
true
|
|
)
|
|
|
|
const cid2 = CID.parse(h2)
|
|
assert.deepStrictEqual(cid1.equals(CID.parse(h2)), false)
|
|
assert.deepStrictEqual(
|
|
cid1.equals(CID.create(cid2.version, cid2.code, cid2.multihash)),
|
|
false
|
|
)
|
|
})
|
|
|
|
it('.equals v0 to v1 and vice versa', () => {
|
|
const cidV1 = CID.parse(h3)
|
|
|
|
const cidV0 = cidV1.toV0()
|
|
|
|
assert.deepStrictEqual(cidV0.equals(cidV1), false)
|
|
assert.deepStrictEqual(cidV1.equals(cidV0), false)
|
|
|
|
assert.deepStrictEqual(cidV1.multihash, cidV0.multihash)
|
|
})
|
|
|
|
it('.equals v1 to v1', () => {
|
|
const cid1 = CID.parse(h3)
|
|
|
|
assert.deepStrictEqual(cid1.equals(CID.parse(h3)), true)
|
|
assert.deepStrictEqual(
|
|
cid1.equals(CID.create(cid1.version, cid1.code, cid1.multihash)),
|
|
true
|
|
)
|
|
})
|
|
|
|
it('works with deepEquals', () => {
|
|
const ch1 = CID.parse(h1)
|
|
assert.deepStrictEqual(ch1, CID.parse(h1))
|
|
assert.notDeepEqual(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)})`
|
|
it(name, async () => assert.throws(() => CID.parse(i)))
|
|
}
|
|
|
|
const decode = [
|
|
textEncoder.encode('hello world'),
|
|
textEncoder.encode('QmaozNR7DZHQK1ZcU9p7QdrshMvXqWK6gpu5rmrkPdT')
|
|
]
|
|
|
|
for (const i of decode) {
|
|
const name = `CID.decode(textEncoder.encode(${JSON.stringify(
|
|
i.toString()
|
|
)}))`
|
|
it(name, async () => assert.throws(() => 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 =
|
|
hash instanceof Uint8Array ? `textEncoder.encode(${form})` : form
|
|
const name = `CID.create(${version}, ${code}, ${mh})`
|
|
// @ts-expect-error - version issn't always 0|1
|
|
it(name, async () => assert.throws(() => CID.create(version, code, hash)))
|
|
}
|
|
|
|
it('invalid fixtures', async () => {
|
|
for (const test of invalidMultihash) {
|
|
const buff = fromHex(`0171${test.hex}`)
|
|
assert.throws(() => CID.decode(buff), new RegExp(test.message))
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('idempotence', () => {
|
|
const h1 = 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n'
|
|
const cid1 = CID.parse(h1)
|
|
const cid2 = CID.asCID(cid1)
|
|
|
|
it('constructor accept constructed instance', () => {
|
|
assert.deepStrictEqual(cid1 === cid2, true)
|
|
})
|
|
})
|
|
|
|
describe('conversion v0 <-> v1', () => {
|
|
it('should convert v0 to v1', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode(`TEST${Date.now()}`))
|
|
const cid = CID.create(0, 112, hash).toV1()
|
|
assert.deepStrictEqual(cid.version, 1)
|
|
})
|
|
|
|
it('should convert v1 to v0', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode(`TEST${Date.now()}`))
|
|
const cid = CID.create(1, 112, hash).toV0()
|
|
assert.deepStrictEqual(cid.version, 0)
|
|
})
|
|
|
|
it('should not convert v1 to v0 if not dag-pb codec', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode(`TEST${Date.now()}`))
|
|
const cid = CID.create(1, 0x71, hash)
|
|
assert.throws(
|
|
() => cid.toV0(),
|
|
'Cannot convert a non dag-pb CID to CIDv0'
|
|
)
|
|
})
|
|
|
|
it('should not convert v1 to v0 if not sha2-256 multihash', async () => {
|
|
const hash = await sha512.digest(textEncoder.encode(`TEST${Date.now()}`))
|
|
const cid = CID.create(1, 112, hash)
|
|
assert.throws(
|
|
() => cid.toV0(),
|
|
'Cannot convert non sha2-256 multihash CID to CIDv0'
|
|
)
|
|
})
|
|
|
|
it('should return assert.deepStrictEqual instance when converting v1 to v1', async () => {
|
|
const hash = await sha512.digest(textEncoder.encode(`TEST${Date.now()}`))
|
|
const cid = CID.create(1, 112, hash)
|
|
|
|
assert.deepStrictEqual(cid.toV1() === cid, true)
|
|
})
|
|
|
|
it('should return assert.deepStrictEqual instance when converting v0 to v0', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode(`TEST${Date.now()}`))
|
|
const cid = CID.create(0, 112, hash)
|
|
assert.deepStrictEqual(cid.toV0() === cid, true)
|
|
})
|
|
|
|
it('should fail to convert unknown version', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode(`TEST${Date.now()}`))
|
|
const cid = CID.create(0, 112, hash)
|
|
const cid1 = Object.assign(Object.create(Object.getPrototypeOf(cid)), {
|
|
...cid
|
|
})
|
|
const cid2 = Object.assign(Object.create(Object.getPrototypeOf(cid)), {
|
|
...cid,
|
|
version: 3
|
|
})
|
|
|
|
assert.deepStrictEqual(cid1.toV0().version, 0)
|
|
assert.deepStrictEqual(cid1.toV1().version, 1)
|
|
assert.equal(cid2.version, 3)
|
|
|
|
assert.throws(
|
|
() => cid2.toV1(),
|
|
/Can not convert CID version 3 to version 1/
|
|
)
|
|
assert.throws(
|
|
() => cid2.toV0(),
|
|
/Can not convert CID version 3 to version 0/
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('caching', () => {
|
|
it('should cache CID as buffer', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode(`TEST${Date.now()}`))
|
|
const cid = CID.create(1, 112, hash)
|
|
assert.ok(cid.bytes)
|
|
assert.deepStrictEqual(cid.bytes, cid.bytes)
|
|
})
|
|
|
|
it('should cache string representation when it matches the multibaseName it was constructed with', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode('abc'))
|
|
const cid = CID.create(1, 112, hash)
|
|
|
|
const b32 = {
|
|
...base32,
|
|
callCount: 0,
|
|
/**
|
|
* @param {Uint8Array} bytes
|
|
*/
|
|
encode (bytes) {
|
|
this.callCount += 1
|
|
return base32.encode(bytes) + '!'
|
|
}
|
|
}
|
|
|
|
const b64 = {
|
|
...base64,
|
|
callCount: 0,
|
|
/**
|
|
* @param {Uint8Array} bytes
|
|
*/
|
|
encode (bytes) {
|
|
this.callCount += 1
|
|
return base64.encode(bytes)
|
|
}
|
|
}
|
|
|
|
const base32String =
|
|
'bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu'
|
|
assert.deepEqual(cid.toString(b32), `${base32String}!`)
|
|
assert.deepEqual(b32.callCount, 1)
|
|
assert.deepEqual(cid.toString(), `${base32String}!`)
|
|
assert.deepEqual(b32.callCount, 1)
|
|
|
|
assert.deepStrictEqual(
|
|
cid.toString(b64),
|
|
'mAXASILp4Fr+PAc/qQUFA3l2uIiOwA2Gjlhd6nLQQ/2HyABWt'
|
|
)
|
|
assert.equal(b64.callCount, 1)
|
|
assert.deepStrictEqual(
|
|
cid.toString(b64),
|
|
'mAXASILp4Fr+PAc/qQUFA3l2uIiOwA2Gjlhd6nLQQ/2HyABWt'
|
|
)
|
|
assert.equal(b64.callCount, 1)
|
|
})
|
|
|
|
it('should cache string representation when constructed with one', () => {
|
|
const base32String =
|
|
'bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu'
|
|
const cid = CID.parse(base32String)
|
|
|
|
assert.deepStrictEqual(
|
|
cid.toString({
|
|
...base32,
|
|
encode () {
|
|
throw Error('Should not call decode')
|
|
}
|
|
}),
|
|
base32String
|
|
)
|
|
})
|
|
})
|
|
|
|
it('toJSON()', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode('abc'))
|
|
const cid = CID.create(1, 112, hash)
|
|
|
|
assert.deepStrictEqual(cid.toJSON(), {
|
|
'/': cid.toString()
|
|
})
|
|
})
|
|
|
|
it('asCID', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode('abc'))
|
|
class IncompatibleCID {
|
|
/**
|
|
* @param {number} version
|
|
* @param {number} code
|
|
* @param {import('multiformats/hashes/interface').MultihashDigest} multihash
|
|
*/
|
|
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.strictEqual(incompatibleCID.toString(), '[object Object]')
|
|
// @ts-expect-error - no such method
|
|
assert.strictEqual(typeof incompatibleCID.toV0, 'undefined')
|
|
|
|
const cid1 = /** @type {CID} */ (CID.asCID(incompatibleCID))
|
|
assert.ok(cid1 instanceof CID)
|
|
assert.strictEqual(cid1.code, code)
|
|
assert.strictEqual(cid1.version, version)
|
|
assert.ok(equals(cid1.multihash.bytes, hash.bytes))
|
|
|
|
const cid2 = CID.asCID({ version, code, hash })
|
|
assert.strictEqual(cid2, null)
|
|
|
|
const duckCID = { version, code, multihash: hash }
|
|
// @ts-expect-error - no such property
|
|
duckCID.asCID = duckCID
|
|
const cid3 = /** @type {CID} */ (CID.asCID(duckCID))
|
|
assert.ok(cid3 instanceof CID)
|
|
assert.strictEqual(cid3.code, code)
|
|
assert.strictEqual(cid3.version, version)
|
|
assert.ok(equals(cid3.multihash.bytes, hash.bytes))
|
|
|
|
const cid4 = CID.asCID(cid3)
|
|
assert.strictEqual(cid3, cid4)
|
|
|
|
const cid5 = /** @type {CID} */ (
|
|
CID.asCID(new OLDCID(1, 'raw', Uint8Array.from(hash.bytes)))
|
|
)
|
|
assert.ok(cid5 instanceof CID)
|
|
assert.strictEqual(cid5.version, 1)
|
|
assert.ok(equals(cid5.multihash.bytes, hash.bytes))
|
|
assert.strictEqual(cid5.code, 85)
|
|
})
|
|
|
|
/**
|
|
* @param {API.CID} x
|
|
* @param {API.CID} y
|
|
*/
|
|
const digestsame = (x, y) => {
|
|
// @ts-ignore - not sure what this supposed to be
|
|
assert.deepStrictEqual(x.hash, y.hash)
|
|
assert.deepStrictEqual(x.bytes, y.bytes)
|
|
if (x.multihash) {
|
|
equalDigest(x.multihash, y.multihash)
|
|
}
|
|
const empty = { hash: null, bytes: null, digest: null, multihash: null }
|
|
assert.deepStrictEqual({ ...x, ...empty }, { ...y, ...empty })
|
|
}
|
|
|
|
/**
|
|
* @typedef {import('multiformats/hashes/interface').MultihashDigest} MultihashDigest
|
|
* @param {MultihashDigest} x
|
|
* @param {MultihashDigest} y
|
|
*/
|
|
const equalDigest = (x, y) => {
|
|
assert.deepStrictEqual(x.digest, y.digest)
|
|
assert.deepStrictEqual(x.code, y.code)
|
|
assert.deepStrictEqual(x.digest, y.digest)
|
|
}
|
|
|
|
describe('CID.parse', async () => {
|
|
it('parse 32 encoded CIDv1', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode('abc'))
|
|
const cid = CID.create(1, 112, hash)
|
|
|
|
const parsed = CID.parse(cid.toString())
|
|
digestsame(cid, parsed)
|
|
})
|
|
|
|
it('parse base58btc encoded CIDv1', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode('abc'))
|
|
const cid = CID.create(1, 112, hash)
|
|
|
|
const parsed = CID.parse(cid.toString(base58btc))
|
|
digestsame(cid, parsed)
|
|
})
|
|
|
|
it('parse base58btc encoded CIDv0', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode('abc'))
|
|
const cid = CID.create(0, 112, hash)
|
|
|
|
const parsed = CID.parse(cid.toString())
|
|
digestsame(cid, parsed)
|
|
})
|
|
|
|
it('fails to parse base64 encoded CIDv1', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode('abc'))
|
|
const cid = CID.create(1, 112, hash)
|
|
const msg =
|
|
'To parse non base32 or base58btc encoded CID multibase decoder must be provided'
|
|
|
|
assert.throws(() => CID.parse(cid.toString(base64)), msg)
|
|
})
|
|
|
|
it('parses base64 encoded CIDv1 if base64 is provided', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode('abc'))
|
|
const cid = CID.create(1, 112, hash)
|
|
|
|
const parsed = CID.parse(cid.toString(base64), base64)
|
|
digestsame(cid, parsed)
|
|
})
|
|
})
|
|
|
|
it('inspect bytes', () => {
|
|
const byts = fromHex(
|
|
'01711220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
|
|
)
|
|
const inspected = CID.inspectBytes(byts.subarray(0, 10)) // should only need the first few bytes
|
|
assert.deepStrictEqual(
|
|
{
|
|
version: 1,
|
|
codec: 0x71,
|
|
multihashCode: 0x12,
|
|
multihashSize: 34,
|
|
digestSize: 32,
|
|
size: 36
|
|
},
|
|
inspected
|
|
)
|
|
|
|
describe('decodeFirst', () => {
|
|
it('no remainder', () => {
|
|
const byts = fromHex(
|
|
'01711220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
|
|
)
|
|
const [cid, remainder] = CID.decodeFirst(byts)
|
|
assert.deepStrictEqual(
|
|
cid.toString(),
|
|
'bafyreif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu'
|
|
)
|
|
assert.deepStrictEqual(remainder.byteLength, 0)
|
|
})
|
|
|
|
it('remainder', () => {
|
|
const byts = fromHex(
|
|
'01711220ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad0102030405'
|
|
)
|
|
const [cid, remainder] = CID.decodeFirst(byts)
|
|
assert.deepStrictEqual(
|
|
cid.toString(),
|
|
'bafyreif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu'
|
|
)
|
|
assert.deepStrictEqual(toHex(remainder), '0102030405')
|
|
})
|
|
})
|
|
})
|
|
|
|
it('new CID from old CID', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode('abc'))
|
|
const cid = /** @type {CID} */ (
|
|
CID.asCID(new OLDCID(1, 'raw', Uint8Array.from(hash.bytes)))
|
|
)
|
|
assert.deepStrictEqual(cid.version, 1)
|
|
|
|
equalDigest(cid.multihash, hash)
|
|
assert.deepStrictEqual(cid.code, 85)
|
|
})
|
|
|
|
it('util.inspect', async () => {
|
|
const hash = await sha256.digest(textEncoder.encode('abc'))
|
|
const cid = CID.create(1, 112, hash)
|
|
assert.deepStrictEqual(
|
|
// @ts-expect-error - no such method is known
|
|
typeof cid[Symbol.for('nodejs.util.inspect.custom')],
|
|
'function'
|
|
)
|
|
assert.deepStrictEqual(
|
|
// @ts-expect-error - no such method is known
|
|
cid[Symbol.for('nodejs.util.inspect.custom')](),
|
|
'CID(bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu)'
|
|
)
|
|
})
|
|
|
|
it('invalid CID version', async () => {
|
|
const encoded = varint.encodeTo(2, new Uint8Array(32))
|
|
assert.throws(() => CID.decode(encoded), 'Invalid CID version 2')
|
|
})
|
|
|
|
it('CID can be moved across JS realms', async () => {
|
|
const cid = CID.parse('bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu')
|
|
const { port1: sender, port2: receiver } = new MessageChannel()
|
|
sender.postMessage(cid)
|
|
const cid2 = await new Promise((resolve) => {
|
|
receiver.onmessage = (event) => { resolve(event.data) }
|
|
})
|
|
sender.close()
|
|
receiver.close()
|
|
assert.strictEqual(cid2['/'], cid2.bytes)
|
|
})
|
|
|
|
describe('decode', () => {
|
|
const tests = {
|
|
v0: 'QmTFHZL5CkgNz19MdPnSuyLAi6AVq9fFp81zmPpaL2amED',
|
|
v1: 'bafybeif2pall7dybz7vecqka3zo24irdwabwdi4wc55jznaq75q7eaavvu'
|
|
}
|
|
|
|
Object.entries(tests).forEach(([version, cidString]) => {
|
|
it(`decode ${version} from bytes`, () => {
|
|
const cid1 = CID.parse(cidString)
|
|
const cid2 = CID.decode(cid1.bytes)
|
|
|
|
assert.deepStrictEqual(cid1, cid2)
|
|
})
|
|
|
|
it(`decode ${version} from subarray`, () => {
|
|
const cid1 = CID.parse(cidString)
|
|
// a byte array with an extra byte at the start and end
|
|
const bytes = new Uint8Array(cid1.bytes.length + 2)
|
|
bytes.set(cid1.bytes, 1)
|
|
// slice the cid bytes out of the middle to have a subarray with a non-zero .byteOffset
|
|
const subarray = bytes.subarray(1, cid1.bytes.length + 1)
|
|
const cid2 = CID.decode(subarray)
|
|
|
|
assert.deepStrictEqual(cid1, cid2)
|
|
assert.equal(cid1.byteLength, cid2.byteLength)
|
|
assert.equal(typeof cid2.byteOffset, 'number')
|
|
})
|
|
})
|
|
})
|
|
})
|