From 5b10c1a986295352f64cab693aaba8c01958cfb9 Mon Sep 17 00:00:00 2001 From: codedread Date: Sun, 21 Jan 2018 01:49:17 -0800 Subject: [PATCH] Make bitstreams pushable --- io/bitstream.js | 113 +++++++++++++++++++++++++++-------- io/bytestream.js | 11 ++-- tests/io-bitstream-test.html | 40 ++++++++++++- 3 files changed, 132 insertions(+), 32 deletions(-) diff --git a/io/bitstream.js b/io/bitstream.js index 62356f0..46fc535 100644 --- a/io/bitstream.js +++ b/io/bitstream.js @@ -34,11 +34,41 @@ bitjs.io.BitStream = class { const offset = opt_offset || 0; const length = opt_length || ab.byteLength; this.bytes = new Uint8Array(ab, offset, length); + this.pages_ = []; this.bytePtr = 0; // tracks which byte we are on this.bitPtr = 0; // tracks which bit we are on (can have values 0 through 7) this.peekBits = rtl ? this.peekBits_rtl : this.peekBits_ltr; } + + /** + * Returns how many bytes are currently in the stream left to be read. + * @private + */ + getNumBitsLeft_() { + const bitsLeftInByte = 8 - this.bitPtr; + const bitsLeftInCurrentPage = (this.bytes.byteLength - this.bytePtr - 1) * 8 + bitsLeftInByte; + return this.pages_.reduce((acc, arr) => acc + arr.length * 8, bitsLeftInCurrentPage); + } + + /** + * Move the pointer ahead n bits. The bytePtr and current page are updated as needed. + * This is a private method, no validation is done. + * @param {number} n Number of bits to increment. + * @private + */ + movePointer_(n) { + this.bitPtr += n; + while (this.bitPtr >= 8) { + this.bitPtr -= 8; + this.bytePtr++; + while (this.bytePtr >= this.bytes.length && this.pages_.length > 0) { + this.bytePtr -= this.bytes.length; + this.bytes = this.pages_.shift(); + } + } + } + /** * byte0 byte1 byte2 byte3 * 7......0 | 7......0 | 7......0 | 7......0 @@ -50,15 +80,22 @@ bitjs.io.BitStream = class { * @return {number} The peeked bits, as an unsigned number. */ peekBits_ltr(n, opt_movePointers) { - let num = parseInt(n, 10); + const NUM = parseInt(n, 10); + let num = NUM; if (n !== num || num < 0) { throw 'Error! Called peekBits_ltr() with a non-positive integer'; } else if (num === 0) { return 0; } + if (num > this.getNumBitsLeft_()) { + throw 'Error! Overflowed the bit stream! n=' + n + ', bytePtr=' + bytePtr + + ', bytes.length=' + bytes.length + ', bitPtr=' + bitPtr; + } + const movePointers = opt_movePointers || false; - const bytes = this.bytes; + let curPage = this.bytes; + let pageIndex = 0; let bytePtr = this.bytePtr; let bitPtr = this.bitPtr; let result = 0; @@ -69,24 +106,23 @@ bitjs.io.BitStream = class { // shifting/masking it to just extract the bits we want. // This could be considerably faster when reading more than 3 or 4 bits at a time. while (num > 0) { - if (bytePtr >= bytes.length) { - throw 'Error! Overflowed the bit stream! n=' + n + ', bytePtr=' + bytePtr + ', bytes.length=' + - bytes.length + ', bitPtr=' + bitPtr; + if (bytePtr >= curPage.length && this.pages_.length > 0) { + curPage = this.pages_[pageIndex++]; + bytePtr = 0; } const numBitsLeftInThisByte = (8 - bitPtr); if (num >= numBitsLeftInThisByte) { const mask = (bitjs.io.BitStream.BITMASK[numBitsLeftInThisByte] << bitPtr); - result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); + result |= (((curPage[bytePtr] & mask) >> bitPtr) << bitsIn); bytePtr++; bitPtr = 0; bitsIn += numBitsLeftInThisByte; num -= numBitsLeftInThisByte; - } - else { + } else { const mask = (bitjs.io.BitStream.BITMASK[num] << bitPtr); - result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); + result |= (((curPage[bytePtr] & mask) >> bitPtr) << bitsIn); bitPtr += num; bitsIn += num; @@ -95,8 +131,7 @@ bitjs.io.BitStream = class { } if (movePointers) { - this.bitPtr = bitPtr; - this.bytePtr = bytePtr; + this.movePointer_(NUM); } return result; @@ -113,15 +148,22 @@ bitjs.io.BitStream = class { * @return {number} The peeked bits, as an unsigned number. */ peekBits_rtl(n, opt_movePointers) { - let num = parseInt(n, 10); + const NUM = parseInt(n, 10); + let num = NUM; if (n !== num || num < 0) { throw 'Error! Called peekBits_rtl() with a non-positive integer'; } else if (num === 0) { return 0; } + if (num > this.getNumBitsLeft_()) { + throw 'Error! Overflowed the bit stream! n=' + n + ', bytePtr=' + bytePtr + + ', bytes.length=' + bytes.length + ', bitPtr=' + bitPtr; + } + const movePointers = opt_movePointers || false; - const bytes = this.bytes; + let curPage = this.bytes; + let pageIndex = 0; let bytePtr = this.bytePtr; let bitPtr = this.bitPtr; let result = 0; @@ -131,23 +173,22 @@ bitjs.io.BitStream = class { // shifting/masking it to just extract the bits we want. // This could be considerably faster when reading more than 3 or 4 bits at a time. while (num > 0) { - if (bytePtr >= bytes.length) { - throw 'Error! Overflowed the bit stream! n=' + n + ', bytePtr=' + bytePtr + ', bytes.length=' + - bytes.length + ', bitPtr=' + bitPtr; + if (bytePtr >= curPage.length && this.pages_.length > 0) { + curPage = this.pages_[pageIndex++]; + bytePtr = 0; } const numBitsLeftInThisByte = (8 - bitPtr); if (num >= numBitsLeftInThisByte) { result <<= numBitsLeftInThisByte; - result |= (bitjs.io.BitStream.BITMASK[numBitsLeftInThisByte] & bytes[bytePtr]); + result |= (bitjs.io.BitStream.BITMASK[numBitsLeftInThisByte] & curPage[bytePtr]); bytePtr++; bitPtr = 0; num -= numBitsLeftInThisByte; - } - else { + } else { result <<= num; const numBits = 8 - num - bitPtr; - result |= ((bytes[bytePtr] & (bitjs.io.BitStream.BITMASK[num] << numBits)) >> numBits); + result |= ((curPage[bytePtr] & (bitjs.io.BitStream.BITMASK[num] << numBits)) >> numBits); bitPtr += num; num = 0; @@ -155,8 +196,7 @@ bitjs.io.BitStream = class { } if (movePointers) { - this.bitPtr = bitPtr; - this.bytePtr = bytePtr; + this.movePointer_(NUM); } return result; @@ -206,14 +246,25 @@ bitjs.io.BitStream = class { this.readBits(1); } - if (this.bytePtr + num > this.bytes.byteLength) { + const numBytesLeft = this.getNumBitsLeft_() / 8; + if (num > numBytesLeft) { throw 'Error! Overflowed the bit stream! n=' + num + ', bytePtr=' + this.bytePtr + ', bytes.length=' + this.bytes.length + ', bitPtr=' + this.bitPtr; } - const result = this.bytes.subarray(this.bytePtr, this.bytePtr + num); - const movePointers = opt_movePointers || false; + const result = new Uint8Array(num); + let curPage = this.bytes; + let pageIndex = 0; + let bytePtr = this.bytePtr; + for (let i = 0; i < num; ++i) { + result[i] = curPage[bytePtr++]; + if (bytePtr >= curPage.length) { + curPage = this.pages_[pageIndex++]; + bytePtr = 0; + } + } + if (movePointers) { this.bytePtr += num; } @@ -228,6 +279,18 @@ bitjs.io.BitStream = class { readBytes(n) { return this.peekBytes(n, true); } + + /** + * Feeds more bytes into the back of the stream. + * @param {ArrayBuffer} ab + */ + push(ab) { + if (!(ab instanceof ArrayBuffer)) { + throw 'Error! BitStream.push() called with an invalid ArrayBuffer object'; + } + + this.pages_.push(new Uint8Array(ab)); + } } // mask for getting N number of bits (0-8) diff --git a/io/bytestream.js b/io/bytestream.js index 547e368..4e678e1 100644 --- a/io/bytestream.js +++ b/io/bytestream.js @@ -13,7 +13,6 @@ var bitjs = bitjs || {}; bitjs.io = bitjs.io || {}; -// TODO: Add method for pushing bits (multiple arrays) and have tests. // TODO: Add method for tee-ing off the stream with tests. /** * This object allows you to peek and consume bytes as numbers and strings @@ -41,7 +40,7 @@ bitjs.io.ByteStream = class { * Returns how many bytes are currently in the stream left to be read. * @private */ - getBytesLeft_() { + getNumBytesLeft_() { const bytesInCurrentPage = (this.bytes.byteLength - this.ptr); return this.pages_.reduce((acc, arr) => acc + arr.length, bytesInCurrentPage); } @@ -78,7 +77,7 @@ bitjs.io.ByteStream = class { // TODO: Throw an error if n > 4. - if (this.getBytesLeft_() < num) { + if (this.getNumBytesLeft_() < num) { throw 'Error! Overflowed the byte stream while peekNumber()! n=' + num + ', ptr=' + this.ptr + ', bytes.length=' + this.bytes.length; } @@ -157,7 +156,7 @@ bitjs.io.ByteStream = class { return new Uint8Array(); } - const totalBytesLeft = this.getBytesLeft_(); + const totalBytesLeft = this.getNumBytesLeft_(); if (num > totalBytesLeft) { throw 'Error! Overflowed the byte stream! n=' + num + ', ptr=' + this.ptr + ', bytes.length=' + this.bytes.length; @@ -204,7 +203,7 @@ bitjs.io.ByteStream = class { return ''; } - const totalBytesLeft = this.getBytesLeft_(); + const totalBytesLeft = this.getNumBytesLeft_(); if (num > totalBytesLeft) { throw 'Error! Overflowed the byte stream while peekString()! n=' + num + ', ptr=' + this.ptr + ', bytes.length=' + this.bytes.length; @@ -243,7 +242,7 @@ bitjs.io.ByteStream = class { */ push(ab) { if (!(ab instanceof ArrayBuffer)) { - throw 'Error! push() called with an invalid ArrayBuffer object'; + throw 'Error! ByteStream.push() called with an invalid ArrayBuffer object'; } this.pages_.push(new Uint8Array(ab)); diff --git a/tests/io-bitstream-test.html b/tests/io-bitstream-test.html index d4623f4..21349b7 100644 --- a/tests/io-bitstream-test.html +++ b/tests/io-bitstream-test.html @@ -73,7 +73,45 @@ assertThrows(() => stream.readBytes(3), 'Did not throw when trying to read bytes past the end of the stream'); - } + }, + + testBitStreamReadAfterPush_RTL() { + array = new Uint8Array(1); + array[0] = Number('0b01010110'); + const stream = new bitjs.io.BitStream(array.buffer, true /* rtl */); + const readBits = stream.readBits(8); + assertEquals(0x56, readBits, 'Could not read 8 bits: ' + readBits); + assertThrows(() => stream.readBits(4), + 'Did not throw when trying to read a bit past the end of the stream'); + + const anotherArray = new Uint8Array(1); + anotherArray[0] = Number('0b01010110'); + stream.push(anotherArray.buffer); + + assertEquals(0x5, stream.readBits(4), + 'Could not read in next 4 bits after pushing in RTL'); + assertEquals(0x6, stream.readBits(4), + 'Could not read in next 4 bits after pushing in RTL'); + }, + + testBitStreamReadAfterPush_LTR() { + array = new Uint8Array(1); + array[0] = Number('0b01010110'); + const stream = new bitjs.io.BitStream(array.buffer, false /* rtl */); + const readBits = stream.readBits(8); + assertEquals(0x56, readBits, 'Could not read 8 bits: ' + readBits); + assertThrows(() => stream.readBits(4), + 'Did not throw when trying to read a bit past the end of the stream'); + + const anotherArray = new Uint8Array(1); + anotherArray[0] = Number('0b01010110'); + stream.push(anotherArray.buffer); + + assertEquals(0x6, stream.readBits(4), + 'Could not read in next 4 bits after pushing in LTR'); + assertEquals(0x5, stream.readBits(4), + 'Could not read in next 4 bits after pushing in LTR'); + }, }, });