Skip to content
This repository has been archived by the owner on Sep 3, 2021. It is now read-only.

fix: add CID validation #30

Merged
merged 2 commits into from
Mar 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ build
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules

lib
dist
docs
35 changes: 1 addition & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,40 +79,7 @@ const cid = new CID(base58Multihash)

## API

### Constructor

- `new CID(<version>, <codec>, <multihash>)`
- `new CID(<cidStr>)`
- `new CID(<cid.buffer>)`
- `new CID(<multihash>)`
- `new CID(<bs58 encoded multihash>)`

### `.codec`

### `.version`

### `.multihash`

### `.buffer`

### `.prefix`

### `.toV0()`

### `.toV1()`

### `.toBaseEncodedString(base)`

Defaults to 'base58btc'

### `.toJSON()`

returns a buffer with CID version + multicodec + hashAlg + hashLen

### `CID.isCID(other)`

### `CID.codecs`

See https://ipld.github.io/js-cid

## Contribute

Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
"test:browser": "aegir-test browser",
"build": "aegir-build",
"test": "aegir-test",
"release": "aegir-release",
"release-minor": "aegir-release --type minor",
"release-major": "aegir-release --type major",
"release": "aegir-release --docs",
"release-minor": "aegir-release --type minor --docs",
"release-major": "aegir-release --type major --docs",
"coverage": "aegir-coverage",
"coverage-publish": "aegir-coverage publish"
"coverage-publish": "aegir-coverage publish",
"docs": "aegir-docs"
},
"pre-commit": [
"lint",
Expand Down
173 changes: 144 additions & 29 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,25 @@ const codecs = require('multicodec/src/base-table')
const codecVarints = require('multicodec/src/varint-table')
const multihash = require('multihashes')

// CID: <mbase><version><mcodec><mhash>
/**
* @typedef {Object} SerializedCID
* @param {string} codec
* @param {number} version
* @param {Buffer} multihash
*
*/

/**
* Class representing a CID `<mbase><version><mcodec><mhash>`
* , as defined in [ipld/cid](https://github.com/ipld/cid).
* @class CID
*/
class CID {
/*
/**
* Create a new CID.
*
* The algorithm for argument input is roughly:
* ```
* if (str)
* if (1st char is on multibase table) -> CID String
* else -> bs58 encoded multihash
Expand All @@ -21,46 +36,74 @@ class CID {
* -> construct CID by parts
*
* ..if only JS had traits..
* ```
*
* @param {string|Buffer} version
* @param {string} [codec]
* @param {Buffer} [multihash]
*
* @example
*
* new CID(<version>, <codec>, <multihash>)
* new CID(<cidStr>)
* new CID(<cid.buffer>)
* new CID(<multihash>)
* new CID(<bs58 encoded multihash>)
*
*/
constructor (version, codec, multihash) {
if (typeof version === 'string') {
if (multibase.isEncoded(version)) { // CID String (encoded with multibase)
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))
version = parseInt(cid.slice(0, 1).toString('hex'), 16)
codec = multicodec.getCodec(cid.slice(1))
multihash = multicodec.rmPrefix(cid.slice(1))
} else { // bs58 string encoded multihash
this.codec = 'dag-pb'
this.multihash = mh.fromB58String(version)
this.version = 0
codec = 'dag-pb'
multihash = mh.fromB58String(version)
version = 0
}
} else if (Buffer.isBuffer(version)) {
const firstByte = version.slice(0, 1)
const v = parseInt(firstByte.toString('hex'), 16)
if (v === 0 || v === 1) { // CID
const cid = version
this.version = v
this.codec = multicodec.getCodec(cid.slice(1))
this.multihash = multicodec.rmPrefix(cid.slice(1))
version = v
codec = multicodec.getCodec(cid.slice(1))
multihash = multicodec.rmPrefix(cid.slice(1))
} else { // multihash
this.codec = 'dag-pb'
this.multihash = version
this.version = 0
}
} else if (typeof version === 'number') {
if (typeof codec !== 'string') {
throw new Error('codec must be string')
codec = 'dag-pb'
multihash = version
version = 0
}
if (!(version === 0 || version === 1)) {
throw new Error('version must be a number equal to 0 or 1')
}
mh.validate(multihash)
this.codec = codec
this.version = version
this.multihash = multihash
}

/**
* @type {string}
*/
this.codec = codec

/**
* @type {number}
*/
this.version = version

/**
* @type {Buffer}
*/
this.multihash = multihash

CID.validateCID(this)
}

/**
* The CID as a `Buffer`
*
* @return {Buffer}
* @readonly
*
* @memberOf CID
*/
get buffer () {
switch (this.version) {
case 0:
Expand All @@ -76,6 +119,12 @@ class CID {
}
}

/**
* Get the prefix of the CID.
*
* @returns {Buffer}
* @readonly
*/
get prefix () {
return Buffer.concat([
new Buffer(`0${this.version}`, 'hex'),
Expand All @@ -84,6 +133,11 @@ class CID {
])
}

/**
* 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')
Expand All @@ -92,11 +146,21 @@ class CID {
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)
}

/* defaults to base58btc */
/**
* Encode the CID into a string.
*
* @param {string} [base='base58btc'] - Base encoding to use.
* @returns {string}
*/
toBaseEncodedString (base) {
base = base || 'base58btc'

Expand All @@ -114,6 +178,11 @@ class CID {
}
}

/**
* Serialize to a plain object.
*
* @returns {SerializedCID}
*/
toJSON () {
return {
codec: this.codec,
Expand All @@ -122,16 +191,62 @@ class CID {
}
}

/**
* 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 CID.
*
* @param {any} other
* @returns {bool}
*/
static isCID (other) {
try {
CID.validateCID(other)
} catch (err) {
return false
}

return true
}

/**
* Test if the given input is a valid CID object.
* Throws if it is not.
*
* @param {any} other
* @returns {void}
*/
static validateCID (other) {
if (other == null) {
throw new Error('null values are not valid CIDs')
}

if (!(other.version === 0 || other.version === 1)) {
throw new Error('Invalid version, must be a number equal to 1 or 0')
}

if (typeof other.codec !== 'string') {
throw new Error('codec must be string')
}

if (!Buffer.isBuffer(other.multihash)) {
throw new Error('multihash must be a Buffer')
}

mh.validate(other.multihash)
}
}

CID.codecs = codecs
CID.isCID = (other) => {
return other.constructor.name === 'CID'
}

module.exports = CID
Loading