123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315 |
- /**
- * Stream class for parsing binary protocols.
- *
- * @author n1474335 [n1474335@gmail.com]
- * @author tlwr [toby@toby.codes]
- * @copyright Crown Copyright 2018
- * @license Apache-2.0
- *
- */
- /**
- * A Stream can be used to traverse a binary blob, interpreting sections of it
- * as various data types.
- */
- export default class Stream {
- /**
- * Stream constructor.
- *
- * @param {Uint8Array} input
- */
- constructor(input) {
- this.bytes = input;
- this.length = this.bytes.length;
- this.position = 0;
- this.bitPos = 0;
- }
- /**
- * Get a number of bytes from the current position.
- *
- * @param {number} numBytes
- * @returns {Uint8Array}
- */
- getBytes(numBytes) {
- if (this.position > this.length) return undefined;
- const newPosition = this.position + numBytes;
- const bytes = this.bytes.slice(this.position, newPosition);
- this.position = newPosition;
- this.bitPos = 0;
- return bytes;
- }
- /**
- * Interpret the following bytes as a string, stopping at the next null byte or
- * the supplied limit.
- *
- * @param {number} numBytes
- * @returns {string}
- */
- readString(numBytes) {
- if (this.position > this.length) return undefined;
- let result = "";
- for (let i = this.position; i < this.position + numBytes; i++) {
- const currentByte = this.bytes[i];
- if (currentByte === 0) break;
- result += String.fromCharCode(currentByte);
- }
- this.position += numBytes;
- this.bitPos = 0;
- return result;
- }
- /**
- * Interpret the following bytes as an integer in big or little endian.
- *
- * @param {number} numBytes
- * @param {string} [endianness="be"]
- * @returns {number}
- */
- readInt(numBytes, endianness="be") {
- if (this.position > this.length) return undefined;
- let val = 0;
- if (endianness === "be") {
- for (let i = this.position; i < this.position + numBytes; i++) {
- val = val << 8;
- val |= this.bytes[i];
- }
- } else {
- for (let i = this.position + numBytes - 1; i >= this.position; i--) {
- val = val << 8;
- val |= this.bytes[i];
- }
- }
- this.position += numBytes;
- this.bitPos = 0;
- return val;
- }
- /**
- * Reads a number of bits from the buffer.
- *
- * @TODO Add endianness
- *
- * @param {number} numBits
- * @returns {number}
- */
- readBits(numBits) {
- if (this.position > this.length) return undefined;
- let bitBuf = 0,
- bitBufLen = 0;
- // Add remaining bits from current byte
- bitBuf = (this.bytes[this.position++] & bitMask(this.bitPos)) >>> this.bitPos;
- bitBufLen = 8 - this.bitPos;
- this.bitPos = 0;
- // Not enough bits yet
- while (bitBufLen < numBits) {
- bitBuf |= this.bytes[this.position++] << bitBufLen;
- bitBufLen += 8;
- }
- // Reverse back to numBits
- if (bitBufLen > numBits) {
- const excess = bitBufLen - numBits;
- bitBuf &= (1 << numBits) - 1;
- bitBufLen -= excess;
- this.position--;
- this.bitPos = 8 - excess;
- }
- return bitBuf;
- /**
- * Calculates the bit mask based on the current bit position.
- *
- * @param {number} bitPos
- * @returns {number} The bit mask
- */
- function bitMask(bitPos) {
- return 256 - (1 << bitPos);
- }
- }
- /**
- * Consume the stream until we reach the specified byte or sequence of bytes.
- *
- * @param {number|List<number>} val
- */
- continueUntil(val) {
- if (this.position > this.length) return;
- this.bitPos = 0;
- if (typeof val === "number") {
- while (++this.position < this.length && this.bytes[this.position] !== val) {
- continue;
- }
- return;
- }
- // val is an array
- /**
- * Builds the skip forward table from the value to be searched.
- *
- * @param {Uint8Array} val
- * @param {Number} len
- * @returns {Uint8Array}
- */
- function preprocess(val, len) {
- const skiptable = new Array();
- val.forEach((element, index) => {
- skiptable[element] = len - index;
- });
- return skiptable;
- }
- const length = val.length;
- const initial = val[length-1];
- this.position = length;
- // Get the skip table.
- const skiptable = preprocess(val, length);
- let found = true;
- while (this.position < this.length) {
- // Until we hit the final element of val in the stream.
- while ((this.position < this.length) && (this.bytes[this.position++] !== initial));
- found = true;
- // Loop through the elements comparing them to val.
- for (let x = length-1; x >= 0; x--) {
- if (this.bytes[this.position - length + x] !== val[x]) {
- found = false;
- // If element is not equal to val's element then jump forward by the correct amount.
- this.position += skiptable[val[x]];
- break;
- }
- }
- if (found) {
- this.position -= length;
- break;
- }
- }
- }
- /**
- * Consume bytes if they match the supplied value.
- *
- * @param {Number} val
- */
- consumeWhile(val) {
- while (this.position < this.length) {
- if (this.bytes[this.position] !== val) {
- break;
- }
- this.position++;
- }
- this.bitPos = 0;
- }
- /**
- * Consume the next byte if it matches the supplied value.
- *
- * @param {number} val
- */
- consumeIf(val) {
- if (this.bytes[this.position] === val) {
- this.position++;
- this.bitPos = 0;
- }
- }
- /**
- * Move forwards through the stream by the specified number of bytes.
- *
- * @param {number} numBytes
- */
- moveForwardsBy(numBytes) {
- const pos = this.position + numBytes;
- if (pos < 0 || pos > this.length)
- throw new Error("Cannot move to position " + pos + " in stream. Out of bounds.");
- this.position = pos;
- this.bitPos = 0;
- }
- /**
- * Move backwards through the stream by the specified number of bytes.
- *
- * @param {number} numBytes
- */
- moveBackwardsBy(numBytes) {
- const pos = this.position - numBytes;
- if (pos < 0 || pos > this.length)
- throw new Error("Cannot move to position " + pos + " in stream. Out of bounds.");
- this.position = pos;
- this.bitPos = 0;
- }
- /**
- * Move backwards through the strem by the specified number of bits.
- *
- * @param {number} numBits
- */
- moveBackwardsByBits(numBits) {
- if (numBits <= this.bitPos) {
- this.bitPos -= numBits;
- } else {
- if (this.bitPos > 0) {
- numBits -= this.bitPos;
- this.bitPos = 0;
- }
- while (numBits > 0) {
- this.moveBackwardsBy(1);
- this.bitPos = 8;
- this.moveBackwardsByBits(numBits);
- numBits -= 8;
- }
- }
- }
- /**
- * Move to a specified position in the stream.
- *
- * @param {number} pos
- */
- moveTo(pos) {
- if (pos < 0 || pos > this.length)
- throw new Error("Cannot move to position " + pos + " in stream. Out of bounds.");
- this.position = pos;
- this.bitPos = 0;
- }
- /**
- * Returns true if there are more bytes left in the stream.
- *
- * @returns {boolean}
- */
- hasMore() {
- return this.position < this.length;
- }
- /**
- * Returns a slice of the stream up to the current position.
- *
- * @param {number} [start=0]
- * @param {number} [finish=this.position]
- * @returns {Uint8Array}
- */
- carve(start=0, finish=this.position) {
- if (this.bitPos > 0) finish++;
- return this.bytes.slice(start, finish);
- }
- }
|