From cf9c5c6c0d86f523ca9f58e7a1c855352b5e6c1b Mon Sep 17 00:00:00 2001 From: achingbrain Date: Thu, 30 Jul 2020 11:34:57 +0100 Subject: [PATCH 1/8] feat: add utility methods for dealing with Uint8Arrays On my weird side-quest to replace node `Buffer`s with `Uint8Array`s I find myself reimplementing the same functions over and over again in each repo. In the interests of reuse I've made utility functions for them here. --- src/uint8array/concat.js | 19 +++++++++++ src/uint8array/equals.js | 21 +++++++++++++ src/uint8array/from-string.js | 10 ++++++ src/uint8array/sort.js | 25 +++++++++++++++ test/uint8array/concat.spec.js | 23 ++++++++++++++ test/uint8array/equals.spec.js | 28 +++++++++++++++++ test/uint8array/from-string.spec.js | 15 +++++++++ test/uint8array/sort.spec.js | 49 +++++++++++++++++++++++++++++ 8 files changed, 190 insertions(+) create mode 100644 src/uint8array/concat.js create mode 100644 src/uint8array/equals.js create mode 100644 src/uint8array/from-string.js create mode 100644 src/uint8array/sort.js create mode 100644 test/uint8array/concat.spec.js create mode 100644 test/uint8array/equals.spec.js create mode 100644 test/uint8array/from-string.spec.js create mode 100644 test/uint8array/sort.spec.js diff --git a/src/uint8array/concat.js b/src/uint8array/concat.js new file mode 100644 index 0000000..419bf6c --- /dev/null +++ b/src/uint8array/concat.js @@ -0,0 +1,19 @@ +'use strict' + +function concat (arrs, length) { + if (!length) { + length = arrs.reduce((acc, curr) => acc + curr.byteLength, 0) + } + + const output = new Uint8Array(length) + let offset = 0 + + arrs.forEach(arr => { + output.set(arr, offset) + offset += arr.byteLength + }) + + return output +} + +module.exports = concat diff --git a/src/uint8array/equals.js b/src/uint8array/equals.js new file mode 100644 index 0000000..008e5a7 --- /dev/null +++ b/src/uint8array/equals.js @@ -0,0 +1,21 @@ +'use strict' + +function equals (a, b) { + if (a === b) { + return true + } + + if (a.byteLength !== b.byteLength) { + return false + } + + for (let i = 0; i < a.byteLength; i++) { + if (a[i] !== b[i]) { + return false + } + } + + return true +} + +module.exports = equals diff --git a/src/uint8array/from-string.js b/src/uint8array/from-string.js new file mode 100644 index 0000000..7bcaaed --- /dev/null +++ b/src/uint8array/from-string.js @@ -0,0 +1,10 @@ +'use strict' + +const TextEncoder = require('../text-encoder') +const utf8Encoder = new TextEncoder('utf8') + +function fromString (str) { + return utf8Encoder.encode(str) +} + +module.exports = fromString diff --git a/src/uint8array/sort.js b/src/uint8array/sort.js new file mode 100644 index 0000000..909494c --- /dev/null +++ b/src/uint8array/sort.js @@ -0,0 +1,25 @@ +'use strict' + +function sort (a, b) { + for (let i = 0; i < a.byteLength; i++) { + if (a[i] < b[i]) { + return -1 + } + + if (a[i] > b[i]) { + return 1 + } + } + + if (a.byteLength > b.byteLength) { + return 1 + } + + if (a.byteLength < b.byteLength) { + return -1 + } + + return 0 +} + +module.exports = sort diff --git a/test/uint8array/concat.spec.js b/test/uint8array/concat.spec.js new file mode 100644 index 0000000..02bd374 --- /dev/null +++ b/test/uint8array/concat.spec.js @@ -0,0 +1,23 @@ +'use strict' + +/* eslint-env mocha */ +const { expect } = require('aegir/utils/chai') +const concat = require('../../src/uint8array/concat') + +describe('Uint8Array concat', () => { + it('concats two Uint8Arrays', () => { + const a = Uint8Array.from([0, 1, 2, 3]) + const b = Uint8Array.from([4, 5, 6, 7]) + const c = Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7]) + + expect(concat([a, b])).to.deep.equal(c) + }) + + it('concats two Uint8Arrays with a length', () => { + const a = Uint8Array.from([0, 1, 2, 3]) + const b = Uint8Array.from([4, 5, 6, 7]) + const c = Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7]) + + expect(concat([a, b], 8)).to.deep.equal(c) + }) +}) diff --git a/test/uint8array/equals.spec.js b/test/uint8array/equals.spec.js new file mode 100644 index 0000000..fe66f1c --- /dev/null +++ b/test/uint8array/equals.spec.js @@ -0,0 +1,28 @@ +'use strict' + +/* eslint-env mocha */ +const { expect } = require('aegir/utils/chai') +const equals = require('../../src/uint8array/equals') + +describe('Uint8Array equals', () => { + it('finds two Uint8Arrays equal', () => { + const a = Uint8Array.from([0, 1, 2, 3]) + const b = Uint8Array.from([0, 1, 2, 3]) + + expect(equals(a, b)).to.be.true() + }) + + it('finds two Uint8Arrays not equal', () => { + const a = Uint8Array.from([0, 1, 2, 3]) + const b = Uint8Array.from([0, 1, 2, 4]) + + expect(equals(a, b)).to.be.false() + }) + + it('finds two Uint8Arrays with different lengths not equal', () => { + const a = Uint8Array.from([0, 1, 2, 3]) + const b = Uint8Array.from([0, 1, 2, 3, 4]) + + expect(equals(a, b)).to.be.false() + }) +}) diff --git a/test/uint8array/from-string.spec.js b/test/uint8array/from-string.spec.js new file mode 100644 index 0000000..19b8719 --- /dev/null +++ b/test/uint8array/from-string.spec.js @@ -0,0 +1,15 @@ +'use strict' + +/* eslint-env mocha */ +const { expect } = require('aegir/utils/chai') +const fromString = require('../../src/uint8array/from-string') +const TextEncoder = require('../../src/text-encoder') + +describe('Uint8Array fromString', () => { + it('creates a Uint8Array from a string', () => { + const str = 'hello world' + const arr = new TextEncoder('utf8').encode(str) + + expect(fromString(str)).to.deep.equal(arr) + }) +}) diff --git a/test/uint8array/sort.spec.js b/test/uint8array/sort.spec.js new file mode 100644 index 0000000..2dce8c9 --- /dev/null +++ b/test/uint8array/sort.spec.js @@ -0,0 +1,49 @@ +'use strict' + +/* eslint-env mocha */ +const { expect } = require('aegir/utils/chai') +const sort = require('../../src/uint8array/sort') + +describe('Uint8Array sort', () => { + it('is stable', () => { + const a = Uint8Array.from([0, 1, 2, 3]) + const b = Uint8Array.from([0, 1, 2, 3]) + + expect([a, b].sort(sort)).to.deep.equal([ + a, + b + ]) + expect([b, a].sort(sort)).to.deep.equal([ + b, + a + ]) + }) + + it('sorts two Uint8Arrays', () => { + const a = Uint8Array.from([0, 1, 2, 4]) + const b = Uint8Array.from([0, 1, 2, 3]) + + expect([a, b].sort(sort)).to.deep.equal([ + b, + a + ]) + expect([b, a].sort(sort)).to.deep.equal([ + b, + a + ]) + }) + + it('sorts two Uint8Arrays with different lengths', () => { + const a = Uint8Array.from([0, 1, 2, 3, 4]) + const b = Uint8Array.from([0, 1, 2, 3]) + + expect([a, b].sort(sort)).to.deep.equal([ + b, + a + ]) + expect([b, a].sort(sort)).to.deep.equal([ + b, + a + ]) + }) +}) From 837bbb1d42471b33499bbdf64966729c5fa5d81d Mon Sep 17 00:00:00 2001 From: achingbrain Date: Thu, 30 Jul 2020 13:11:33 +0100 Subject: [PATCH 2/8] chore: add from hex string and jsdoc comments --- src/uint8array/concat.js | 7 ++++++ src/uint8array/equals.js | 7 ++++++ src/uint8array/from-hex-string.js | 29 +++++++++++++++++++++++++ src/uint8array/from-string.js | 6 +++++ src/uint8array/sort.js | 7 ++++++ test/uint8array/from-hex-string.spec.js | 26 ++++++++++++++++++++++ 6 files changed, 82 insertions(+) create mode 100644 src/uint8array/from-hex-string.js create mode 100644 test/uint8array/from-hex-string.spec.js diff --git a/src/uint8array/concat.js b/src/uint8array/concat.js index 419bf6c..d2df7c0 100644 --- a/src/uint8array/concat.js +++ b/src/uint8array/concat.js @@ -1,5 +1,12 @@ 'use strict' +/** + * Returns a new Uint8Array created by concatenating the passed Arrays + * + * @param {Array} arrs + * @param {Number} length + * @returns {Uint8Array} + */ function concat (arrs, length) { if (!length) { length = arrs.reduce((acc, curr) => acc + curr.byteLength, 0) diff --git a/src/uint8array/equals.js b/src/uint8array/equals.js index 008e5a7..efe82f5 100644 --- a/src/uint8array/equals.js +++ b/src/uint8array/equals.js @@ -1,5 +1,12 @@ 'use strict' +/** + * Returns true if the two passed Uint8Arrays have the same content + * + * @param {Uint8Array} a + * @param {Uint8Array} b + * @returns {boolean} + */ function equals (a, b) { if (a === b) { return true diff --git a/src/uint8array/from-hex-string.js b/src/uint8array/from-hex-string.js new file mode 100644 index 0000000..b8e0965 --- /dev/null +++ b/src/uint8array/from-hex-string.js @@ -0,0 +1,29 @@ +'use strict' + +/** + * Turn a string of hex characters into a Uint8Array + * + * @param {String} str A hex string with two characters per digit + * @returns {Uint8Array} + */ +function fromHexString (str) { + if (str.length % 2 !== 0) { + throw new Error('Invalid hex string') + } + + str = str.toLowerCase() + + if (!str.match(/^[0-9a-f]+$/)) { + throw new Error('Invalid hex string') + } + + const parts = [] + + for (let i = 0; i < str.length; i += 2) { + parts.push(parseInt(`${str[i]}${str[i + 1]}`, 16)) + } + + return Uint8Array.from(parts) +} + +module.exports = fromHexString diff --git a/src/uint8array/from-string.js b/src/uint8array/from-string.js index 7bcaaed..a82a7f2 100644 --- a/src/uint8array/from-string.js +++ b/src/uint8array/from-string.js @@ -3,6 +3,12 @@ const TextEncoder = require('../text-encoder') const utf8Encoder = new TextEncoder('utf8') +/** + * Returns a Uint8Array created from the passed utf8 encoded string + * + * @param {String} str + * @returns {Uint8Array} + */ function fromString (str) { return utf8Encoder.encode(str) } diff --git a/src/uint8array/sort.js b/src/uint8array/sort.js index 909494c..c1ea501 100644 --- a/src/uint8array/sort.js +++ b/src/uint8array/sort.js @@ -1,5 +1,12 @@ 'use strict' +/** + * Can be used with Array.sort to sort and array with Uint8Array entries + * + * @param {Uint8Array} a + * @param {Uint8Array} b + * @returns {Number} + */ function sort (a, b) { for (let i = 0; i < a.byteLength; i++) { if (a[i] < b[i]) { diff --git a/test/uint8array/from-hex-string.spec.js b/test/uint8array/from-hex-string.spec.js new file mode 100644 index 0000000..4436eed --- /dev/null +++ b/test/uint8array/from-hex-string.spec.js @@ -0,0 +1,26 @@ +'use strict' + +/* eslint-env mocha */ +const { expect } = require('aegir/utils/chai') +const fromHexString = require('../../src/uint8array/from-hex-string') + +describe('Uint8Array fromHexString', () => { + it('creates a Uint8Array from a hex string', () => { + const str = '00010203aabbcc' + const arr = Uint8Array.from([0, 1, 2, 3, 170, 187, 204]) + + expect(fromHexString(str)).to.deep.equal(arr) + }) + + it('rejects an invalid hex string', () => { + const str = '00010203aabbc' + + expect(() => fromHexString(str)).to.throw(/Invalid hex string/) + }) + + it('rejects a hex string with invalid characters', () => { + const str = '00010203aabbci' + + expect(() => fromHexString(str)).to.throw(/Invalid hex string/) + }) +}) From 659b76c3e1cdf610389f764b2d4b91242a951275 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Thu, 30 Jul 2020 13:59:00 +0100 Subject: [PATCH 3/8] chore: rename for consistency --- src/{uint8array => uint8arrays}/concat.js | 0 src/{uint8array => uint8arrays}/equals.js | 0 src/{uint8array => uint8arrays}/from-hex-string.js | 0 src/{uint8array => uint8arrays}/from-string.js | 0 src/{uint8array => uint8arrays}/sort.js | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename src/{uint8array => uint8arrays}/concat.js (100%) rename src/{uint8array => uint8arrays}/equals.js (100%) rename src/{uint8array => uint8arrays}/from-hex-string.js (100%) rename src/{uint8array => uint8arrays}/from-string.js (100%) rename src/{uint8array => uint8arrays}/sort.js (100%) diff --git a/src/uint8array/concat.js b/src/uint8arrays/concat.js similarity index 100% rename from src/uint8array/concat.js rename to src/uint8arrays/concat.js diff --git a/src/uint8array/equals.js b/src/uint8arrays/equals.js similarity index 100% rename from src/uint8array/equals.js rename to src/uint8arrays/equals.js diff --git a/src/uint8array/from-hex-string.js b/src/uint8arrays/from-hex-string.js similarity index 100% rename from src/uint8array/from-hex-string.js rename to src/uint8arrays/from-hex-string.js diff --git a/src/uint8array/from-string.js b/src/uint8arrays/from-string.js similarity index 100% rename from src/uint8array/from-string.js rename to src/uint8arrays/from-string.js diff --git a/src/uint8array/sort.js b/src/uint8arrays/sort.js similarity index 100% rename from src/uint8array/sort.js rename to src/uint8arrays/sort.js From 079f80f23844708ea2862e6651858376667de53f Mon Sep 17 00:00:00 2001 From: achingbrain Date: Thu, 30 Jul 2020 13:59:53 +0100 Subject: [PATCH 4/8] chore: unsaved files --- test/uint8array/concat.spec.js | 2 +- test/uint8array/equals.spec.js | 2 +- test/uint8array/from-hex-string.spec.js | 2 +- test/uint8array/from-string.spec.js | 2 +- test/uint8array/sort.spec.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/uint8array/concat.spec.js b/test/uint8array/concat.spec.js index 02bd374..65e27f6 100644 --- a/test/uint8array/concat.spec.js +++ b/test/uint8array/concat.spec.js @@ -2,7 +2,7 @@ /* eslint-env mocha */ const { expect } = require('aegir/utils/chai') -const concat = require('../../src/uint8array/concat') +const concat = require('../../src/uint8arrays/concat') describe('Uint8Array concat', () => { it('concats two Uint8Arrays', () => { diff --git a/test/uint8array/equals.spec.js b/test/uint8array/equals.spec.js index fe66f1c..1466de4 100644 --- a/test/uint8array/equals.spec.js +++ b/test/uint8array/equals.spec.js @@ -2,7 +2,7 @@ /* eslint-env mocha */ const { expect } = require('aegir/utils/chai') -const equals = require('../../src/uint8array/equals') +const equals = require('../../src/uint8arrays/equals') describe('Uint8Array equals', () => { it('finds two Uint8Arrays equal', () => { diff --git a/test/uint8array/from-hex-string.spec.js b/test/uint8array/from-hex-string.spec.js index 4436eed..d7e70f4 100644 --- a/test/uint8array/from-hex-string.spec.js +++ b/test/uint8array/from-hex-string.spec.js @@ -2,7 +2,7 @@ /* eslint-env mocha */ const { expect } = require('aegir/utils/chai') -const fromHexString = require('../../src/uint8array/from-hex-string') +const fromHexString = require('../../src/uint8arrays/from-hex-string') describe('Uint8Array fromHexString', () => { it('creates a Uint8Array from a hex string', () => { diff --git a/test/uint8array/from-string.spec.js b/test/uint8array/from-string.spec.js index 19b8719..468be4a 100644 --- a/test/uint8array/from-string.spec.js +++ b/test/uint8array/from-string.spec.js @@ -2,7 +2,7 @@ /* eslint-env mocha */ const { expect } = require('aegir/utils/chai') -const fromString = require('../../src/uint8array/from-string') +const fromString = require('../../src/uint8arrays/from-string') const TextEncoder = require('../../src/text-encoder') describe('Uint8Array fromString', () => { diff --git a/test/uint8array/sort.spec.js b/test/uint8array/sort.spec.js index 2dce8c9..01d6552 100644 --- a/test/uint8array/sort.spec.js +++ b/test/uint8array/sort.spec.js @@ -2,7 +2,7 @@ /* eslint-env mocha */ const { expect } = require('aegir/utils/chai') -const sort = require('../../src/uint8array/sort') +const sort = require('../../src/uint8arrays/sort') describe('Uint8Array sort', () => { it('is stable', () => { From 371b6f4c66de94846fe5514745ad122c8ac559da Mon Sep 17 00:00:00 2001 From: achingbrain Date: Thu, 30 Jul 2020 14:16:25 +0100 Subject: [PATCH 5/8] chore: add to hex string method --- src/uint8arrays/to-hex-string.js | 13 +++++++++++++ test/uint8array/to-hex-string.spec.js | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 src/uint8arrays/to-hex-string.js create mode 100644 test/uint8array/to-hex-string.spec.js diff --git a/src/uint8arrays/to-hex-string.js b/src/uint8arrays/to-hex-string.js new file mode 100644 index 0000000..112aaaa --- /dev/null +++ b/src/uint8arrays/to-hex-string.js @@ -0,0 +1,13 @@ +'use strict' + +/** + * Turn a Uint8Array into a string of hex characters + * + * @param {Uint8Array} buf A Uint8Array to turn into a string + * @returns {String} + */ +function toHexString (buf) { + return Array.from(buf).map(i => i.toString(16).padStart(2, '0')).join('') +} + +module.exports = toHexString diff --git a/test/uint8array/to-hex-string.spec.js b/test/uint8array/to-hex-string.spec.js new file mode 100644 index 0000000..bcb52d1 --- /dev/null +++ b/test/uint8array/to-hex-string.spec.js @@ -0,0 +1,21 @@ +'use strict' + +/* eslint-env mocha */ +const { expect } = require('aegir/utils/chai') +const toHexString = require('../../src/uint8arrays/to-hex-string') + +describe('Uint8Array toHexString', () => { + it('creates a hex string from a Uint8Array', () => { + const str = '00010203aabbcc' + const arr = Uint8Array.from([0, 1, 2, 3, 170, 187, 204]) + + expect(toHexString(arr)).to.deep.equal(str) + }) + + it('creates an empty hex string from a Uint8Array', () => { + const str = '' + const arr = new Uint8Array() + + expect(toHexString(arr)).to.deep.equal(str) + }) +}) From fb83a286ea5b3e244b08fa661e8a367b5f7b9945 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Thu, 30 Jul 2020 14:40:37 +0100 Subject: [PATCH 6/8] chore: add encoding to to/from string --- package.json | 1 + src/uint8arrays/from-hex-string.js | 29 ------------------------- src/uint8arrays/from-string.js | 24 +++++++++++++------- src/uint8arrays/to-hex-string.js | 13 ----------- src/uint8arrays/to-string.js | 15 +++++++++++++ test/uint8array/from-hex-string.spec.js | 26 ---------------------- test/uint8array/from-string.spec.js | 14 ++++++++++++ test/uint8array/to-hex-string.spec.js | 21 ------------------ test/uint8array/to-string.spec.js | 29 +++++++++++++++++++++++++ 9 files changed, 75 insertions(+), 97 deletions(-) delete mode 100644 src/uint8arrays/from-hex-string.js delete mode 100644 src/uint8arrays/to-hex-string.js create mode 100644 src/uint8arrays/to-string.js delete mode 100644 test/uint8array/from-hex-string.spec.js delete mode 100644 test/uint8array/to-hex-string.spec.js create mode 100644 test/uint8array/to-string.spec.js diff --git a/package.json b/package.json index 8b37399..4adb1da 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "iso-url": "^0.4.7", "it-glob": "0.0.8", "merge-options": "^2.0.0", + "multibase": "^2.0.0", "nanoid": "^3.1.3", "node-fetch": "^2.6.0", "stream-to-it": "^0.2.0" diff --git a/src/uint8arrays/from-hex-string.js b/src/uint8arrays/from-hex-string.js deleted file mode 100644 index b8e0965..0000000 --- a/src/uint8arrays/from-hex-string.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict' - -/** - * Turn a string of hex characters into a Uint8Array - * - * @param {String} str A hex string with two characters per digit - * @returns {Uint8Array} - */ -function fromHexString (str) { - if (str.length % 2 !== 0) { - throw new Error('Invalid hex string') - } - - str = str.toLowerCase() - - if (!str.match(/^[0-9a-f]+$/)) { - throw new Error('Invalid hex string') - } - - const parts = [] - - for (let i = 0; i < str.length; i += 2) { - parts.push(parseInt(`${str[i]}${str[i + 1]}`, 16)) - } - - return Uint8Array.from(parts) -} - -module.exports = fromHexString diff --git a/src/uint8arrays/from-string.js b/src/uint8arrays/from-string.js index a82a7f2..b0da906 100644 --- a/src/uint8arrays/from-string.js +++ b/src/uint8arrays/from-string.js @@ -1,16 +1,24 @@ 'use strict' +const multibase = require('multibase') +const { names } = require('multibase/src/constants') const TextEncoder = require('../text-encoder') const utf8Encoder = new TextEncoder('utf8') -/** - * Returns a Uint8Array created from the passed utf8 encoded string - * - * @param {String} str - * @returns {Uint8Array} - */ -function fromString (str) { - return utf8Encoder.encode(str) +function fromString (string, encoding = 'utf8') { + if (encoding !== 'utf8') { + const base = names[encoding] + + if (!base) { + throw new Error('Unknown base') + } + + string = `${base.code}${string}` + + return Uint8Array.from(multibase.decode(string)) + } + + return utf8Encoder.encode(string) } module.exports = fromString diff --git a/src/uint8arrays/to-hex-string.js b/src/uint8arrays/to-hex-string.js deleted file mode 100644 index 112aaaa..0000000 --- a/src/uint8arrays/to-hex-string.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict' - -/** - * Turn a Uint8Array into a string of hex characters - * - * @param {Uint8Array} buf A Uint8Array to turn into a string - * @returns {String} - */ -function toHexString (buf) { - return Array.from(buf).map(i => i.toString(16).padStart(2, '0')).join('') -} - -module.exports = toHexString diff --git a/src/uint8arrays/to-string.js b/src/uint8arrays/to-string.js new file mode 100644 index 0000000..2326754 --- /dev/null +++ b/src/uint8arrays/to-string.js @@ -0,0 +1,15 @@ +'use strict' + +const multibase = require('multibase') +const TextDecoder = require('../text-decoder') +const utf8Decoder = new TextDecoder('utf8') + +function toString (buf, encoding = 'utf8') { + if (encoding !== 'utf8') { + buf = multibase.encode(encoding, buf).subarray(1) + } + + return utf8Decoder.decode(buf) +} + +module.exports = toString diff --git a/test/uint8array/from-hex-string.spec.js b/test/uint8array/from-hex-string.spec.js deleted file mode 100644 index d7e70f4..0000000 --- a/test/uint8array/from-hex-string.spec.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict' - -/* eslint-env mocha */ -const { expect } = require('aegir/utils/chai') -const fromHexString = require('../../src/uint8arrays/from-hex-string') - -describe('Uint8Array fromHexString', () => { - it('creates a Uint8Array from a hex string', () => { - const str = '00010203aabbcc' - const arr = Uint8Array.from([0, 1, 2, 3, 170, 187, 204]) - - expect(fromHexString(str)).to.deep.equal(arr) - }) - - it('rejects an invalid hex string', () => { - const str = '00010203aabbc' - - expect(() => fromHexString(str)).to.throw(/Invalid hex string/) - }) - - it('rejects a hex string with invalid characters', () => { - const str = '00010203aabbci' - - expect(() => fromHexString(str)).to.throw(/Invalid hex string/) - }) -}) diff --git a/test/uint8array/from-string.spec.js b/test/uint8array/from-string.spec.js index 468be4a..ca833a5 100644 --- a/test/uint8array/from-string.spec.js +++ b/test/uint8array/from-string.spec.js @@ -12,4 +12,18 @@ describe('Uint8Array fromString', () => { expect(fromString(str)).to.deep.equal(arr) }) + + it('creates a Uint8Array from a base16 string', () => { + const str = '00010203aabbcc' + const arr = Uint8Array.from([0, 1, 2, 3, 170, 187, 204]) + + expect(fromString(str, 'base16')).to.deep.equal(arr) + }) + + it('creates a Uint8Array from a base64 string', () => { + const str = 'AAECA6q7zA' + const arr = Uint8Array.from([0, 1, 2, 3, 170, 187, 204]) + + expect(fromString(str, 'base64')).to.deep.equal(arr) + }) }) diff --git a/test/uint8array/to-hex-string.spec.js b/test/uint8array/to-hex-string.spec.js deleted file mode 100644 index bcb52d1..0000000 --- a/test/uint8array/to-hex-string.spec.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict' - -/* eslint-env mocha */ -const { expect } = require('aegir/utils/chai') -const toHexString = require('../../src/uint8arrays/to-hex-string') - -describe('Uint8Array toHexString', () => { - it('creates a hex string from a Uint8Array', () => { - const str = '00010203aabbcc' - const arr = Uint8Array.from([0, 1, 2, 3, 170, 187, 204]) - - expect(toHexString(arr)).to.deep.equal(str) - }) - - it('creates an empty hex string from a Uint8Array', () => { - const str = '' - const arr = new Uint8Array() - - expect(toHexString(arr)).to.deep.equal(str) - }) -}) diff --git a/test/uint8array/to-string.spec.js b/test/uint8array/to-string.spec.js new file mode 100644 index 0000000..7b87ab7 --- /dev/null +++ b/test/uint8array/to-string.spec.js @@ -0,0 +1,29 @@ +'use strict' + +/* eslint-env mocha */ +const { expect } = require('aegir/utils/chai') +const toString = require('../../src/uint8arrays/to-string') +const TextEncoder = require('../../src/text-encoder') + +describe('Uint8Array toString', () => { + it('creates a String from a Uint8Array', () => { + const str = 'hello world' + const arr = new TextEncoder('utf8').encode(str) + + expect(toString(arr)).to.deep.equal(str) + }) + + it('creates a hex string from a Uint8Array', () => { + const str = '00010203aabbcc' + const arr = Uint8Array.from([0, 1, 2, 3, 170, 187, 204]) + + expect(toString(arr, 'base16')).to.deep.equal(str) + }) + + it('creates a base64 string from a Uint8Array', () => { + const str = 'AAECA6q7zA' + const arr = Uint8Array.from([0, 1, 2, 3, 170, 187, 204]) + + expect(toString(arr, 'base64')).to.deep.equal(str) + }) +}) From bb5033ea94578708db90dc76724694e33754dcbe Mon Sep 17 00:00:00 2001 From: achingbrain Date: Thu, 30 Jul 2020 15:56:21 +0100 Subject: [PATCH 7/8] chore: rename sort to compare --- src/uint8arrays/{sort.js => compare.js} | 4 +-- src/uint8arrays/concat.js | 19 ++++++++++-- .../{sort.spec.js => compare.spec.js} | 20 ++++++------- test/uint8array/concat.spec.js | 30 +++++++++++++++++++ 4 files changed, 59 insertions(+), 14 deletions(-) rename src/uint8arrays/{sort.js => compare.js} (89%) rename test/uint8array/{sort.spec.js => compare.spec.js} (52%) diff --git a/src/uint8arrays/sort.js b/src/uint8arrays/compare.js similarity index 89% rename from src/uint8arrays/sort.js rename to src/uint8arrays/compare.js index c1ea501..f0be7b1 100644 --- a/src/uint8arrays/sort.js +++ b/src/uint8arrays/compare.js @@ -7,7 +7,7 @@ * @param {Uint8Array} b * @returns {Number} */ -function sort (a, b) { +function compare (a, b) { for (let i = 0; i < a.byteLength; i++) { if (a[i] < b[i]) { return -1 @@ -29,4 +29,4 @@ function sort (a, b) { return 0 } -module.exports = sort +module.exports = compare diff --git a/src/uint8arrays/concat.js b/src/uint8arrays/concat.js index d2df7c0..219b271 100644 --- a/src/uint8arrays/concat.js +++ b/src/uint8arrays/concat.js @@ -9,7 +9,15 @@ */ function concat (arrs, length) { if (!length) { - length = arrs.reduce((acc, curr) => acc + curr.byteLength, 0) + length = arrs.reduce((acc, curr) => { + if (ArrayBuffer.isView(curr)) { + return acc + curr.byteLength + } else if (Array.isArray(curr)) { + return acc + curr.length + } + + throw new Error('Invalid input passed to concat, should be an Array or ArrayBuffer view') + }, 0) } const output = new Uint8Array(length) @@ -17,7 +25,14 @@ function concat (arrs, length) { arrs.forEach(arr => { output.set(arr, offset) - offset += arr.byteLength + + if (ArrayBuffer.isView(arr)) { + offset += arr.byteLength + } else if (Array.isArray(arr)) { + offset += arr.length + } else { + throw new Error('Invalid input passed to concat, should be an Array or ArrayBuffer view') + } }) return output diff --git a/test/uint8array/sort.spec.js b/test/uint8array/compare.spec.js similarity index 52% rename from test/uint8array/sort.spec.js rename to test/uint8array/compare.spec.js index 01d6552..4155a0e 100644 --- a/test/uint8array/sort.spec.js +++ b/test/uint8array/compare.spec.js @@ -2,46 +2,46 @@ /* eslint-env mocha */ const { expect } = require('aegir/utils/chai') -const sort = require('../../src/uint8arrays/sort') +const compare = require('../../src/uint8arrays/compare') -describe('Uint8Array sort', () => { +describe('Uint8Array compare', () => { it('is stable', () => { const a = Uint8Array.from([0, 1, 2, 3]) const b = Uint8Array.from([0, 1, 2, 3]) - expect([a, b].sort(sort)).to.deep.equal([ + expect([a, b].sort(compare)).to.deep.equal([ a, b ]) - expect([b, a].sort(sort)).to.deep.equal([ + expect([b, a].sort(compare)).to.deep.equal([ b, a ]) }) - it('sorts two Uint8Arrays', () => { + it('compares two Uint8Arrays', () => { const a = Uint8Array.from([0, 1, 2, 4]) const b = Uint8Array.from([0, 1, 2, 3]) - expect([a, b].sort(sort)).to.deep.equal([ + expect([a, b].sort(compare)).to.deep.equal([ b, a ]) - expect([b, a].sort(sort)).to.deep.equal([ + expect([b, a].sort(compare)).to.deep.equal([ b, a ]) }) - it('sorts two Uint8Arrays with different lengths', () => { + it('compares two Uint8Arrays with different lengths', () => { const a = Uint8Array.from([0, 1, 2, 3, 4]) const b = Uint8Array.from([0, 1, 2, 3]) - expect([a, b].sort(sort)).to.deep.equal([ + expect([a, b].sort(compare)).to.deep.equal([ b, a ]) - expect([b, a].sort(sort)).to.deep.equal([ + expect([b, a].sort(compare)).to.deep.equal([ b, a ]) diff --git a/test/uint8array/concat.spec.js b/test/uint8array/concat.spec.js index 65e27f6..302dc28 100644 --- a/test/uint8array/concat.spec.js +++ b/test/uint8array/concat.spec.js @@ -20,4 +20,34 @@ describe('Uint8Array concat', () => { expect(concat([a, b], 8)).to.deep.equal(c) }) + + it('concats mixed Uint8Arrays and Arrays', () => { + const a = Uint8Array.from([0, 1, 2, 3]) + const b = [4, 5, 6, 7] + const c = Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7]) + + expect(concat([a, b])).to.deep.equal(c) + }) + + it('concats mixed Uint8Arrays and Arrays with a length', () => { + const a = Uint8Array.from([0, 1, 2, 3]) + const b = [4, 5, 6, 7] + const c = Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7]) + + expect(concat([a, b], 8)).to.deep.equal(c) + }) + + it('throws when passed non ArrayBuffer views/Arrays', () => { + const a = Uint8Array.from([0, 1, 2, 3]) + const b = 'Hello world' + + expect(() => concat([a, b])).to.throw(/Invalid input/) + }) + + it('throws when passed non ArrayBuffer views/Arrays with a length', () => { + const a = Uint8Array.from([0, 1, 2, 3]) + const b = 'Hello world' + + expect(() => concat([a, b], 100)).to.throw(/Invalid input/) + }) }) From 40309270c29a85736e4b4a3eeeb1eb46d9a06467 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Fri, 31 Jul 2020 11:12:13 +0100 Subject: [PATCH 8/8] chore: address PR comments --- src/uint8arrays/concat.js | 28 ++++++++++++++++------------ src/uint8arrays/from-string.js | 28 ++++++++++++++++++---------- src/uint8arrays/to-string.js | 12 +++++++++++- 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/uint8arrays/concat.js b/src/uint8arrays/concat.js index 219b271..8762b76 100644 --- a/src/uint8arrays/concat.js +++ b/src/uint8arrays/concat.js @@ -1,29 +1,33 @@ 'use strict' +function byteLength (arrs) { + return arrs.reduce((acc, curr) => { + if (ArrayBuffer.isView(curr)) { + return acc + curr.byteLength + } else if (Array.isArray(curr)) { + return acc + curr.length + } + + throw new Error('Invalid input passed to concat, should be an Array or ArrayBuffer view') + }, 0) +} + /** * Returns a new Uint8Array created by concatenating the passed Arrays * * @param {Array} arrs - * @param {Number} length + * @param {Number} [length] * @returns {Uint8Array} */ function concat (arrs, length) { if (!length) { - length = arrs.reduce((acc, curr) => { - if (ArrayBuffer.isView(curr)) { - return acc + curr.byteLength - } else if (Array.isArray(curr)) { - return acc + curr.length - } - - throw new Error('Invalid input passed to concat, should be an Array or ArrayBuffer view') - }, 0) + length = byteLength(arrs) } const output = new Uint8Array(length) let offset = 0 - arrs.forEach(arr => { + for (const arr of arrs) { output.set(arr, offset) if (ArrayBuffer.isView(arr)) { @@ -33,7 +37,7 @@ function concat (arrs, length) { } else { throw new Error('Invalid input passed to concat, should be an Array or ArrayBuffer view') } - }) + } return output } diff --git a/src/uint8arrays/from-string.js b/src/uint8arrays/from-string.js index b0da906..148f195 100644 --- a/src/uint8arrays/from-string.js +++ b/src/uint8arrays/from-string.js @@ -3,22 +3,30 @@ const multibase = require('multibase') const { names } = require('multibase/src/constants') const TextEncoder = require('../text-encoder') -const utf8Encoder = new TextEncoder('utf8') +const utf8Encoder = new TextEncoder() +/** + * Create a `Uint8Array` from the passed string + * + * @param {String} string + * @param {String} [encoding=utf8] utf8, base16, base64, base64urlpad, etc + * @returns {Uint8Array} + * @see {@link https://www.npmjs.com/package/multibase|multibase} for supported encodings other than `utf8` + */ function fromString (string, encoding = 'utf8') { - if (encoding !== 'utf8') { - const base = names[encoding] - - if (!base) { - throw new Error('Unknown base') - } + if (encoding === 'utf8' || encoding === 'utf-8') { + return utf8Encoder.encode(string) + } - string = `${base.code}${string}` + const base = names[encoding] - return Uint8Array.from(multibase.decode(string)) + if (!base) { + throw new Error('Unknown base') } - return utf8Encoder.encode(string) + string = `${base.code}${string}` + + return Uint8Array.from(multibase.decode(string)) } module.exports = fromString diff --git a/src/uint8arrays/to-string.js b/src/uint8arrays/to-string.js index 2326754..0105ea5 100644 --- a/src/uint8arrays/to-string.js +++ b/src/uint8arrays/to-string.js @@ -4,8 +4,18 @@ const multibase = require('multibase') const TextDecoder = require('../text-decoder') const utf8Decoder = new TextDecoder('utf8') +/** + * Turns a `Uint8Array` into a string. + * + * Supports `utf8` and any encoding supported by the multibase module + * + * @param {Uint8Array} buf The array to turn into a string + * @param {String} [encoding=utf8] The encoding to use + * @returns {String} + * @see {@link https://www.npmjs.com/package/multibase|multibase} for supported encodings other than `utf8` + */ function toString (buf, encoding = 'utf8') { - if (encoding !== 'utf8') { + if (encoding !== 'utf8' && encoding !== 'utf-8') { buf = multibase.encode(encoding, buf).subarray(1) }