// Tweaked version of nathan7's binary-parse-stream
// (see https://github.com/nathan7/binary-parse-stream)
// Uses NoFilter instead of the readable in the original. Removes
// the ability to read -1, which was odd and un-needed.
// License for binary-parse-stream: MIT
// binary-parse-stream is now unmaintained, so I have rewritten it as
// more modern JS so I can get tsc to help check types.
'use strict';
// eslint-disable-next-line n/prefer-node-protocol
const stream = require('stream');
const NoFilter = require('nofilter');
/**
* BinaryParseStream is a TransformStream that consumes buffers and outputs
* objects on the other end. It expects your subclass to implement a `_parse`
* method that is a generator. When your generator yields a number, it'll be
* fed a buffer of that length from the input. When your generator returns,
* the return value will be pushed to the output side.
*
* @extends stream.Transform
*/
class BinaryParseStream extends stream.Transform {
/**
* Creates an instance of BinaryParseStream.
*
* @memberof BinaryParseStream
* @param {stream.TransformOptions} options Stream options.
*/
constructor(options) {
super(options);
// Doesn't work to pass these in as opts, for some reason
// also, work around typescript not knowing TransformStream internals
// eslint-disable-next-line dot-notation
this['_writableState'].objectMode = false;
// eslint-disable-next-line dot-notation
this['_readableState'].objectMode = true;
this.bs = new NoFilter();
this.__restart();
}
/**
* Transforming.
*
* @param {any} fresh Buffer to transcode.
* @param {BufferEncoding} _encoding Name of encoding.
* @param {stream.TransformCallback} cb Callback when done.
* @ignore
*/
_transform(fresh, _encoding, cb) {
this.bs.write(fresh);
while (this.bs.length >= this.__needed) {
let ret = null;
const chunk = (this.__needed === null) ?
undefined :
this.bs.read(this.__needed);
try {
ret = this.__parser.next(chunk);
} catch (e) {
return cb(e);
}
if (this.__needed) {
this.__fresh = false;
}
if (ret.done) {
this.push(ret.value);
this.__restart();
} else {
this.__needed = ret.value || Infinity;
}
}
return cb();
}
/**
* Subclasses must override this to set their parsing behavior. Yield a
* number to receive a Buffer of that many bytes.
*
* @abstract
* @returns {Generator<number, any, Buffer>}
*/
/* istanbul ignore next */
*_parse() { // eslint-disable-line class-methods-use-this, require-yield
throw new Error('Must be implemented in subclass');
}
__restart() {
this.__needed = null;
this.__parser = this._parse();
this.__fresh = true;
}
/**
* Flushing.
*
* @param {stream.TransformCallback} cb Callback when done.
* @ignore
*/
_flush(cb) {
cb(this.__fresh ? null : new Error('unexpected end of input'));
}
}
module.exports = BinaryParseStream;