ox
Version:
253 lines (241 loc) • 7.24 kB
text/typescript
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}\`).`,
)
}
}