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 1 commit
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('Nothing to see here')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a valuable error message

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a great error message 📦

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh common :)

}

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