87 lines
2.3 KiB
TypeScript
87 lines
2.3 KiB
TypeScript
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;
|
|
}
|
|
}
|