multiformats
Version:
Interface for multihash, multicodec, multibase and CID
313 lines (306 loc) • 8.72 kB
JavaScript
;
var varint = require('./varint.js');
var digest = require('./hashes/digest.js');
var base58 = require('./bases/base58.js');
var base32 = require('./bases/base32.js');
var bytes = require('./bytes.js');
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: digest$1} = this.multihash;
const multihash = digest.create(code, digest$1);
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 || base58.base58btc.encoder);
default:
return toStringV1(bytes, _baseCache, base || base32.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$1 = digest.decode(multihash);
return CID.create(version, code, digest$1);
} 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$1) {
const specs = CID.inspectBytes(bytes$1);
const prefixSize = specs.size - specs.multihashSize;
const multihashBytes = bytes.coerce(bytes$1.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$1 = new digest.Digest(specs.multihashCode, specs.digestSize, digestBytes, multihashBytes);
const cid = specs.version === 0 ? CID.createV0(digest$1) : CID.createV1(specs.codec, digest$1);
return [
cid,
bytes$1.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 || base58.base58btc;
return [
base58.base58btc.prefix,
decoder.decode(`${ base58.base58btc.prefix }${ source }`)
];
}
case base58.base58btc.prefix: {
const decoder = base || base58.base58btc;
return [
base58.base58btc.prefix,
decoder.decode(source)
];
}
case base32.base32.prefix: {
const decoder = base || base32.base32;
return [
base32.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 !== base58.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)
}
`;
module.exports = CID;