Added utils package for parsing helpers
This commit is contained in:
163
src/utils.ts
Normal file
163
src/utils.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
export type TypedArray = Int8Array | Uint8ClampedArray | Uint8Array | Uint16Array | Int16Array | Uint32Array | Int32Array;
|
||||
|
||||
export const U8 = (arr: ArrayBuffer | TypedArray): Uint8Array => {
|
||||
if (arr instanceof ArrayBuffer) return new Uint8Array(arr);
|
||||
return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
|
||||
}
|
||||
|
||||
export const I8 = (arr: ArrayBuffer | TypedArray): Int8Array => {
|
||||
if (arr instanceof ArrayBuffer) return new Int8Array(arr);
|
||||
return new Int8Array(arr.buffer, arr.byteOffset, arr.byteLength);
|
||||
}
|
||||
|
||||
export const U16 = (arr: ArrayBuffer | TypedArray): Uint16Array => {
|
||||
if (arr instanceof ArrayBuffer) return new Uint16Array(arr);
|
||||
return new Uint16Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 2));
|
||||
}
|
||||
|
||||
export const I16 = (arr: ArrayBuffer | TypedArray): Int16Array => {
|
||||
if (arr instanceof ArrayBuffer) return new Int16Array(arr);
|
||||
return new Int16Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 2));
|
||||
}
|
||||
|
||||
export const U32 = (arr: ArrayBuffer | TypedArray): Uint32Array => {
|
||||
if (arr instanceof ArrayBuffer) return new Uint32Array(arr);
|
||||
return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
||||
}
|
||||
|
||||
export const I32 = (arr: ArrayBuffer | TypedArray): Int32Array => {
|
||||
if (arr instanceof ArrayBuffer) return new Int32Array(arr);
|
||||
return new Int32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
||||
}
|
||||
|
||||
export const F32 = (arr: ArrayBuffer | TypedArray): Float32Array => {
|
||||
if (arr instanceof ArrayBuffer) return new Float32Array(arr);
|
||||
return new Float32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
||||
}
|
||||
|
||||
export const F64 = (arr: ArrayBuffer | TypedArray): Float64Array => {
|
||||
if (arr instanceof ArrayBuffer) return new Float64Array(arr);
|
||||
return new Float64Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 8));
|
||||
}
|
||||
|
||||
export const isBytes = (bytes: unknown): boolean => {
|
||||
return bytes instanceof Uint8Array || (ArrayBuffer.isView(bytes) && bytes.constructor.name === "Uint8Array");
|
||||
}
|
||||
|
||||
export const assertBytes = (bytes: Uint8Array, length?: number, name: string = ""): void => {
|
||||
const valid = isBytes(bytes);
|
||||
const sized = (typeof length !== 'undefined') ? (bytes.byteLength === length) : true;
|
||||
if (!valid || !sized) {
|
||||
const expected = typeof length !== 'undefined' ? `Uint8Array of length ${length}` : 'Uint8Array';
|
||||
const actual = valid ? `Uint8Array of length ${bytes.byteLength}` : typeof bytes;
|
||||
throw new TypeError(`Expected ${name} to be ${expected}, got ${actual}`);
|
||||
}
|
||||
}
|
||||
|
||||
const hasHexMethods = (() =>
|
||||
// @ts-ignore testing for builtins
|
||||
'toHex' in Uint8Array.from([]) && typeof Uint8Array.from([])['toHex'] === 'function' &&
|
||||
'fromHex' in Uint8Array && typeof Uint8Array.fromHex === 'function')();
|
||||
|
||||
// Array where index 0xf0 (240) is mapped to string 'f0'
|
||||
const hexes = Array.from({ length: 256 }, (_, i) =>
|
||||
i.toString(16).padStart(2, '0')
|
||||
);
|
||||
|
||||
export type HexEncoding = 'hex';
|
||||
export type Base64Encoding = 'base64';
|
||||
export type Base64RawEncoding = 'base64raw';
|
||||
export type Base64URLEncoding = 'base64url';
|
||||
export type Base64RawURLEncoding = 'base64urlraw';
|
||||
export type Encoding = HexEncoding | Base64Encoding | Base64RawEncoding | Base64URLEncoding | Base64RawURLEncoding;
|
||||
|
||||
export const bytesToHex = (bytes: Uint8Array, length?: number): string => {
|
||||
assertBytes(bytes, (typeof length !== 'undefined') ? length * 2 : undefined, "bytes");
|
||||
let hex = '';
|
||||
for (const byte of bytes) {
|
||||
hex += hexes[byte];
|
||||
}
|
||||
return hex;
|
||||
};
|
||||
|
||||
export const hexToBytes = (hex: string): Uint8Array => {
|
||||
if (typeof hex !== 'string' || hex.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(hex)) {
|
||||
throw new TypeError(`Expected hex string of even length, got ${hex}`);
|
||||
}
|
||||
if (hasHexMethods) {
|
||||
// @ts-ignore using built-in hex methods if available
|
||||
return Uint8Array.fromHex(hex);
|
||||
}
|
||||
|
||||
const bytes = new Uint8Array(hex.length / 2);
|
||||
for (let i = 0; i < hex.length; i += 2) {
|
||||
bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
export const bytesToBase64 = (bytes: Uint8Array, encoding: Base64Encoding | Base64RawEncoding | Base64URLEncoding | Base64RawURLEncoding): string => {
|
||||
assertBytes(bytes, (typeof length !== 'undefined') ? length * 2 : undefined, "bytes");
|
||||
const binary = String.fromCharCode(...bytes);
|
||||
switch (encoding) {
|
||||
case 'base64':
|
||||
return btoa(binary);
|
||||
case 'base64raw':
|
||||
return btoa(binary).replace(/=+$/, '');
|
||||
case 'base64url':
|
||||
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_');
|
||||
case 'base64urlraw':
|
||||
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||
default:
|
||||
throw new TypeError(`Unsupported encoding: ${encoding}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const base64ToBytes = (b64: string): Uint8Array => {
|
||||
if (typeof b64 !== "string") {
|
||||
throw new TypeError(`Expected base64 string, got ${b64}`);
|
||||
}
|
||||
// Accept URL-safe base64 by replacing '-' with '+' and '_' with '/'
|
||||
let normalized = b64.replace(/-/g, "+").replace(/_/g, "/");
|
||||
// Pad with '=' to make length a multiple of 4
|
||||
if (normalized.length % 4 !== 0) {
|
||||
normalized += "=".repeat(4 - (normalized.length % 4));
|
||||
}
|
||||
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(normalized)) {
|
||||
throw new TypeError(`Expected base64 string, got ${b64}`);
|
||||
}
|
||||
const binary = atob(normalized);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
for (let i = 0; i < binary.length; i++) {
|
||||
bytes[i] = binary.charCodeAt(i);
|
||||
}
|
||||
return bytes;
|
||||
};
|
||||
|
||||
export const decodeBytes = (encoded: string, encoding: Encoding): Uint8Array => {
|
||||
switch (encoding) {
|
||||
case 'hex':
|
||||
return hexToBytes(encoded);
|
||||
case 'base64':
|
||||
case 'base64raw':
|
||||
case 'base64url':
|
||||
case 'base64urlraw':
|
||||
return base64ToBytes(encoded);
|
||||
default:
|
||||
throw new TypeError(`Unsupported encoding: ${encoding}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const encodeBytes = (bytes: Uint8Array, encoding: Encoding): string => {
|
||||
switch (encoding) {
|
||||
case 'hex':
|
||||
return bytesToHex(bytes);
|
||||
case 'base64':
|
||||
case 'base64raw':
|
||||
case 'base64url':
|
||||
case 'base64urlraw':
|
||||
return bytesToBase64(bytes, encoding);
|
||||
default:
|
||||
throw new TypeError(`Unsupported encoding: ${encoding}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user