151 lines
4.4 KiB
TypeScript
151 lines
4.4 KiB
TypeScript
import { equalBytes } from "@noble/ciphers/utils.js";
|
|
import { bytesToHex, hexToBytes as nobleHexToBytes } from "@noble/hashes/utils.js";
|
|
|
|
export {
|
|
bytesToHex,
|
|
equalBytes
|
|
};
|
|
|
|
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
|
|
while (normalized.length % 4 !== 0) {
|
|
normalized += '=';
|
|
}
|
|
const binaryString = atob(normalized);
|
|
const bytes = new Uint8Array(binaryString.length);
|
|
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;
|
|
}
|
|
|
|
// Note: encodedStringToBytes removed — prefer explicit parsers.
|
|
// Use `hexToBytes` for hex inputs and `base64ToBytes` for base64 inputs.
|
|
|
|
// 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}`);
|
|
}
|
|
return bytes;
|
|
};
|
|
|
|
export class BufferReader {
|
|
private buffer: Uint8Array;
|
|
private offset: number;
|
|
|
|
constructor(buffer: Uint8Array) {
|
|
this.buffer = buffer;
|
|
this.offset = 0;
|
|
}
|
|
|
|
public readByte(): number {
|
|
if (!this.hasMore()) throw new Error('read past end');
|
|
return this.buffer[this.offset++];
|
|
}
|
|
|
|
public readBytes(length?: number): Uint8Array {
|
|
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;
|
|
}
|
|
|
|
public hasMore(): boolean {
|
|
return this.offset < this.buffer.length;
|
|
}
|
|
|
|
public remainingBytes(): number {
|
|
return this.buffer.length - this.offset;
|
|
}
|
|
|
|
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 {
|
|
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 {
|
|
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 u < 0x80000000 ? u : u - 0x100000000;
|
|
}
|
|
|
|
public readTimestamp(): Date {
|
|
const timestamp = this.readUint32LE();
|
|
return new Date(timestamp * 1000);
|
|
}
|
|
}
|
|
|
|
export class BufferWriter {
|
|
private buffer: number[] = [];
|
|
|
|
public writeByte(value: number): void {
|
|
this.buffer.push(value & 0xFF);
|
|
}
|
|
|
|
public writeBytes(bytes: Uint8Array): void {
|
|
this.buffer.push(...bytes);
|
|
}
|
|
|
|
public writeUint16LE(value: number): void {
|
|
this.buffer.push(value & 0xFF, (value >> 8) & 0xFF);
|
|
}
|
|
|
|
public writeUint32LE(value: number): void {
|
|
this.buffer.push(
|
|
value & 0xFF,
|
|
(value >> 8) & 0xFF,
|
|
(value >> 16) & 0xFF,
|
|
(value >> 24) & 0xFF
|
|
);
|
|
}
|
|
|
|
public writeInt16LE(value: number): void {
|
|
this.writeUint16LE(value < 0 ? value + 0x10000 : value);
|
|
}
|
|
|
|
public writeInt32LE(value: number): void {
|
|
this.writeUint32LE(value < 0 ? value + 0x100000000 : value);
|
|
}
|
|
|
|
public writeTimestamp(date: Date): void {
|
|
const timestamp = Math.floor(date.getTime() / 1000);
|
|
this.writeUint32LE(timestamp);
|
|
}
|
|
|
|
public toBytes(): Uint8Array {
|
|
return new Uint8Array(this.buffer);
|
|
}
|
|
}
|