UNPKG

ox

Version:

Ethereum Standard Library

253 lines (241 loc) 7.24 kB
import type { Bytes } from '../Bytes.js' import * as Errors from '../Errors.js' /** @internal */ export type Cursor = { bytes: Bytes dataView: DataView position: number positionReadCount: Map<number, number> recursiveReadCount: number recursiveReadLimit: number remaining: number assertReadLimit(position?: number): void assertPosition(position: number): void decrementPosition(offset: number): void getReadCount(position?: number): number incrementPosition(offset: number): void inspectByte(position?: number): Bytes[number] inspectBytes(length: number, position?: number): Bytes inspectUint8(position?: number): number inspectUint16(position?: number): number inspectUint24(position?: number): number inspectUint32(position?: number): number pushByte(byte: Bytes[number]): void pushBytes(bytes: Bytes): void pushUint8(value: number): void pushUint16(value: number): void pushUint24(value: number): void pushUint32(value: number): void readByte(): Bytes[number] readBytes(length: number, size?: number): Bytes readUint8(): number readUint16(): number readUint24(): number readUint32(): number setPosition(position: number): () => void _touch(): void } const staticCursor: Cursor = { bytes: new Uint8Array(), dataView: new DataView(new ArrayBuffer(0)), position: 0, positionReadCount: new Map(), recursiveReadCount: 0, recursiveReadLimit: Number.POSITIVE_INFINITY, assertReadLimit() { if (this.recursiveReadCount >= this.recursiveReadLimit) throw new RecursiveReadLimitExceededError({ count: this.recursiveReadCount + 1, limit: this.recursiveReadLimit, }) }, assertPosition(position) { if (position < 0 || position > this.bytes.length - 1) throw new PositionOutOfBoundsError({ length: this.bytes.length, position, }) }, decrementPosition(offset) { if (offset < 0) throw new NegativeOffsetError({ offset }) const position = this.position - offset this.assertPosition(position) this.position = position }, getReadCount(position) { return this.positionReadCount.get(position || this.position) || 0 }, incrementPosition(offset) { if (offset < 0) throw new NegativeOffsetError({ offset }) const position = this.position + offset this.assertPosition(position) this.position = position }, inspectByte(position_) { const position = position_ ?? this.position this.assertPosition(position) return this.bytes[position]! }, inspectBytes(length, position_) { const position = position_ ?? this.position this.assertPosition(position + length - 1) return this.bytes.subarray(position, position + length) }, inspectUint8(position_) { const position = position_ ?? this.position this.assertPosition(position) return this.bytes[position]! }, inspectUint16(position_) { const position = position_ ?? this.position this.assertPosition(position + 1) return this.dataView.getUint16(position) }, inspectUint24(position_) { const position = position_ ?? this.position this.assertPosition(position + 2) return ( (this.dataView.getUint16(position) << 8) + this.dataView.getUint8(position + 2) ) }, inspectUint32(position_) { const position = position_ ?? this.position this.assertPosition(position + 3) return this.dataView.getUint32(position) }, pushByte(byte: Bytes[number]) { this.assertPosition(this.position) this.bytes[this.position] = byte this.position++ }, pushBytes(bytes: Bytes) { this.assertPosition(this.position + bytes.length - 1) this.bytes.set(bytes, this.position) this.position += bytes.length }, pushUint8(value: number) { this.assertPosition(this.position) this.bytes[this.position] = value this.position++ }, pushUint16(value: number) { this.assertPosition(this.position + 1) this.dataView.setUint16(this.position, value) this.position += 2 }, pushUint24(value: number) { this.assertPosition(this.position + 2) this.dataView.setUint16(this.position, value >> 8) this.dataView.setUint8(this.position + 2, value & ~4294967040) this.position += 3 }, pushUint32(value: number) { this.assertPosition(this.position + 3) this.dataView.setUint32(this.position, value) this.position += 4 }, readByte() { this.assertReadLimit() this._touch() const value = this.inspectByte() this.position++ return value }, readBytes(length, size) { this.assertReadLimit() this._touch() const value = this.inspectBytes(length) this.position += size ?? length return value }, readUint8() { this.assertReadLimit() this._touch() const value = this.inspectUint8() this.position += 1 return value }, readUint16() { this.assertReadLimit() this._touch() const value = this.inspectUint16() this.position += 2 return value }, readUint24() { this.assertReadLimit() this._touch() const value = this.inspectUint24() this.position += 3 return value }, readUint32() { this.assertReadLimit() this._touch() const value = this.inspectUint32() this.position += 4 return value }, get remaining() { return this.bytes.length - this.position }, setPosition(position) { const oldPosition = this.position this.assertPosition(position) this.position = position return () => (this.position = oldPosition) }, _touch() { if (this.recursiveReadLimit === Number.POSITIVE_INFINITY) return const count = this.getReadCount() this.positionReadCount.set(this.position, count + 1) if (count > 0) this.recursiveReadCount++ }, } /** @internal */ export function create( bytes: Bytes, { recursiveReadLimit = 8_192 }: create.Config = {}, ): Cursor { const cursor: Cursor = Object.create(staticCursor) cursor.bytes = bytes cursor.dataView = new DataView( bytes.buffer, bytes.byteOffset, bytes.byteLength, ) cursor.positionReadCount = new Map() cursor.recursiveReadLimit = recursiveReadLimit return cursor } /** @internal */ export declare namespace create { type Config = { recursiveReadLimit?: number | undefined } type ErrorType = Errors.GlobalErrorType } /** @internal */ export class NegativeOffsetError extends Errors.BaseError { override readonly name = 'Cursor.NegativeOffsetError' constructor({ offset }: { offset: number }) { super(`Offset \`${offset}\` cannot be negative.`) } } /** @internal */ export class PositionOutOfBoundsError extends Errors.BaseError { override readonly name = 'Cursor.PositionOutOfBoundsError' constructor({ length, position }: { length: number; position: number }) { super( `Position \`${position}\` is out of bounds (\`0 < position < ${length}\`).`, ) } } /** @internal */ export class RecursiveReadLimitExceededError extends Errors.BaseError { override readonly name = 'Cursor.RecursiveReadLimitExceededError' constructor({ count, limit }: { count: number; limit: number }) { super( `Recursive read limit of \`${limit}\` exceeded (recursive read count: \`${count}\`).`, ) } }