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

feat: accept Uint8Arrays in place of Buffers #61

Merged
merged 4 commits into from
Jul 24, 2020
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
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
},
"dependencies": {
"base-x": "^3.0.8",
"buffer": "^5.5.0"
"buffer": "^5.5.0",
"web-encoding": "^1.0.2"
},
"devDependencies": {
"aegir": "^25.0.0",
Expand All @@ -62,4 +63,4 @@
"theobat <[email protected]>",
"Henrique Dias <[email protected]>"
]
}
}
23 changes: 23 additions & 0 deletions src/base.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
// @ts-check
'use strict'
const { Buffer } = require('buffer')

/**
* @typedef {Object} Codec
* @property {function(Uint8Array):string} encode
* @property {function(string):Uint8Array} decode
*
* @typedef {function(string):Codec} CodecFactory
*/

class Base {
/**
* @param {string} name
* @param {string} code
* @param {CodecFactory} implementation
* @param {string} alphabet
*/
constructor (name, code, implementation, alphabet) {
this.name = name
this.code = code
Expand All @@ -10,10 +25,18 @@ class Base {
this.engine = implementation(alphabet)
}

/**
* @param {Uint8Array} buf
* @returns {string}
*/
encode (buf) {
return this.engine.encode(buf)
}

/**
* @param {string} string
* @returns {Uint8Array}
*/
decode (string) {
for (const char of string) {
if (this.alphabet && this.alphabet.indexOf(char) < 0) {
Expand Down
14 changes: 10 additions & 4 deletions src/constants.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
// @ts-check
'use strict'

const baseX = require('base-x')
const { Buffer } = require('buffer')
const Base = require('./base.js')
const rfc4648 = require('./rfc4648')
const { decodeText, encodeText } = require('./util')

const identity = () => {
return {
encode: (data) => Buffer.from(data).toString(),
decode: (string) => Buffer.from(string)
encode: decodeText,
decode: encodeText
}
}

// name, code, implementation, alphabet
/**
* @typedef {import('./base').CodecFactory} CodecFactory
*
* name, code, implementation, alphabet
* @type {Array<[string, string, CodecFactory, string]>}
*/
const constants = [
['identity', '\x00', identity, ''],
['base2', '0', rfc4648(1), '01'],
Expand Down
49 changes: 28 additions & 21 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-check
/**
* Implementation of the [multibase](https://github.com/multiformats/multibase) specification.
*
Expand All @@ -7,31 +8,37 @@

const { Buffer } = require('buffer')
const constants = require('./constants')
const { decodeText, asBuffer } = require('./util')

/** @typedef {import("./base")} Base */

/**
* Create a new buffer with the multibase varint+code.
*
* @param {string|number} nameOrCode - The multibase name or code number.
* @param {Buffer} buf - The data to be prefixed with multibase.
* @param {Uint8Array} buf - The data to be prefixed with multibase.
* @returns {Buffer}
* @throws {Error} Will throw if the encoding is not supported
*/
function multibase (nameOrCode, buf) {
if (!buf) {
throw new Error('requires an encoded buffer')
}
const enc = encoding(nameOrCode)
validEncode(enc.name, buf)
return Buffer.concat([enc.codeBuf, buf])
const { name, codeBuf } = encoding(nameOrCode)
validEncode(name, buf)

const buffer = Buffer.alloc(codeBuf.length + buf.length)
buffer.set(codeBuf, 0)
buffer.set(buf, codeBuf.length)

return buffer
}

/**
* Encode data with the specified base and add the multibase prefix.
*
* @param {string|number} nameOrCode - The multibase name or code number.
* @param {Buffer} buf - The data to be encoded.
* @param {Uint8Array} buf - The data to be encoded.
* @returns {Buffer}
* @throws {Error} Will throw if the encoding is not supported
*
Expand All @@ -43,17 +50,17 @@ function encode (nameOrCode, buf) {
}

/**
* Takes a buffer or string encoded with multibase header, decodes it and
* Takes a Uint8Array or string encoded with multibase header, decodes it and
* returns the decoded buffer
*
* @param {Buffer|string} data
* @param {Uint8Array|string} data
* @returns {Buffer}
* @throws {Error} Will throw if the encoding is not supported
*
*/
function decode (data) {
if (Buffer.isBuffer(data)) {
data = data.toString()
if (ArrayBuffer.isView(data)) {
data = decodeText(data)
}
const prefix = data[0]

Expand All @@ -62,18 +69,18 @@ function decode (data) {
data = data.toLowerCase()
}
const enc = encoding(data[0])
return Buffer.from(enc.decode(data.substring(1)))
return asBuffer(enc.decode(data.substring(1)))
}

/**
* Is the given data multibase encoded?
*
* @param {Buffer|string} data
* @returns {boolean}
* @param {Uint8Array|string} data
* @returns {false|string}
*/
function isEncoded (data) {
if (Buffer.isBuffer(data)) {
data = data.toString()
if (data instanceof Uint8Array) {
data = decodeText(data)
}

// Ensure bufOrString is a string
Expand All @@ -93,19 +100,19 @@ function isEncoded (data) {
* Validate encoded data
*
* @param {string} name
* @param {Buffer} buf
* @returns {undefined}
* @param {Uint8Array} buf
* @returns {void}
* @throws {Error} Will throw if the encoding is not supported
*/
function validEncode (name, buf) {
const enc = encoding(name)
enc.decode(buf.toString())
enc.decode(decodeText(buf))
}

/**
* Get the encoding by name or code
*
* @param {string} nameOrCode
* @param {string|number} nameOrCode
* @returns {Base}
* @throws {Error} Will throw if the encoding is not supported
*/
Expand All @@ -122,13 +129,13 @@ function encoding (nameOrCode) {
/**
* Get encoding from data
*
* @param {string|Buffer} data
* @param {string|Uint8Array} data
* @returns {Base}
* @throws {Error} Will throw if the encoding is not supported
*/
function encodingFromData (data) {
if (Buffer.isBuffer(data)) {
data = data.toString()
if (data instanceof Uint8Array) {
data = decodeText(data)
}

return encoding(data[0])
Expand Down
27 changes: 27 additions & 0 deletions src/rfc4648.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
// @ts-check
'use strict'

/** @typedef {import('./base').CodecFactory} CodecFactory */

/**
* @param {string} string
* @param {string} alphabet
* @param {number} bitsPerChar
* @returns {Uint8Array}
*/
const decode = (string, alphabet, bitsPerChar) => {
// Build the character lookup table:
const codes = {}
Expand Down Expand Up @@ -46,6 +55,12 @@ const decode = (string, alphabet, bitsPerChar) => {
return out
}

/**
* @param {Uint8Array} data
* @param {string} alphabet
* @param {number} bitsPerChar
* @returns {string}
*/
const encode = (data, alphabet, bitsPerChar) => {
const pad = alphabet[alphabet.length - 1] === '='
const mask = (1 << bitsPerChar) - 1
Expand Down Expand Up @@ -80,11 +95,23 @@ const encode = (data, alphabet, bitsPerChar) => {
return out
}

/**
* @param {number} bitsPerChar
* @returns {CodecFactory}
*/
module.exports = (bitsPerChar) => (alphabet) => {
return {
/**
* @param {Uint8Array} input
* @returns {string}
*/
encode (input) {
return encode(input, alphabet, bitsPerChar)
},
/**
* @param {string} input
* @returns {Uint8Array}
*/
decode (input) {
return decode(input, alphabet, bitsPerChar)
}
Expand Down
28 changes: 28 additions & 0 deletions src/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// @ts-check
'use strict'

const { Buffer } = require('buffer')
const { TextEncoder, TextDecoder } = require('web-encoding')

const textDecoder = new TextDecoder()
/**
* @param {ArrayBufferView|ArrayBuffer} bytes
* @returns {string}
*/
const decodeText = (bytes) => textDecoder.decode(bytes)

const textEncoder = new TextEncoder()
/**
* @param {string} text
* @returns {Uint8Array}
*/
const encodeText = (text) => textEncoder.encode(text)

/**
* @param {ArrayBufferView} bytes
* @returns {Buffer}
*/
const asBuffer = ({ buffer, byteLength, byteOffset }) =>
Buffer.from(buffer, byteOffset, byteLength)

module.exports = { decodeText, encodeText, asBuffer }
Loading