309 lines
7.3 KiB
JavaScript
309 lines
7.3 KiB
JavaScript
'use strict'
|
|
|
|
const { Buffer } = require('buffer')
|
|
const mh = require('multihashes')
|
|
const multibase = require('multibase')
|
|
const multicodec = require('multicodec')
|
|
const codecs = require('multicodec/src/base-table.json')
|
|
const CIDUtil = require('./cid-util')
|
|
const withIs = require('class-is')
|
|
|
|
/**
|
|
* @typedef {Object} SerializedCID
|
|
* @param {string} codec
|
|
* @param {number} version
|
|
* @param {Buffer} multihash
|
|
*/
|
|
|
|
/**
|
|
* Test if the given input is a CID.
|
|
* @function isCID
|
|
* @memberof CID
|
|
* @static
|
|
* @param {any} other
|
|
* @returns {bool}
|
|
*/
|
|
|
|
/**
|
|
* Class representing a CID `<mbase><version><mcodec><mhash>`
|
|
* , as defined in [ipld/cid](https://github.com/multiformats/cid).
|
|
* @class CID
|
|
*/
|
|
class CID {
|
|
/**
|
|
* Create a new CID.
|
|
*
|
|
* The algorithm for argument input is roughly:
|
|
* ```
|
|
* if (cid)
|
|
* -> create a copy
|
|
* else if (str)
|
|
* if (1st char is on multibase table) -> CID String
|
|
* else -> bs58 encoded multihash
|
|
* else if (Buffer)
|
|
* if (1st byte is 0 or 1) -> CID
|
|
* else -> multihash
|
|
* else if (Number)
|
|
* -> construct CID by parts
|
|
* ```
|
|
*
|
|
* @param {string|Buffer|CID} version
|
|
* @param {string} [codec]
|
|
* @param {Buffer} [multihash]
|
|
* @param {string} [multibaseName]
|
|
*
|
|
* @example
|
|
* new CID(<version>, <codec>, <multihash>, <multibaseName>)
|
|
* new CID(<cidStr>)
|
|
* new CID(<cid.buffer>)
|
|
* new CID(<multihash>)
|
|
* new CID(<bs58 encoded multihash>)
|
|
* new CID(<cid>)
|
|
*/
|
|
constructor (version, codec, multihash, multibaseName) {
|
|
if (_CID.isCID(version)) {
|
|
// version is an exising CID instance
|
|
const cid = version
|
|
this.version = cid.version
|
|
this.codec = cid.codec
|
|
this.multihash = Buffer.from(cid.multihash)
|
|
// Default guard for when a CID < 0.7 is passed with no multibaseName
|
|
this.multibaseName = cid.multibaseName || (cid.version === 0 ? 'base58btc' : 'base32')
|
|
return
|
|
}
|
|
|
|
if (typeof version === 'string') {
|
|
// e.g. 'base32' or false
|
|
const baseName = multibase.isEncoded(version)
|
|
if (baseName) {
|
|
// version is a CID String encoded with multibase, so v1
|
|
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))
|
|
this.multibaseName = baseName
|
|
} else {
|
|
// version is a base58btc string multihash, so v0
|
|
this.version = 0
|
|
this.codec = 'dag-pb'
|
|
this.multihash = mh.fromB58String(version)
|
|
this.multibaseName = 'base58btc'
|
|
}
|
|
CID.validateCID(this)
|
|
Object.defineProperty(this, 'string', { value: version })
|
|
return
|
|
}
|
|
|
|
if (Buffer.isBuffer(version)) {
|
|
const firstByte = version.slice(0, 1)
|
|
const v = parseInt(firstByte.toString('hex'), 16)
|
|
if (v === 1) {
|
|
// version is a CID buffer
|
|
const cid = version
|
|
this.version = v
|
|
this.codec = multicodec.getCodec(cid.slice(1))
|
|
this.multihash = multicodec.rmPrefix(cid.slice(1))
|
|
this.multibaseName = 'base32'
|
|
} else {
|
|
// version is a raw multihash buffer, so v0
|
|
this.version = 0
|
|
this.codec = 'dag-pb'
|
|
this.multihash = version
|
|
this.multibaseName = 'base58btc'
|
|
}
|
|
CID.validateCID(this)
|
|
return
|
|
}
|
|
|
|
// otherwise, assemble the CID from the parameters
|
|
|
|
/**
|
|
* @type {number}
|
|
*/
|
|
this.version = version
|
|
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
this.codec = codec
|
|
|
|
/**
|
|
* @type {Buffer}
|
|
*/
|
|
this.multihash = multihash
|
|
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
this.multibaseName = multibaseName || (version === 0 ? 'base58btc' : 'base32')
|
|
|
|
CID.validateCID(this)
|
|
}
|
|
|
|
/**
|
|
* The CID as a `Buffer`
|
|
*
|
|
* @return {Buffer}
|
|
* @readonly
|
|
*
|
|
* @memberOf CID
|
|
*/
|
|
get buffer () {
|
|
let buffer = this._buffer
|
|
|
|
if (!buffer) {
|
|
if (this.version === 0) {
|
|
buffer = this.multihash
|
|
} else if (this.version === 1) {
|
|
buffer = Buffer.concat([
|
|
Buffer.from('01', 'hex'),
|
|
multicodec.getCodeVarint(this.codec),
|
|
this.multihash
|
|
])
|
|
} else {
|
|
throw new Error('unsupported version')
|
|
}
|
|
|
|
// Cache this buffer so it doesn't have to be recreated
|
|
Object.defineProperty(this, '_buffer', { value: buffer })
|
|
}
|
|
|
|
return buffer
|
|
}
|
|
|
|
/**
|
|
* Get the prefix of the CID.
|
|
*
|
|
* @returns {Buffer}
|
|
* @readonly
|
|
*/
|
|
get prefix () {
|
|
return Buffer.concat([
|
|
Buffer.from(`0${this.version}`, 'hex'),
|
|
multicodec.getCodeVarint(this.codec),
|
|
mh.prefix(this.multihash)
|
|
])
|
|
}
|
|
|
|
/**
|
|
* Convert to a CID of version `0`.
|
|
*
|
|
* @returns {CID}
|
|
*/
|
|
toV0 () {
|
|
if (this.codec !== 'dag-pb') {
|
|
throw new Error('Cannot convert a non dag-pb CID to CIDv0')
|
|
}
|
|
|
|
const { name, length } = mh.decode(this.multihash)
|
|
|
|
if (name !== 'sha2-256') {
|
|
throw new Error('Cannot convert non sha2-256 multihash CID to CIDv0')
|
|
}
|
|
|
|
if (length !== 32) {
|
|
throw new Error('Cannot convert non 32 byte multihash CID to CIDv0')
|
|
}
|
|
|
|
return new _CID(0, this.codec, this.multihash)
|
|
}
|
|
|
|
/**
|
|
* Convert to a CID of version `1`.
|
|
*
|
|
* @returns {CID}
|
|
*/
|
|
toV1 () {
|
|
return new _CID(1, this.codec, this.multihash)
|
|
}
|
|
|
|
/**
|
|
* Encode the CID into a string.
|
|
*
|
|
* @param {string} [base=this.multibaseName] - Base encoding to use.
|
|
* @returns {string}
|
|
*/
|
|
toBaseEncodedString (base = this.multibaseName) {
|
|
if (this.string && base === this.multibaseName) {
|
|
return this.string
|
|
}
|
|
let str = null
|
|
if (this.version === 0) {
|
|
if (base !== 'base58btc') {
|
|
throw new Error('not supported with CIDv0, to support different bases, please migrate the instance do CIDv1, you can do that through cid.toV1()')
|
|
}
|
|
str = mh.toB58String(this.multihash)
|
|
} else if (this.version === 1) {
|
|
str = multibase.encode(base, this.buffer).toString()
|
|
} else {
|
|
throw new Error('unsupported version')
|
|
}
|
|
if (base === this.multibaseName) {
|
|
// cache the string value
|
|
Object.defineProperty(this, 'string', { value: str })
|
|
}
|
|
return str
|
|
}
|
|
|
|
/**
|
|
* CID(QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n)
|
|
*
|
|
* @returns {String}
|
|
*/
|
|
[Symbol.for('nodejs.util.inspect.custom')] () {
|
|
return 'CID(' + this.toString() + ')'
|
|
}
|
|
|
|
toString (base) {
|
|
return this.toBaseEncodedString(base)
|
|
}
|
|
|
|
/**
|
|
* Serialize to a plain object.
|
|
*
|
|
* @returns {SerializedCID}
|
|
*/
|
|
toJSON () {
|
|
return {
|
|
codec: this.codec,
|
|
version: this.version,
|
|
hash: this.multihash
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compare equality with another CID.
|
|
*
|
|
* @param {CID} other
|
|
* @returns {bool}
|
|
*/
|
|
equals (other) {
|
|
return this.codec === other.codec &&
|
|
this.version === other.version &&
|
|
this.multihash.equals(other.multihash)
|
|
}
|
|
|
|
/**
|
|
* Test if the given input is a valid CID object.
|
|
* Throws if it is not.
|
|
*
|
|
* @param {any} other
|
|
* @returns {void}
|
|
*/
|
|
static validateCID (other) {
|
|
const errorMsg = CIDUtil.checkCIDComponents(other)
|
|
if (errorMsg) {
|
|
throw new Error(errorMsg)
|
|
}
|
|
}
|
|
}
|
|
|
|
const _CID = withIs(CID, {
|
|
className: 'CID',
|
|
symbolName: '@ipld/js-cid/CID'
|
|
})
|
|
|
|
_CID.codecs = codecs
|
|
|
|
module.exports = _CID
|