diff --git a/io/bitstream.js b/io/bitstream.js index 148691d..b6db701 100644 --- a/io/bitstream.js +++ b/io/bitstream.js @@ -1,7 +1,7 @@ /* * bitstream.js * - * Provides readers for bitstreams. + * A pull stream for binary bits. * * Licensed under the MIT License * @@ -118,6 +118,8 @@ export class BitStream { peekBits_ltm(n, opt_movePointers) { const NUM = parseInt(n, 10); let num = NUM; + + // TODO: Handle this consistently between ByteStream and BitStream. ByteStream throws an error. if (n !== num || num <= 0) { return 0; } @@ -176,6 +178,8 @@ export class BitStream { peekBits_mtl(n, opt_movePointers) { const NUM = parseInt(n, 10); let num = NUM; + + // TODO: Handle this consistently between ByteStream and BitStream. ByteStream throws an error. if (n !== num || num <= 0) { return 0; } @@ -305,4 +309,30 @@ export class BitStream { readBytes(n) { return this.peekBytes(n, true); } + + /** + * Skips n bits in the stream. Will throw an error if n is < 0 or greater than the number of + * bits left in the stream. + * @param {number} n The number of bits to skip. Must be a positive integer. + * @returns {BitStream} Returns this BitStream for chaining. + */ + skip(n) { + const num = parseInt(n, 10); + if (n !== num || num < 0) throw `Error! Called skip(${n})`; + else if (num === 0) return this; + + const totalBitsLeft = this.getNumBitsLeft(); + if (n > totalBitsLeft) { + throw `Error! Overflowed the bit stream for skip(${n}), ptrs=${this.bytePtr}/${this.bitPtr}`; + } + + this.bitsRead_ += num; + this.bitPtr += num; + if (this.bitPtr >= 8) { + this.bytePtr += Math.floor(this.bitPtr / 8); + this.bitPtr %= 8; + } + + return this; + } } diff --git a/io/bytestream.js b/io/bytestream.js index 5c2fae0..1353d61 100644 --- a/io/bytestream.js +++ b/io/bytestream.js @@ -1,7 +1,7 @@ /* * bytestream.js * - * Provides readers for byte streams. + * A pull stream for bytes. * * Licensed under the MIT License * @@ -88,7 +88,7 @@ export class ByteStream { } /** - * Returns how many bytes have been read in the stream since the beginning of time. + * Returns how many bytes have been consumed (read or skipped) since the beginning of time. * @returns {number} */ getNumBytesRead() { diff --git a/tests/io-bitstream.spec.js b/tests/io-bitstream.spec.js index 31cbd7f..c38a377 100644 --- a/tests/io-bitstream.spec.js +++ b/tests/io-bitstream.spec.js @@ -23,76 +23,148 @@ describe('bitjs.io.BitStream', () => { expect(() => new BitStream()).throws(); }); - it('BitPeekAndRead_MTL', () => { - const stream = new BitStream(array.buffer, true /* mtl */); + describe('Most-to-Least', () => { + it('peek() and read()', () => { + const stream = new BitStream(array.buffer, true /** mtl */); - expect(stream.peekBits(0)).equals(0); - expect(stream.peekBits(-1)).equals(0); - expect(stream.bytePtr).equals(0); - expect(stream.bitPtr).equals(0); + expect(stream.peekBits(0)).equals(0); + expect(stream.peekBits(-1)).equals(0); + expect(stream.bytePtr).equals(0); + expect(stream.bitPtr).equals(0); - // 0110 = 2 + 4 = 6 - expect(stream.readBits(4)).equals(6); - expect(stream.getNumBitsRead()).equals(4); + // 0110 + expect(stream.readBits(4)).equals(0b0110); + expect(stream.getNumBitsRead()).equals(4); - // 0101 011 = 1 + 2 + 8 + 32 = 43 - expect(stream.readBits(7)).equals(43); - // 00101 01100101 01 = 1 + 4 + 16 + 128 + 256 + 1024 + 4096 = 5525 - expect(stream.readBits(15)).equals(5525); - // 10010 = 2 + 16 = 18 - expect(stream.readBits(5)).equals(18); + // 0101 011 + expect(stream.readBits(7)).equals(0b0101011); + // 00101 01100101 01 + expect(stream.readBits(15)).equals(0b001010110010101); + // 10010 + expect(stream.readBits(5)).equals(0b10010); - // Ensure the last bit is read, even if we flow past the end of the stream. - expect(stream.readBits(2)).equals(1); + // Ensure the last bit is read, even if we flow past the end of the stream. + expect(stream.readBits(2)).equals(1); - expect(stream.getNumBitsRead()).equals(33); + expect(stream.getNumBitsRead()).equals(33); + }); + + it('skip() works correctly', () => { + const stream = new BitStream(array.buffer, true /** mtl */); + + expect(stream.skip(0)).equals(stream); + expect(stream.getNumBitsRead()).equals(0); + expect(stream.skip(3)).equals(stream); + expect(stream.getNumBitsRead()).equals(3); + expect(stream.readBits(3)).equals(0b001); + expect(stream.getNumBitsRead()).equals(6); + }); + + it('skip() works over byte boundary', () => { + const stream = new BitStream(array.buffer, true /** mtl */); + expect(stream.readBits(5)).equals(0b01100); + stream.skip(5); + expect(stream.getNumBitsRead()).equals(10); + expect(stream.readBits(5)).equals(0b10010); + }); + + it('skip() throws errors if overflowed', () => { + const stream = new BitStream(array.buffer, true /** mtl */); + expect(() => stream.skip(-1)).throws(); + stream.readBits(30); + expect(() => stream.skip(3)).throws(); + }); }); - it('BitPeekAndRead_LTM', () => { - /** @type {BitStream} */ - const stream = new BitStream(array.buffer, false /* mtl */); + describe('Least-to-Most', () => { + it('peek() and read()', () => { + /** @type {BitStream} */ + const stream = new BitStream(array.buffer, false /** mtl */); - expect(stream.peekBits(0)).equals(0); - expect(stream.peekBits(-1)).equals(0); - expect(stream.bytePtr).equals(0); - expect(stream.bitPtr).equals(0); + expect(stream.peekBits(0)).equals(0); + expect(stream.peekBits(-1)).equals(0); + expect(stream.bytePtr).equals(0); + expect(stream.bitPtr).equals(0); - // 0101 = 2 + 4 = 6 - expect(stream.peekBits(4)).equals(5); - expect(stream.readBits(4)).equals(5); - // 101 0110 = 2 + 4 + 16 + 64 = 86 - expect(stream.readBits(7)).equals(86); - // 01 01100101 01100 = 4 + 8 + 32 + 128 + 1024 + 2048 + 8192 = 11436 - expect(stream.readBits(15)).equals(11436); - // 11001 = 1 + 8 + 16 = 25 - expect(stream.readBits(5)).equals(25); + // 0101 + expect(stream.peekBits(4)).equals(0b0101); + expect(stream.readBits(4)).equals(0b0101); + // 101 0110 + expect(stream.readBits(7)).equals(0b1010110); + // 01 01100101 01100 + expect(stream.readBits(15)).equals(0b010110010101100); + // 11001 + expect(stream.readBits(5)).equals(0b11001); - // Only 1 bit left in the buffer, make sure it reads in, even if we over-read. - expect(stream.readBits(2)).equals(0); + // Only 1 bit left in the buffer, make sure it reads in, even if we over-read. + expect(stream.readBits(2)).equals(0); + }); + + it('skip() works correctly', () => { + const stream = new BitStream(array.buffer, false /** mtl */); + + expect(stream.skip(0)).equals(stream); + expect(stream.getNumBitsRead()).equals(0); + expect(stream.skip(3)).equals(stream); + expect(stream.getNumBitsRead()).equals(3); + expect(stream.readBits(3)).equals(0b100); + expect(stream.getNumBitsRead()).equals(6); + }); + + it('skip() works over byte boundary', () => { + const stream = new BitStream(array.buffer, false /** mtl */); + expect(stream.readBits(5)).equals(0b00101); + stream.skip(5); + expect(stream.getNumBitsRead()).equals(10); + expect(stream.readBits(5)).equals(0b11001); + }); + + it('skip() throws errors if overflowed', () => { + const stream = new BitStream(array.buffer, false /** mtl */); + expect(() => stream.skip(-1)).throws(); + stream.readBits(30); + expect(() => stream.skip(3)).throws(); + }); }); - it('BitStreamReadBytes', () => { - array[1] = Number('0b01010110'); - const stream = new BitStream(array.buffer); + describe('bytes', () => { + it('peekBytes() and readBytes()', () => { + array[1] = Number('0b01010110'); + const stream = new BitStream(array.buffer); - let twoBytes = stream.peekBytes(2); - expect(twoBytes instanceof Uint8Array).true; - expect(twoBytes.byteLength).equals(2); - expect(twoBytes[0]).equals(Number('0b01100101')); - expect(twoBytes[1]).equals(Number('0b01010110')); + let twoBytes = stream.peekBytes(2); + expect(twoBytes instanceof Uint8Array).true; + expect(twoBytes.byteLength).equals(2); + expect(twoBytes[0]).equals(Number('0b01100101')); + expect(twoBytes[1]).equals(Number('0b01010110')); - twoBytes = stream.readBytes(2); - expect(twoBytes instanceof Uint8Array).true; - expect(twoBytes.byteLength).equals(2); - expect(twoBytes[0]).equals(Number('0b01100101')); - expect(twoBytes[1]).equals(Number('0b01010110')); + twoBytes = stream.readBytes(2); + expect(twoBytes instanceof Uint8Array).true; + expect(twoBytes.byteLength).equals(2); + expect(twoBytes[0]).equals(Number('0b01100101')); + expect(twoBytes[1]).equals(Number('0b01010110')); - expect(() => stream.readBytes(3)).throws(); - }); + expect(() => stream.readBytes(3)).throws(); + }); - it('throws an error with weird values of peekBytes()', () => { - /** @type {BitStream} */ - const stream = new BitStream(array.buffer); - expect(() => stream.peekBytes(-1)).throws(); + it('peekBytes(0) returns an empty array', () => { + const stream = new BitStream(array.buffer); + const arr = stream.peekBytes(0); + expect(arr.byteLength).equals(0); + }); + + it('peekBytes() should ignore bits until byte-aligned', () => { + array[1] = Number('0b01010110'); + const stream = new BitStream(array.buffer); + stream.skip(3); + const bytes = stream.readBytes(2); + expect(bytes[0]).equals(0b01010110); + expect(bytes[1]).equals(0b01100101); + }) + + it('throws an error with weird values of peekBytes()', () => { + const stream = new BitStream(array.buffer); + expect(() => stream.peekBytes(-1)).throws(); + }); }); });