it-length-prefixed
Version:
Streaming length prefixed buffers with async iterables
155 lines (124 loc) • 4.34 kB
text/typescript
/* eslint max-depth: ["error", 6] */
import * as varint from 'uint8-varint'
import { Uint8ArrayList } from 'uint8arraylist'
import { MAX_DATA_LENGTH, MAX_LENGTH_LENGTH } from './constants.js'
import { InvalidDataLengthError, InvalidDataLengthLengthError, InvalidMessageLengthError, UnexpectedEOFError } from './errors.js'
import { isAsyncIterable } from './utils.js'
import type { DecoderOptions, LengthDecoderFunction } from './index.js'
import type { Reader } from 'it-reader'
import type { Source } from 'it-stream-types'
enum ReadMode {
LENGTH,
DATA
}
const defaultDecoder: LengthDecoderFunction = (buf) => {
const length = varint.decode(buf)
defaultDecoder.bytes = varint.encodingLength(length)
return length
}
defaultDecoder.bytes = 0
export function decode (source: Iterable<Uint8ArrayList | Uint8Array>, options?: DecoderOptions): Generator<Uint8ArrayList, void, unknown>
export function decode (source: Source<Uint8ArrayList | Uint8Array>, options?: DecoderOptions): AsyncGenerator<Uint8ArrayList, void, unknown>
export function decode (source: Source<Uint8ArrayList | Uint8Array>, options?: DecoderOptions): Generator<Uint8ArrayList, void, unknown> | AsyncGenerator<Uint8ArrayList, void, unknown> {
const buffer = new Uint8ArrayList()
let mode = ReadMode.LENGTH
let dataLength = -1
const lengthDecoder = options?.lengthDecoder ?? defaultDecoder
const maxLengthLength = options?.maxLengthLength ?? MAX_LENGTH_LENGTH
const maxDataLength = options?.maxDataLength ?? MAX_DATA_LENGTH
function * maybeYield (): Generator<Uint8ArrayList> {
while (buffer.byteLength > 0) {
if (mode === ReadMode.LENGTH) {
// read length, ignore errors for short reads
try {
dataLength = lengthDecoder(buffer)
if (dataLength < 0) {
throw new InvalidMessageLengthError('Invalid message length')
}
if (dataLength > maxDataLength) {
throw new InvalidDataLengthError('Message length too long')
}
const dataLengthLength = lengthDecoder.bytes
buffer.consume(dataLengthLength)
if (options?.onLength != null) {
options.onLength(dataLength)
}
mode = ReadMode.DATA
} catch (err: any) {
if (err instanceof RangeError) {
if (buffer.byteLength > maxLengthLength) {
throw new InvalidDataLengthLengthError('Message length length too long')
}
break
}
throw err
}
}
if (mode === ReadMode.DATA) {
if (buffer.byteLength < dataLength) {
// not enough data, wait for more
break
}
const data = buffer.sublist(0, dataLength)
buffer.consume(dataLength)
if (options?.onData != null) {
options.onData(data)
}
yield data
mode = ReadMode.LENGTH
}
}
}
if (isAsyncIterable(source)) {
return (async function * () {
for await (const buf of source) {
buffer.append(buf)
yield * maybeYield()
}
if (buffer.byteLength > 0) {
throw new UnexpectedEOFError('Unexpected end of input')
}
})()
}
return (function * () {
for (const buf of source) {
buffer.append(buf)
yield * maybeYield()
}
if (buffer.byteLength > 0) {
throw new UnexpectedEOFError('Unexpected end of input')
}
})()
}
decode.fromReader = (reader: Reader, options?: DecoderOptions) => {
let byteLength = 1 // Read single byte chunks until the length is known
const varByteSource = (async function * () {
while (true) {
try {
const { done, value } = await reader.next(byteLength)
if (done === true) {
return
}
if (value != null) {
yield value
}
} catch (err: any) {
if (err.code === 'ERR_UNDER_READ') {
return { done: true, value: null }
}
throw err
} finally {
// Reset the byteLength so we continue to check for varints
byteLength = 1
}
}
}())
/**
* Once the length has been parsed, read chunk for that length
*/
const onLength = (l: number): void => { byteLength = l }
return decode(varByteSource, {
...(options ?? {}),
onLength
})
}