UNPKG

it-length-prefixed

Version:

Streaming length prefixed buffers with async iterables

155 lines (124 loc) 4.34 kB
/* 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 }) }