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
51 changes: 47 additions & 4 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,21 +121,30 @@ 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.

Replacement for [`Buffer#toString()`](https://nodejs.org/api/buffer.html#buftostringencoding-start-end).
@param encoding - The [encoding](https://developer.mozilla.org/en-US/docs/Web/API/Encoding_API/Encodings) to convert from. Default: `'utf8'`

Replacement for [`Buffer#toString()`](https://nodejs.org/api/buffer.html#buftostringencoding-start-end). For the `encoding` parameter, `latin1` should be used instead of `binary` and `utf-16le` instead of `utf16le`.

@example
```
import {uint8ArrayToString} from 'uint8array-extras';

const byteArray = new Uint8Array([72, 101, 108, 108, 111]);

console.log(uint8ArrayToString(byteArray));
//=> 'Hello'

const zh = new Uint8Array([167, 65, 166, 110]);
console.log(uint8ArrayToString(zh, 'big5'));
//=> '你好'

const ja = new Uint8Array([130, 177, 130, 241, 130, 201, 130, 191, 130, 205]);
console.log(uint8ArrayToString(ja, 'shift-jis'));
//=> 'こんにちは'
```
*/
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 +258,37 @@ console.log(hexToUint8Array('48656c6c6f'));
```
*/
export function hexToUint8Array(hexString: string): Uint8Array;

/**
Read `DataView#byteLength` number of bytes from the given view, up to 48-bit.

Replacement for [`Buffer#readUintBE`](https://nodejs.org/api/buffer.html#bufreadintbeoffset-bytelength)

@example
```
import {getUintBE} from 'uint8array-extras';

const byteArray = new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]);

console.log(getUintBE(new DataView(byteArray.buffer)));
//=> 20015998341291
```
*/
export function getUintBE(view: DataView): number; // eslint-disable-line @typescript-eslint/naming-convention

/**
Find the index of the first occurrence of the given sequence of bytes (`value`) within the given `Uint8Array` (`array`).

Replacement for [`Buffer#indexOf`](https://nodejs.org/api/buffer.html#bufindexofvalue-byteoffset-encoding). `Uint8Array#indexOf` only takes a number which is different from Buffer's `indexOf` implementation.

@example
```
import {indexOf} from 'uint8array-extras';

const byteArray = new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef]);

console.log(indexOf(byteArray, new Uint8Array([0x78, 0x90])));
//=> 3
```
*/
export function indexOf(array: Uint8Array, value: 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} array
@param {Uint8Array} value
@returns {number}
*/
export function findSequence(array, value) {
const valueLength = value.length;
const validOffsetLength = array.length - valueLength;

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

for (let j = 0; j < valueLength; j += 1) {
if (array[i + j] !== value[j]) {
match = false;
j = 0;
break;
}
}

if (match) {
return i;
}
}

return -1;
}
47 changes: 43 additions & 4 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,19 +130,28 @@ const array3 = new Uint8Array([7, 8, 9]);
//=> [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
```

### `uint8ArrayToString(array: Uint8Array): string`
### `uint8ArrayToString(array: Uint8Array, encoding?: string = 'utf8'): string`

Convert a `Uint8Array` (containing a UTF-8 string) to a string.
Convert a `Uint8Array` to a string.

Replacement for [`Buffer#toString()`](https://nodejs.org/api/buffer.html#buftostringencoding-start-end).
- Parameter: `encoding` - The [encoding](https://developer.mozilla.org/en-US/docs/Web/API/Encoding_API/Encodings) to convert from.

Replacement for [`Buffer#toString()`](https://nodejs.org/api/buffer.html#buftostringencoding-start-end). For the `encoding` parameter, `latin1` should be used instead of `binary` and `utf-16le` instead of `utf16le`.

```js
import {uint8ArrayToString} from 'uint8array-extras';

const byteArray = new Uint8Array([72, 101, 108, 108, 111]);

console.log(uint8ArrayToString(byteArray));
//=> 'Hello'

const zh = new Uint8Array([167, 65, 166, 110]);
console.log(uint8ArrayToString(zh, 'big5'));
//=> '你好'

const ja = new Uint8Array([130, 177, 130, 241, 130, 201, 130, 191, 130, 205]);
console.log(uint8ArrayToString(ja, 'shift-jis'));
//=> 'こんにちは'
```

### `stringToUint8Array(string: string): Uint8Array`
Expand Down Expand Up @@ -243,3 +252,33 @@ import {hexToUint8Array} from 'uint8array-extras';
console.log(hexToUint8Array('48656c6c6f'));
//=> Uint8Array [72, 101, 108, 108, 111]
```

### `getUintBE(view: DataView): number`

Read `DataView#byteLength` number of bytes from the given view, up to 48-bit.

Replacement for [`Buffer#readUintBE`](https://nodejs.org/api/buffer.html#bufreadintbeoffset-bytelength)

```js
import {getUintBE} from 'uint8array-extras';

const byteArray = new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]);

console.log(getUintBE(new DataView(byteArray.buffer)));
//=> 20015998341291
```

### `indexOf(array: Uint8Array, value: Uint8Array): number`

Find the index of the first occurrence of the given sequence of bytes (`value`) within the given `Uint8Array` (`array`).

Replacement for [`Buffer#indexOf`](https://nodejs.org/api/buffer.html#bufindexofvalue-byteoffset-encoding). `Uint8Array#indexOf` only takes a number which is different from Buffer's `indexOf` implementation.

```js
import {indexOf} from 'uint8array-extras';

const byteArray = new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef]);

console.log(indexOf(byteArray, new Uint8Array([0x78, 0x90])));
//=> 3
```
38 changes: 38 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,20 @@ 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'), 'Привет, мир!');

t.is(uint8ArrayToString(new Uint8Array([
167, 65, 166, 110,
]), 'big5'), '你好');

t.is(uint8ArrayToString(new Uint8Array([
130, 177, 130, 241, 130, 201, 130, 191, 130, 205,
]), 'shift-jis'), 'こんにちは');
});

test('uint8ArrayToBase64 and base64ToUint8Array', t => {
const fixture = stringToUint8Array('Hello');
const base64 = uint8ArrayToBase64(fixture);
Expand Down Expand Up @@ -158,3 +174,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('indexOf', 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
});