Files
meshcore.ts/src/parser.ts

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);
}
}