it-length-prefixed
Version:
Streaming length prefixed buffers with async iterables
124 lines • 4.54 kB
JavaScript
/* 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';
var ReadMode;
(function (ReadMode) {
ReadMode[ReadMode["LENGTH"] = 0] = "LENGTH";
ReadMode[ReadMode["DATA"] = 1] = "DATA";
})(ReadMode || (ReadMode = {}));
const defaultDecoder = (buf) => {
const length = varint.decode(buf);
defaultDecoder.bytes = varint.encodingLength(length);
return length;
};
defaultDecoder.bytes = 0;
export function decode(source, options) {
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() {
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) {
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, options) => {
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) {
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) => { byteLength = l; };
return decode(varByteSource, {
...(options ?? {}),
onLength
});
};
//# sourceMappingURL=decode.js.map