UNPKG

multiformats

Version:

Interface for multihash, multicodec, multibase and CID

307 lines (304 loc) 8.58 kB
import * as varint from './varint.js'; import * as Digest from './hashes/digest.js'; import { base58btc } from './bases/base58.js'; import { base32 } from './bases/base32.js'; import { coerce } from './bytes.js'; export default class CID { constructor(version, code, multihash, bytes) { this.code = code; this.version = version; this.multihash = multihash; this.bytes = bytes; this.byteOffset = bytes.byteOffset; this.byteLength = bytes.byteLength; this.asCID = this; this._baseCache = new Map(); Object.defineProperties(this, { byteOffset: hidden, byteLength: hidden, code: readonly, version: readonly, multihash: readonly, bytes: readonly, _baseCache: hidden, asCID: hidden }); } toV0() { switch (this.version) { case 0: { return this; } default: { const {code, multihash} = this; if (code !== DAG_PB_CODE) { throw new Error('Cannot convert a non dag-pb CID to CIDv0'); } if (multihash.code !== SHA_256_CODE) { throw new Error('Cannot convert non sha2-256 multihash CID to CIDv0'); } return CID.createV0(multihash); } } } toV1() { switch (this.version) { case 0: { const {code, digest} = this.multihash; const multihash = Digest.create(code, digest); return CID.createV1(this.code, multihash); } case 1: { return this; } default: { throw Error(`Can not convert CID version ${ this.version } to version 0. This is a bug please report`); } } } equals(other) { return other && this.code === other.code && this.version === other.version && Digest.equals(this.multihash, other.multihash); } toString(base) { const {bytes, version, _baseCache} = this; switch (version) { case 0: return toStringV0(bytes, _baseCache, base || base58btc.encoder); default: return toStringV1(bytes, _baseCache, base || base32.encoder); } } toJSON() { return { code: this.code, version: this.version, hash: this.multihash.bytes }; } get [Symbol.toStringTag]() { return 'CID'; } [Symbol.for('nodejs.util.inspect.custom')]() { return 'CID(' + this.toString() + ')'; } static isCID(value) { deprecate(/^0\.0/, IS_CID_DEPRECATION); return !!(value && (value[cidSymbol] || value.asCID === value)); } get toBaseEncodedString() { throw new Error('Deprecated, use .toString()'); } get codec() { throw new Error('"codec" property is deprecated, use integer "code" property instead'); } get buffer() { throw new Error('Deprecated .buffer property, use .bytes to get Uint8Array instead'); } get multibaseName() { throw new Error('"multibaseName" property is deprecated'); } get prefix() { throw new Error('"prefix" property is deprecated'); } static asCID(value) { if (value instanceof CID) { return value; } else if (value != null && value.asCID === value) { const {version, code, multihash, bytes} = value; return new CID(version, code, multihash, bytes || encodeCID(version, code, multihash.bytes)); } else if (value != null && value[cidSymbol] === true) { const {version, multihash, code} = value; const digest = Digest.decode(multihash); return CID.create(version, code, digest); } else { return null; } } static create(version, code, digest) { if (typeof code !== 'number') { throw new Error('String codecs are no longer supported'); } switch (version) { case 0: { if (code !== DAG_PB_CODE) { throw new Error(`Version 0 CID must use dag-pb (code: ${ DAG_PB_CODE }) block encoding`); } else { return new CID(version, code, digest, digest.bytes); } } case 1: { const bytes = encodeCID(version, code, digest.bytes); return new CID(version, code, digest, bytes); } default: { throw new Error('Invalid version'); } } } static createV0(digest) { return CID.create(0, DAG_PB_CODE, digest); } static createV1(code, digest) { return CID.create(1, code, digest); } static decode(bytes) { const [cid, remainder] = CID.decodeFirst(bytes); if (remainder.length) { throw new Error('Incorrect length'); } return cid; } static decodeFirst(bytes) { const specs = CID.inspectBytes(bytes); const prefixSize = specs.size - specs.multihashSize; const multihashBytes = coerce(bytes.subarray(prefixSize, prefixSize + specs.multihashSize)); if (multihashBytes.byteLength !== specs.multihashSize) { throw new Error('Incorrect length'); } const digestBytes = multihashBytes.subarray(specs.multihashSize - specs.digestSize); const digest = new Digest.Digest(specs.multihashCode, specs.digestSize, digestBytes, multihashBytes); const cid = specs.version === 0 ? CID.createV0(digest) : CID.createV1(specs.codec, digest); return [ cid, bytes.subarray(specs.size) ]; } static inspectBytes(initialBytes) { let offset = 0; const next = () => { const [i, length] = varint.decode(initialBytes.subarray(offset)); offset += length; return i; }; let version = next(); let codec = DAG_PB_CODE; if (version === 18) { version = 0; offset = 0; } else if (version === 1) { codec = next(); } else if (version !== 1) { throw new RangeError(`Invalid CID version ${ version }`); } const prefixSize = offset; const multihashCode = next(); const digestSize = next(); const size = offset + digestSize; const multihashSize = size - prefixSize; return { version, codec, multihashCode, digestSize, multihashSize, size }; } static parse(source, base) { const [prefix, bytes] = parseCIDtoBytes(source, base); const cid = CID.decode(bytes); cid._baseCache.set(prefix, source); return cid; } } const parseCIDtoBytes = (source, base) => { switch (source[0]) { case 'Q': { const decoder = base || base58btc; return [ base58btc.prefix, decoder.decode(`${ base58btc.prefix }${ source }`) ]; } case base58btc.prefix: { const decoder = base || base58btc; return [ base58btc.prefix, decoder.decode(source) ]; } case base32.prefix: { const decoder = base || base32; return [ base32.prefix, decoder.decode(source) ]; } default: { if (base == null) { throw Error('To parse non base32 or base56btc encoded CID multibase decoder must be provided'); } return [ source[0], base.decode(source) ]; } } }; const toStringV0 = (bytes, cache, base) => { const {prefix} = base; if (prefix !== base58btc.prefix) { throw Error(`Cannot string encode V0 in ${ base.name } encoding`); } const cid = cache.get(prefix); if (cid == null) { const cid = base.encode(bytes).slice(1); cache.set(prefix, cid); return cid; } else { return cid; } }; const toStringV1 = (bytes, cache, base) => { const {prefix} = base; const cid = cache.get(prefix); if (cid == null) { const cid = base.encode(bytes); cache.set(prefix, cid); return cid; } else { return cid; } }; const DAG_PB_CODE = 112; const SHA_256_CODE = 18; const encodeCID = (version, code, multihash) => { const codeOffset = varint.encodingLength(version); const hashOffset = codeOffset + varint.encodingLength(code); const bytes = new Uint8Array(hashOffset + multihash.byteLength); varint.encodeTo(version, bytes, 0); varint.encodeTo(code, bytes, codeOffset); bytes.set(multihash, hashOffset); return bytes; }; const cidSymbol = Symbol.for('@ipld/js-cid/CID'); const readonly = { writable: false, configurable: false, enumerable: true }; const hidden = { writable: false, enumerable: false, configurable: false }; const version = '0.0.0-dev'; const deprecate = (range, message) => { if (range.test(version)) { console.warn(message); } else { throw new Error(message); } }; const IS_CID_DEPRECATION = `CID.isCID(v) is deprecated and will be removed in the next major release. Following code pattern: if (CID.isCID(value)) { doSomethingWithCID(value) } Is replaced with: const cid = CID.asCID(value) if (cid) { // Make sure to use cid instead of value doSomethingWithCID(cid) } `;