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

Commit

Permalink
feat: accept Uint8Arrays input in place of Buffers (#88)
Browse files Browse the repository at this point in the history
Co-authored-by: Alex Potsides <[email protected]>
  • Loading branch information
Gozala and achingbrain authored Jul 30, 2020
1 parent aba7d98 commit 4bc2a05
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 58 deletions.
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@
"repository": "github:multiformats/js-multihash",
"dependencies": {
"buffer": "^5.6.0",
"multibase": "^1.0.1",
"varint": "^5.0.0"
"multibase": "^2.0.0",
"varint": "^5.0.0",
"web-encoding": "^1.0.2"
},
"devDependencies": {
"aegir": "^24.0.0",
Expand Down Expand Up @@ -67,4 +68,4 @@
"node": ">=10.0.0",
"npm": ">=6.0.0"
}
}
}
71 changes: 41 additions & 30 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-check
/* eslint-disable guard-for-in */
/**
* Multihash implementation in JavaScript.
Expand All @@ -10,7 +11,9 @@ const { Buffer } = require('buffer')
const multibase = require('multibase')
const varint = require('varint')
const { names } = require('./constants')
const { TextDecoder } = require('web-encoding')

const textDecoder = new TextDecoder()
const codes = {}

for (const key in names) {
Expand All @@ -22,15 +25,19 @@ exports.codes = Object.freeze(codes)
/**
* Convert the given multihash to a hex encoded string.
*
* @param {Buffer} hash
* @param {Uint8Array} hash
* @returns {string}
*/
exports.toHexString = function toHexString (hash) {
if (!Buffer.isBuffer(hash)) {
throw new Error('must be passed a buffer')
if (!(hash instanceof Uint8Array)) {
throw new Error('must be passed a Uint8Array')
}

return hash.toString('hex')
const buffer = Buffer.isBuffer(hash)
? hash
: Buffer.from(hash.buffer, hash.byteOffset, hash.byteLength)

return buffer.toString('hex')
}

/**
Expand All @@ -46,42 +53,44 @@ exports.fromHexString = function fromHexString (hash) {
/**
* Convert the given multihash to a base58 encoded string.
*
* @param {Buffer} hash
* @param {Uint8Array} hash
* @returns {string}
*/
exports.toB58String = function toB58String (hash) {
if (!Buffer.isBuffer(hash)) {
throw new Error('must be passed a buffer')
if (!(hash instanceof Uint8Array)) {
throw new Error('must be passed a Uint8Array')
}

return multibase.encode('base58btc', hash).toString().slice(1)
return textDecoder.decode(multibase.encode('base58btc', hash)).slice(1)
}

/**
* Convert the given base58 encoded string to a multihash.
*
* @param {string|Buffer} hash
* @param {string|Uint8Array} hash
* @returns {Buffer}
*/
exports.fromB58String = function fromB58String (hash) {
let encoded = hash
if (Buffer.isBuffer(hash)) {
encoded = hash.toString()
}
const encoded = hash instanceof Uint8Array
? textDecoder.decode(hash)
: hash

return multibase.decode('z' + encoded)
}

/**
* Decode a hash from the given multihash.
*
* @param {Buffer} buf
* @param {Uint8Array} bytes
* @returns {{code: number, name: string, length: number, digest: Buffer}} result
*/
exports.decode = function decode (buf) {
if (!(Buffer.isBuffer(buf))) {
throw new Error('multihash must be a Buffer')
exports.decode = function decode (bytes) {
if (!(bytes instanceof Uint8Array)) {
throw new Error('multihash must be a Uint8Array')
}
let buf = Buffer.isBuffer(bytes)
? bytes
: Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)

if (buf.length < 2) {
throw new Error('multihash too short. must be > 2 bytes.')
Expand Down Expand Up @@ -116,7 +125,7 @@ exports.decode = function decode (buf) {
*
* > **Note:** the length is derived from the length of the digest itself.
*
* @param {Buffer} digest
* @param {Uint8Array} digest
* @param {string|number} code
* @param {number} [length]
* @returns {Buffer}
Expand All @@ -129,8 +138,8 @@ exports.encode = function encode (digest, code, length) {
// ensure it's a hashfunction code.
const hashfn = exports.coerceCode(code)

if (!(Buffer.isBuffer(digest))) {
throw new Error('digest should be a Buffer')
if (!(digest instanceof Uint8Array)) {
throw new Error('digest should be a Uint8Array')
}

if (length == null) {
Expand All @@ -141,11 +150,13 @@ exports.encode = function encode (digest, code, length) {
throw new Error('digest length should be equal to specified length.')
}

return Buffer.concat([
Buffer.from(varint.encode(hashfn)),
Buffer.from(varint.encode(length)),
digest
])
const hash = varint.encode(hashfn)
const len = varint.encode(length)
const buffer = Buffer.alloc(hash.length + len.length + digest.length)
buffer.set(hash, 0)
buffer.set(len, hash.length)
buffer.set(digest, hash.length + len.length)
return buffer
}

/**
Expand Down Expand Up @@ -206,8 +217,8 @@ exports.isValidCode = function validCode (code) {
/**
* Check if the given buffer is a valid multihash. Throws an error if it is not valid.
*
* @param {Buffer} multihash
* @returns {undefined}
* @param {Uint8Array} multihash
* @returns {void}
* @throws {Error}
*/
function validate (multihash) {
Expand All @@ -218,12 +229,12 @@ exports.validate = validate
/**
* Returns a prefix from a valid multihash. Throws an error if it is not valid.
*
* @param {Buffer} multihash
* @returns {undefined}
* @param {Uint8Array} multihash
* @returns {Buffer}
* @throws {Error}
*/
exports.prefix = function prefix (multihash) {
validate(multihash)

return multihash.slice(0, 2)
return Buffer.from(multihash.buffer, multihash.byteOffset, 2)
}
67 changes: 42 additions & 25 deletions test/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const mh = require('../src')
const constants = require('../src/constants')
const validCases = require('./fixtures/valid')
const invalidCases = require('./fixtures/invalid')
const { TextEncoder } = require('web-encoding')

function sample (code, size, hex) {
const toHex = (i) => {
Expand All @@ -24,12 +25,28 @@ function sample (code, size, hex) {
return Buffer.from(`${toHex(code)}${toHex(size)}${hex}`, 'hex')
}

const they = (description, test) => {
it(`${description} (Buffer)`, () => test({
encodeText: Buffer.from,
encodeHex: (text) => Buffer.from(text, 'hex')
}))

const textEncoder = new TextEncoder()
it(`${description} (Uint8Array)`, () => test({
encodeText: (text) => textEncoder.encode(text),
encodeHex: (text) => {
const { buffer, byteOffset, byteLength } = Buffer.from(text, 'hex')
return new Uint8Array(buffer, byteOffset, byteLength)
}
}))
}

describe('multihash', () => {
describe('toHexString', () => {
it('valid', () => {
they('valid', ({ encodeHex }) => {
validCases.forEach((test) => {
const code = test.encoding.code
const buf = mh.encode(Buffer.from(test.hex, 'hex'), code)
const buf = mh.encode(encodeHex(test.hex), code)
expect(
mh.toHexString(buf)
).to.be.eql(
Expand All @@ -42,16 +59,16 @@ describe('multihash', () => {
expect(
() => mh.toHexString('hello world')
).to.throw(
/must be passed a buffer/
/must be passed a Uint8Array/
)
})
})

describe('fromHexString', () => {
it('valid', () => {
they('valid', ({ encodeHex }) => {
validCases.forEach((test) => {
const code = test.encoding.code
const buf = mh.encode(Buffer.from(test.hex, 'hex'), code)
const buf = mh.encode(encodeHex(test.hex), code)
expect(
mh.fromHexString(buf.toString('hex')).toString('hex')
).to.be.eql(
Expand All @@ -62,10 +79,10 @@ describe('multihash', () => {
})

describe('toB58String', () => {
it('valid', () => {
they('valid', ({ encodeHex }) => {
validCases.forEach((test) => {
const code = test.encoding.code
const buf = mh.encode(Buffer.from(test.hex, 'hex'), code)
const buf = mh.encode(encodeHex(test.hex), code)
expect(
mh.toB58String(buf)
).to.be.eql(
Expand All @@ -78,15 +95,15 @@ describe('multihash', () => {
expect(
() => mh.toB58String('hello world')
).to.throw(
/must be passed a buffer/
/must be passed a Uint8Array/
)
})
})

describe('fromB58String', () => {
it('valid', () => {
they('valid', ({ encodeHex, encodeText }) => {
const src = 'QmPfjpVaf593UQJ9a5ECvdh2x17XuJYG5Yanv5UFnH3jPE'
const expected = Buffer.from('122013bf801597d74a660453412635edd8c34271e5998f801fac5d700c6ce8d8e461', 'hex')
const expected = encodeHex('122013bf801597d74a660453412635edd8c34271e5998f801fac5d700c6ce8d8e461')

expect(
mh.fromB58String(src)
Expand All @@ -95,7 +112,7 @@ describe('multihash', () => {
)

expect(
mh.fromB58String(Buffer.from(src))
mh.fromB58String(encodeText(src))
).to.be.eql(
expected
)
Expand Down Expand Up @@ -125,20 +142,20 @@ describe('multihash', () => {
expect(
() => mh.decode('hello')
).to.throw(
/multihash must be a Buffer/
/multihash must be a Uint8Array/
)
})
})

describe('encode', () => {
it('valid', () => {
they('valid', ({ encodeHex }) => {
validCases.forEach((test) => {
const code = test.encoding.code
const name = test.encoding.name
const buf = sample(test.encoding.varint || code, test.size, test.hex)
const results = [
mh.encode(Buffer.from(test.hex, 'hex'), code),
mh.encode(Buffer.from(test.hex, 'hex'), name)
mh.encode(encodeHex(test.hex), code),
mh.encode(encodeHex(test.hex), name)
]

results.forEach((res) => {
Expand All @@ -151,7 +168,7 @@ describe('multihash', () => {
})
})

it('invalid', () => {
they('invalid', ({ encodeText }) => {
expect(
() => mh.encode()
).to.throw(
Expand All @@ -161,11 +178,11 @@ describe('multihash', () => {
expect(
() => mh.encode('hello', 0x11)
).to.throw(
/digest should be a Buffer/
/digest should be a Uint8Array/
)

expect(
() => mh.encode(Buffer.from('hello'), 0x11, 2)
() => mh.encode(encodeText('hello'), 0x11, 2)
).to.throw(
/length should be equal/
)
Expand All @@ -188,7 +205,7 @@ describe('multihash', () => {
).to.throw()
})

const longBuffer = Buffer.alloc(150, 'a')
const longBuffer = Uint8Array.from(Buffer.alloc(150, 'a'))
expect(
() => mh.validate(longBuffer)
).to.throw()
Expand Down Expand Up @@ -277,7 +294,7 @@ describe('multihash', () => {
})
})

it('invalid', () => {
they('invalid', ({ encodeText }) => {
const invalidNames = [
'sha256',
'sha9',
Expand All @@ -293,7 +310,7 @@ describe('multihash', () => {
})

expect(
() => mh.coerceCode(Buffer.from('hello'))
() => mh.coerceCode(encodeText('hello'))
).to.throw(
/should be a number/
)
Expand All @@ -306,14 +323,14 @@ describe('multihash', () => {
})
})

it('prefix', () => {
const multihash = mh.encode(Buffer.from('hey'), 0x11, 3)
they('prefix', ({ encodeText }) => {
const multihash = mh.encode(encodeText('hey'), 0x11, 3)
const prefix = mh.prefix(multihash)
expect(prefix.toString('hex')).to.eql('1103')
})

it('prefix throws on invalid multihash', () => {
const multihash = Buffer.from('definitely not valid')
they('prefix throws on invalid multihash', ({ encodeText }) => {
const multihash = encodeText('definitely not valid')

expect(() => mh.prefix(multihash)).to.throw()
})
Expand Down

0 comments on commit 4bc2a05

Please sign in to comment.