Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add getUintBE, findSequence, and encoding for uint8ArrayToString #12

Merged
18 changes: 16 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ const array3 = new Uint8Array([7, 8, 9]);
export function compareUint8Arrays(a: Uint8Array, b: Uint8Array): 0 | 1 | -1;

/**
Convert a `Uint8Array` (containing a UTF-8 string) to a string.
Convert a `Uint8Array` to a string using an optional encoding (defaults to UTF-8).

Replacement for [`Buffer#toString()`](https://nodejs.org/api/buffer.html#buftostringencoding-start-end).

Expand All @@ -135,7 +135,7 @@ console.log(uint8ArrayToString(byteArray));
//=> 'Hello'
```
*/
export function uint8ArrayToString(array: Uint8Array): string;
export function uint8ArrayToString(array: Uint8Array, encoding?: string): string;

/**
Convert a string to a `Uint8Array` (using UTF-8 encoding).
Expand Down Expand Up @@ -249,3 +249,17 @@ console.log(hexToUint8Array('48656c6c6f'));
```
*/
export function hexToUint8Array(hexString: string): Uint8Array;

/**
Get up to a 48-bit integer from a DataView`

Replacement for [`Buffer#readUintBE`](https://nodejs.org/api/buffer.html#bufreadintbeoffset-bytelength)
*/
export function getUintBE(view: DataView): number; // eslint-disable-line @typescript-eslint/naming-convention

/**
* Find a sequence of bytes in an array buffer
*
* Replacement for [`Buffer#indexOf`](https://nodejs.org/api/buffer.html#bufindexofvalue-byteoffset-encoding)
*/
export function findSequence(arrayBuffer: ArrayBuffer, sequence: Uint8Array): number;
69 changes: 66 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,14 @@ export function compareUint8Arrays(a, b) {
return Math.sign(a.length - b.length);
}

const cachedDecoder = new globalThis.TextDecoder();
const cachedDecoders = {
utf8: new globalThis.TextDecoder('utf8'),
};

export function uint8ArrayToString(array) {
export function uint8ArrayToString(array, encoding = 'utf8') {
assertUint8Array(array);
return cachedDecoder.decode(array);
cachedDecoders[encoding] ??= new globalThis.TextDecoder(encoding);
return cachedDecoders[encoding].decode(array);
}

function assertString(value) {
Expand Down Expand Up @@ -220,3 +223,63 @@ export function hexToUint8Array(hexString) {

return bytes;
}

/**
* @param {DataView} view
* @returns {number}
*/
export function getUintBE(view) {
const {byteLength} = view;

if (byteLength === 6) {
return (view.getUint16(0) * (2 ** 32)) + view.getUint32(2);
}

if (byteLength === 5) {
return (view.getUint8(0) * (2 ** 32)) + view.getUint32(1);
}

if (byteLength === 4) {
return view.getUint32(0);
}

if (byteLength === 3) {
return (view.getUint8(0) * (2 ** 16)) + view.getUint16(1);
}

if (byteLength === 2) {
return view.getUint16(0);
}

if (byteLength === 1) {
return view.getUint8(0);
}
}

/**
* @param {Uint8Array} arrayBuffer
* @param {Uint8Array} sequence
* @returns {number}
*/
export function findSequence(arrayBuffer, sequence) {
const sequenceLength = sequence.length;
const validOffsetLength = arrayBuffer.length - sequenceLength;

for (let i = 0; i < validOffsetLength; i += 1) {
let match = true;

for (let j = 0; j < sequenceLength; j += 1) {
if (arrayBuffer[i + j] !== sequence[j]) {
match = false;
j = 0;
break;
}
}

if (match) {
return i;
}
}

return -1;
}
30 changes: 30 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
base64ToString,
uint8ArrayToHex,
hexToUint8Array,
getUintBE,
findSequence,
} from './index.js';

test('isUint8Array', t => {
Expand Down Expand Up @@ -119,6 +121,12 @@ test('stringToUint8Array and uint8ArrayToString', t => {
t.is(uint8ArrayToString(array), fixture);
});

test('uint8ArrayToString with encoding', t => {
t.is(uint8ArrayToString(new Uint8Array([
207, 240, 232, 226, 229, 242, 44, 32, 236, 232, 240, 33,
]), 'windows-1251'), 'Привет, мир!');
});

test('uint8ArrayToBase64 and base64ToUint8Array', t => {
const fixture = stringToUint8Array('Hello');
const base64 = uint8ArrayToBase64(fixture);
Expand Down Expand Up @@ -158,3 +166,25 @@ test('hexToUint8Array', t => {
const fixtureHex = Buffer.from(fixtureString).toString('hex'); // eslint-disable-line n/prefer-global/buffer
t.deepEqual(hexToUint8Array(fixtureHex), new Uint8Array(Buffer.from(fixtureHex, 'hex'))); // eslint-disable-line n/prefer-global/buffer
});

test('getUintBE', t => {
const fixture = [0x12, 0x34, 0x56, 0x78, 0x90, 0xab]; // eslint-disable-line unicorn/number-literal-case

for (let i = 1; i < 6; i += 1) {
t.is(getUintBE(new DataView(new Uint8Array(fixture).buffer, 0, i)), Buffer.from(fixture).readUintBE(0, i)); // eslint-disable-line n/prefer-global/buffer
}

for (let i = 0; i < 5; i += 1) {
t.is(getUintBE(new DataView(new Uint8Array(fixture).buffer, i, 6 - i)), Buffer.from(fixture).readUintBE(i, 6 - i)); // eslint-disable-line n/prefer-global/buffer
}
});

test('findSequence', t => {
const fixture = [0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef]; // eslint-disable-line unicorn/number-literal-case
const sequence = [0x78, 0x90];
t.is(findSequence(new Uint8Array(fixture), new Uint8Array(sequence)), Buffer.from(fixture).indexOf(Buffer.from(sequence))); // eslint-disable-line n/prefer-global/buffer
t.is(findSequence(new Uint8Array(fixture), new Uint8Array(sequence)), 3);
t.is(findSequence(new Uint8Array(fixture), new Uint8Array([0x00, 0x01])), -1);
// Uint8Array only works with a number so it cannot replace Buffer.indexOf
t.not(new Uint8Array(fixture).indexOf(new Uint8Array(sequence)), Buffer.from(fixture).indexOf(Buffer.from(sequence))); // eslint-disable-line n/prefer-global/buffer
});