We can not sensibly parse both hex and base64, assume all input is hex
This commit is contained in:
@@ -1,13 +1,12 @@
|
||||
import { equalBytes } from "@noble/ciphers/utils.js";
|
||||
import { bytesToHex, hexToBytes } from "@noble/hashes/utils.js";
|
||||
import { bytesToHex, hexToBytes as nobleHexToBytes } from "@noble/hashes/utils.js";
|
||||
|
||||
export {
|
||||
bytesToHex,
|
||||
hexToBytes,
|
||||
equalBytes
|
||||
};
|
||||
|
||||
export const base64ToBytes = (base64: string): Uint8Array => {
|
||||
export const base64ToBytes = (base64: string, size?: number): Uint8Array => {
|
||||
// Normalize URL-safe base64 to standard base64
|
||||
let normalized = base64.replace(/-/g, '+').replace(/_/g, '/');
|
||||
// Add padding if missing
|
||||
@@ -19,32 +18,23 @@ export const base64ToBytes = (base64: string): Uint8Array => {
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
if (size !== undefined && bytes.length !== size) {
|
||||
throw new Error(`Invalid base64 length: expected ${size} bytes, got ${bytes.length}`);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
export const encodedStringToBytes = (str: string, size: number): Uint8Array => {
|
||||
const hexRegex = /^[0-9a-fA-F]+$/;
|
||||
// Accept both standard and URL-safe base64, with or without padding
|
||||
const b64Regex = /^(?:[A-Za-z0-9+\/_-]{4})*(?:[A-Za-z0-9+\/_-]{2}(?:==)?|[A-Za-z0-9+\/_-]{3}=?)?$/;
|
||||
// Note: encodedStringToBytes removed — prefer explicit parsers.
|
||||
// Use `hexToBytes` for hex inputs and `base64ToBytes` for base64 inputs.
|
||||
|
||||
if (hexRegex.test(str) && str.length === size * 2) {
|
||||
return hexToBytes(str);
|
||||
} else if (b64Regex.test(str)) {
|
||||
const bytes = base64ToBytes(str);
|
||||
if (bytes.length === size) {
|
||||
return bytes;
|
||||
}
|
||||
} else if (str.length === size) {
|
||||
// Raw format: treat as bytes (latin1)
|
||||
const bytes = new Uint8Array(size);
|
||||
for (let i = 0; i < size; i++) {
|
||||
bytes[i] = str.charCodeAt(i) & 0xFF;
|
||||
}
|
||||
return bytes;
|
||||
// Wrapper around @noble/hashes hexToBytes that optionally validates size.
|
||||
export const hexToBytes = (hex: string, size?: number): Uint8Array => {
|
||||
const bytes = nobleHexToBytes(hex);
|
||||
if (size !== undefined && bytes.length !== size) {
|
||||
throw new Error(`Invalid hex length: expected ${size} bytes, got ${bytes.length}`);
|
||||
}
|
||||
|
||||
throw new Error(`Invalid input: expected hex, base64 (standard or URL-safe), or raw string of size ${size}`);
|
||||
}
|
||||
return bytes;
|
||||
};
|
||||
|
||||
export class BufferReader {
|
||||
private buffer: Uint8Array;
|
||||
@@ -56,6 +46,7 @@ export class BufferReader {
|
||||
}
|
||||
|
||||
public readByte(): number {
|
||||
if (!this.hasMore()) throw new Error('read past end');
|
||||
return this.buffer[this.offset++];
|
||||
}
|
||||
|
||||
@@ -63,6 +54,7 @@ export class BufferReader {
|
||||
if (length === undefined) {
|
||||
length = this.buffer.length - this.offset;
|
||||
}
|
||||
if (this.remainingBytes() < length) throw new Error('read past end');
|
||||
const bytes = this.buffer.slice(this.offset, this.offset + length);
|
||||
this.offset += length;
|
||||
return bytes;
|
||||
@@ -77,31 +69,36 @@ export class BufferReader {
|
||||
}
|
||||
|
||||
public peekByte(): number {
|
||||
if (!this.hasMore()) throw new Error('read past end');
|
||||
return this.buffer[this.offset];
|
||||
}
|
||||
|
||||
public readUint16LE(): number {
|
||||
if (this.remainingBytes() < 2) throw new Error('read past end');
|
||||
const value = this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8);
|
||||
this.offset += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
public readUint32LE(): number {
|
||||
const value = this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8) | (this.buffer[this.offset + 2] << 16) | (this.buffer[this.offset + 3] << 24);
|
||||
if (this.remainingBytes() < 4) throw new Error('read past end');
|
||||
const value = (this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8) | (this.buffer[this.offset + 2] << 16) | (this.buffer[this.offset + 3] << 24)) >>> 0;
|
||||
this.offset += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
public readInt16LE(): number {
|
||||
if (this.remainingBytes() < 2) throw new Error('read past end');
|
||||
const value = this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8);
|
||||
this.offset += 2;
|
||||
return value < 0x8000 ? value : value - 0x10000;
|
||||
}
|
||||
|
||||
public readInt32LE(): number {
|
||||
const value = this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8) | (this.buffer[this.offset + 2] << 16) | (this.buffer[this.offset + 3] << 24);
|
||||
if (this.remainingBytes() < 4) throw new Error('read past end');
|
||||
const u = (this.buffer[this.offset] | (this.buffer[this.offset + 1] << 8) | (this.buffer[this.offset + 2] << 16) | (this.buffer[this.offset + 3] << 24)) >>> 0;
|
||||
this.offset += 4;
|
||||
return value < 0x80000000 ? value : value - 0x100000000;
|
||||
return u < 0x80000000 ? u : u - 0x100000000;
|
||||
}
|
||||
|
||||
public readTimestamp(): Date {
|
||||
|
||||
Reference in New Issue
Block a user