import { fail } from './errors.js'; export class BinaryReader { readonly bytes: Uint8Array; readonly end: number; offset: number; private readonly view: DataView; constructor(bytes: Uint8Array, offset = 0, end = bytes.byteLength) { if (offset < 0 || end < offset || end > bytes.byteLength) { throw new RangeError('Invalid BinaryReader range'); } this.bytes = bytes; this.offset = offset; this.end = end; this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); } ensure(length: number, structure: string): void { if ( !Number.isSafeInteger(length) || length < 0 || this.offset > this.end - length ) { fail( 'cab-unexpected-eof', `Unexpected end of cabinet while reading ${structure}.`, { offset: this.offset, structure } ); } } readU8(structure: string): number { this.ensure(1, structure); return this.bytes[this.offset++]!; } readU16(structure: string): number { this.ensure(2, structure); const value = this.view.getUint16(this.offset, true); this.offset += 2; return value; } readU32(structure: string): number { this.ensure(4, structure); const value = this.view.getUint32(this.offset, true); this.offset += 4; return value; } readBytes(length: number, structure: string): Uint8Array { this.ensure(length, structure); const result = this.bytes.subarray(this.offset, this.offset + length); this.offset += length; return result; } skip(length: number, structure: string): void { this.ensure(length, structure); this.offset += length; } readNullTerminatedBytes(maxBytes: number, structure: string): Uint8Array { const start = this.offset; while (this.offset < this.end && this.bytes[this.offset] !== 0) { if (this.offset - start >= maxBytes) { fail( 'cab-name-too-long', `Cabinet string exceeds the configured ${maxBytes}-byte limit.`, { offset: start, structure } ); } this.offset += 1; } if (this.offset >= this.end) { fail('cab-unterminated-string', 'Cabinet string is not NUL-terminated.', { offset: start, structure, }); } const value = this.bytes.subarray(start, this.offset); this.offset += 1; return value; } }