diff --git a/README.md b/README.md index d2c2f5d..0041892 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,10 @@ A set of tools to handle binary data in JS (using Typed Arrays). This namespace includes stream objects for reading and writing binary data at the bit and byte level: BitStream, ByteStream. ```javascript -var bstream = new bitjs.io.BitStream(someArrayBuffer, true, offset, length); -var crc = bstream.readBits(12); // read in 12 bits as CRC, advancing the pointer -var flagbits = bstream.peekBits(6); // look ahead at next 6 bits, but do not advance the pointer +import {BitStream} from './bitjs/io/bitstream.js'; +const bstream = new BitStream(someArrayBuffer, true, offset, length); +const crc = bstream.readBits(12); // read in 12 bits as CRC, advancing the pointer +const flagbits = bstream.peekBits(6); // look ahead at next 6 bits, but do not advance the pointer ``` ### bitjs.archive @@ -21,7 +22,7 @@ var flagbits = bstream.peekBits(6); // look ahead at next 6 bits, but do not adv This namespace includes objects for unarchiving binary data in popular archive formats (zip, rar, tar) providing unzip, unrar and untar capabilities via JavaScript in the browser. The unarchiving actually happens inside a Web Worker. ```javascript -var unzipper = new bitjs.archive.Unzipper(zipFileArrayBuffer); +const unzipper = new bitjs.archive.Unzipper(zipFileArrayBuffer); unzipper.addEventListener('progress', updateProgress); unzipper.addEventListener('extract', receiveOneFile); unzipper.addEventListener('finish', displayZipContents); @@ -44,7 +45,7 @@ function displayZipContents() { The unarchivers also support streaming, if you are receiving the zipped file from a slow place (a Cloud API, for instance). For example: ```javascript -var unzipper = new bitjs.archive.Unzipper(anArrayBufferWithStartingBytes); +const unzipper = new bitjs.archive.Unzipper(anArrayBufferWithStartingBytes); unzipper.addEventListener('progress', updateProgress); unzipper.addEventListener('extract', receiveOneFile); unzipper.addEventListener('finish', displayZipContents); @@ -78,7 +79,7 @@ convertWebPtoPNG(webpBuffer).then(pngBuf => { This namespace includes code for dealing with files. It includes a sniffer which detects the type of file, given an ArrayBuffer. ```javascript -let mimeType = bitjs.file.findMimeType(someArrayBuffer); +const mimeType = bitjs.file.findMimeType(someArrayBuffer); ``` ## Tests diff --git a/archive/unrar.js b/archive/unrar.js index 24552bc..2ca05d0 100644 --- a/archive/unrar.js +++ b/archive/unrar.js @@ -13,7 +13,7 @@ // This file expects to be invoked as a Worker (see onmessage below). importScripts('../io/bitstream-worker.js'); -importScripts('../io/bytestream.js'); +importScripts('../io/bytestream-worker.js'); importScripts('../io/bytebuffer.js'); importScripts('archive.js'); importScripts('rarvm.js'); diff --git a/archive/untar.js b/archive/untar.js index 99b3d60..6f6b95f 100644 --- a/archive/untar.js +++ b/archive/untar.js @@ -11,7 +11,7 @@ */ // This file expects to be invoked as a Worker (see onmessage below). -importScripts('../io/bytestream.js'); +importScripts('../io/bytestream-worker.js'); importScripts('archive.js'); const UnarchiveState = { @@ -36,26 +36,26 @@ let totalUncompressedBytesInArchive = 0; let totalFilesInArchive = 0; // Helper functions. -const info = function(str) { +const info = function (str) { postMessage(new bitjs.archive.UnarchiveInfoEvent(str)); }; -const err = function(str) { +const err = function (str) { postMessage(new bitjs.archive.UnarchiveErrorEvent(str)); }; -const postProgress = function() { +const postProgress = function () { postMessage(new bitjs.archive.UnarchiveProgressEvent( - currentFilename, - currentFileNumber, - currentBytesUnarchivedInFile, - currentBytesUnarchived, - totalUncompressedBytesInArchive, - totalFilesInArchive, - bytestream.getNumBytesRead(), + currentFilename, + currentFileNumber, + currentBytesUnarchivedInFile, + currentBytesUnarchived, + totalUncompressedBytesInArchive, + totalFilesInArchive, + bytestream.getNumBytesRead(), )); }; // Removes all characters from the first zero-byte in the string onwards. -const readCleanString = function(bstr, numBytes) { +const readCleanString = function (bstr, numBytes) { const str = bstr.readString(numBytes); const zIndex = str.indexOf(String.fromCharCode(0)); return zIndex != -1 ? str.substr(0, zIndex) : str; @@ -122,12 +122,12 @@ class TarLocalFile { bstream.readBytes(remaining); } } else if (this.typeflag == 5) { - info(" This is a directory.") + info(" This is a directory.") } } } -const untar = function() { +const untar = function () { let bstream = bytestream.tee(); // While we don't encounter an empty block, keep making TarLocalFiles. @@ -159,7 +159,7 @@ const untar = function() { // event.data.file has the first ArrayBuffer. // event.data.bytes has all subsequent ArrayBuffers. -onmessage = function(event) { +onmessage = function (event) { const bytes = event.data.file || event.data.bytes; logToConsole = !!event.data.logToConsole; @@ -178,7 +178,7 @@ onmessage = function(event) { totalUncompressedBytesInArchive = 0; totalFilesInArchive = 0; allLocalFiles = []; - + postMessage(new bitjs.archive.UnarchiveStartEvent()); unarchiveState = UnarchiveState.UNARCHIVING; diff --git a/archive/unzip.js b/archive/unzip.js index cbe06e1..aecd1e1 100644 --- a/archive/unzip.js +++ b/archive/unzip.js @@ -15,7 +15,7 @@ // This file expects to be invoked as a Worker (see onmessage below). importScripts('../io/bitstream-worker.js'); importScripts('../io/bytebuffer.js'); -importScripts('../io/bytestream.js'); +importScripts('../io/bytestream-worker.js'); importScripts('archive.js'); const UnarchiveState = { diff --git a/build/io/Makefile b/build/io/Makefile index 9e0581b..c7c3d1d 100644 --- a/build/io/Makefile +++ b/build/io/Makefile @@ -1,12 +1,31 @@ OUT_PATH=/out/io + BITSTREAM_MODULE=${OUT_PATH}/bitstream.js BITSTREAM_WORKER=${OUT_PATH}/bitstream-worker.js +BYTESTREAM_MODULE=${OUT_PATH}/bytestream.js +BYTESTREAM_WORKER=${OUT_PATH}/bytestream-worker.js + +BYTEBUFFER_MODULE=${OUT_PATH}/bytebuffer.js +BYTEBUFFER_WORKER=${OUT_PATH}/bytebuffer-worker.js + BITSTREAM_DEF=bitstream-def.js +BYTESTREAM_DEF=bytestream-def.js +BYTEBUFFER_DEF=bytebuffer-def.js DISCLAIMER="// THIS IS A GENERATED FILE! DO NOT EDIT, INSTEAD EDIT THE FILE IN bitjs/build/io." -all: ${BITSTREAM_MODULE} ${BITSTREAM_WORKER} +all: ${BITSTREAM_MODULE} ${BITSTREAM_WORKER} \ + ${BYTESTREAM_MODULE} ${BYTESTREAM_WORKER} \ + ${BYTEBUFFER_MODULE} ${BYTEBUFFER_WORKER} + +clean: + rm -rf ${BITSTREAM_MODULE} + rm -rf ${BITSTREAM_WORKER} + rm -rf ${BYTESTREAM_MODULE} + rm -rf ${BYTESTREAM_WORKER} + rm -rf ${BYTEBUFFER_MODULE} + rm -rf ${BYTEBUFFER_WORKER} ${BITSTREAM_MODULE}: Makefile ${BITSTREAM_DEF} echo ${DISCLAIMER} > ${BITSTREAM_MODULE} @@ -20,6 +39,26 @@ ${BITSTREAM_WORKER}: Makefile ${BITSTREAM_DEF} echo "bitjs.io.BitStream =" >> ${BITSTREAM_WORKER} cat ${BITSTREAM_DEF} >> ${BITSTREAM_WORKER} -clean: - rm -rf ${BITSTREAM_MODULE} - rm -rf ${BITSTREAM_WORKER} +${BYTESTREAM_MODULE}: Makefile ${BYTESTREAM_DEF} + echo ${DISCLAIMER} > ${BYTESTREAM_MODULE} + echo "export const ByteStream =" >> ${BYTESTREAM_MODULE} + cat ${BYTESTREAM_DEF} >> ${BYTESTREAM_MODULE} + +${BYTESTREAM_WORKER}: Makefile ${BYTESTREAM_DEF} + echo ${DISCLAIMER} > ${BYTESTREAM_WORKER} + echo "var bitjs = bitjs || {};" >> ${BYTESTREAM_WORKER} + echo "bitjs.io = bitjs.io || {};" >> ${BYTESTREAM_WORKER} + echo "bitjs.io.ByteStream =" >> ${BYTESTREAM_WORKER} + cat ${BYTESTREAM_DEF} >> ${BYTESTREAM_WORKER} + +${BYTEBUFFER_MODULE}: Makefile ${BYTEBUFFER_DEF} + echo ${DISCLAIMER} > ${BYTEBUFFER_MODULE} + echo "export const ByteBuffer =" >> ${BYTEBUFFER_MODULE} + cat ${BYTEBUFFER_DEF} >> ${BYTEBUFFER_MODULE} + +${BYTEBUFFER_WORKER}: Makefile ${BYTEBUFFER_DEF} + echo ${DISCLAIMER} > ${BYTEBUFFER_WORKER} + echo "var bitjs = bitjs || {};" >> ${BYTEBUFFER_WORKER} + echo "bitjs.io = bitjs.io || {};" >> ${BYTEBUFFER_WORKER} + echo "bitjs.io.ByteBuffer =" >> ${BYTEBUFFER_WORKER} + cat ${BYTEBUFFER_DEF} >> ${BYTEBUFFER_WORKER} diff --git a/build/io/bitstream-def.js b/build/io/bitstream-def.js index 5f99455..606a279 100644 --- a/build/io/bitstream-def.js +++ b/build/io/bitstream-def.js @@ -283,5 +283,4 @@ } return BitStream; - })(); diff --git a/build/io/bytebuffer-def.js b/build/io/bytebuffer-def.js new file mode 100644 index 0000000..19f2f52 --- /dev/null +++ b/build/io/bytebuffer-def.js @@ -0,0 +1,117 @@ +/* + * bytebuffer-def.js + * + * Provides a writer for bytes. + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Google Inc. + * Copyright(c) 2011 antimatter15 + */ + +(function () { + /** + * A write-only Byte buffer which uses a Uint8 Typed Array as a backing store. + */ + class ByteBuffer { + /** + * @param {number} numBytes The number of bytes to allocate. + */ + constructor(numBytes) { + if (typeof numBytes != typeof 1 || numBytes <= 0) { + throw "Error! ByteBuffer initialized with '" + numBytes + "'"; + } + this.data = new Uint8Array(numBytes); + this.ptr = 0; + } + + + /** + * @param {number} b The byte to insert. + */ + insertByte(b) { + // TODO: throw if byte is invalid? + this.data[this.ptr++] = b; + } + + /** + * @param {Array.|Uint8Array|Int8Array} bytes The bytes to insert. + */ + insertBytes(bytes) { + // TODO: throw if bytes is invalid? + this.data.set(bytes, this.ptr); + this.ptr += bytes.length; + } + + /** + * Writes an unsigned number into the next n bytes. If the number is too large + * to fit into n bytes or is negative, an error is thrown. + * @param {number} num The unsigned number to write. + * @param {number} numBytes The number of bytes to write the number into. + */ + writeNumber(num, numBytes) { + if (numBytes < 1 || !numBytes) { + throw 'Trying to write into too few bytes: ' + numBytes; + } + if (num < 0) { + throw 'Trying to write a negative number (' + num + + ') as an unsigned number to an ArrayBuffer'; + } + if (num > (Math.pow(2, numBytes * 8) - 1)) { + throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes'; + } + + // Roll 8-bits at a time into an array of bytes. + const bytes = []; + while (numBytes-- > 0) { + const eightBits = num & 255; + bytes.push(eightBits); + num >>= 8; + } + + this.insertBytes(bytes); + } + + /** + * Writes a signed number into the next n bytes. If the number is too large + * to fit into n bytes, an error is thrown. + * @param {number} num The signed number to write. + * @param {number} numBytes The number of bytes to write the number into. + */ + writeSignedNumber(num, numBytes) { + if (numBytes < 1) { + throw 'Trying to write into too few bytes: ' + numBytes; + } + + const HALF = Math.pow(2, (numBytes * 8) - 1); + if (num >= HALF || num < -HALF) { + throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes'; + } + + // Roll 8-bits at a time into an array of bytes. + const bytes = []; + while (numBytes-- > 0) { + const eightBits = num & 255; + bytes.push(eightBits); + num >>= 8; + } + + this.insertBytes(bytes); + } + + /** + * @param {string} str The ASCII string to write. + */ + writeASCIIString(str) { + for (let i = 0; i < str.length; ++i) { + const curByte = str.charCodeAt(i); + if (curByte < 0 || curByte > 255) { + throw 'Trying to write a non-ASCII string!'; + } + this.insertByte(curByte); + } + }; + } + + return ByteBuffer; +})(); diff --git a/build/io/bytestream-def.js b/build/io/bytestream-def.js new file mode 100644 index 0000000..5db999d --- /dev/null +++ b/build/io/bytestream-def.js @@ -0,0 +1,308 @@ +/* + * bytestream-def.js + * + * Provides readers for byte streams. + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Google Inc. + * Copyright(c) 2011 antimatter15 + */ + +(function () { + /** + * This object allows you to peek and consume bytes as numbers and strings out + * of a stream. More bytes can be pushed into the back of the stream via the + * push() method. + */ + class ByteStream { + /** + * @param {ArrayBuffer} ab The ArrayBuffer object. + * @param {number=} opt_offset The offset into the ArrayBuffer + * @param {number=} opt_length The length of this ByteStream + */ + constructor(ab, opt_offset, opt_length) { + if (!(ab instanceof ArrayBuffer)) { + throw 'Error! BitArray constructed with an invalid ArrayBuffer object'; + } + + const offset = opt_offset || 0; + const length = opt_length || ab.byteLength; + + /** + * The current page of bytes in the stream. + * @type {Uint8Array} + * @private + */ + this.bytes = new Uint8Array(ab, offset, length); + + /** + * The next pages of bytes in the stream. + * @type {Array} + * @private + */ + this.pages_ = []; + + /** + * The byte in the current page that we will read next. + * @type {Number} + * @private + */ + this.ptr = 0; + + /** + * An ever-increasing number. + * @type {Number} + * @private + */ + this.bytesRead_ = 0; + } + + /** + * Returns how many bytes have been read in the stream since the beginning of time. + */ + getNumBytesRead() { + return this.bytesRead_; + } + + /** + * Returns how many bytes are currently in the stream left to be read. + */ + getNumBytesLeft() { + const bytesInCurrentPage = (this.bytes.byteLength - this.ptr); + return this.pages_.reduce((acc, arr) => acc + arr.length, bytesInCurrentPage); + } + + /** + * Move the pointer ahead n bytes. If the pointer is at the end of the current array + * of bytes and we have another page of bytes, point at the new page. This is a private + * method, no validation is done. + * @param {number} n Number of bytes to increment. + * @private + */ + movePointer_(n) { + this.ptr += n; + this.bytesRead_ += n; + while (this.ptr >= this.bytes.length && this.pages_.length > 0) { + this.ptr -= this.bytes.length; + this.bytes = this.pages_.shift(); + } + } + + /** + * Peeks at the next n bytes as an unsigned number but does not advance the + * pointer. + * @param {number} n The number of bytes to peek at. Must be a positive integer. + * @return {number} The n bytes interpreted as an unsigned number. + */ + peekNumber(n) { + const num = parseInt(n, 10); + if (n !== num || num < 0) { + throw 'Error! Called peekNumber() with a non-positive integer'; + } else if (num === 0) { + return 0; + } + + if (n > 4) { + throw 'Error! Called peekNumber(' + n + + ') but this method can only reliably read numbers up to 4 bytes long'; + } + + if (this.getNumBytesLeft() < num) { + throw 'Error! Overflowed the byte stream while peekNumber()! n=' + num + + ', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft(); + } + + let result = 0; + let curPage = this.bytes; + let pageIndex = 0; + let ptr = this.ptr; + for (let i = 0; i < num; ++i) { + result |= (curPage[ptr++] << (i * 8)); + + if (ptr >= curPage.length) { + curPage = this.pages_[pageIndex++]; + ptr = 0; + } + } + + return result; + } + + + /** + * Returns the next n bytes as an unsigned number (or -1 on error) + * and advances the stream pointer n bytes. + * @param {number} n The number of bytes to read. Must be a positive integer. + * @return {number} The n bytes interpreted as an unsigned number. + */ + readNumber(n) { + const num = this.peekNumber(n); + this.movePointer_(n); + return num; + } + + + /** + * Returns the next n bytes as a signed number but does not advance the + * pointer. + * @param {number} n The number of bytes to read. Must be a positive integer. + * @return {number} The bytes interpreted as a signed number. + */ + peekSignedNumber(n) { + let num = this.peekNumber(n); + const HALF = Math.pow(2, (n * 8) - 1); + const FULL = HALF * 2; + + if (num >= HALF) num -= FULL; + + return num; + } + + + /** + * Returns the next n bytes as a signed number and advances the stream pointer. + * @param {number} n The number of bytes to read. Must be a positive integer. + * @return {number} The bytes interpreted as a signed number. + */ + readSignedNumber(n) { + const num = this.peekSignedNumber(n); + this.movePointer_(n); + return num; + } + + + /** + * This returns n bytes as a sub-array, advancing the pointer if movePointers + * is true. + * @param {number} n The number of bytes to read. Must be a positive integer. + * @param {boolean} movePointers Whether to move the pointers. + * @return {Uint8Array} The subarray. + */ + peekBytes(n, movePointers) { + const num = parseInt(n, 10); + if (n !== num || num < 0) { + throw 'Error! Called peekBytes() with a non-positive integer'; + } else if (num === 0) { + return new Uint8Array(); + } + + const totalBytesLeft = this.getNumBytesLeft(); + if (num > totalBytesLeft) { + throw 'Error! Overflowed the byte stream during peekBytes! n=' + num + + ', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft(); + } + + const result = new Uint8Array(num); + let curPage = this.bytes; + let ptr = this.ptr; + let bytesLeftToCopy = num; + let pageIndex = 0; + while (bytesLeftToCopy > 0) { + const bytesLeftInPage = curPage.length - ptr; + const sourceLength = Math.min(bytesLeftToCopy, bytesLeftInPage); + + result.set(curPage.subarray(ptr, ptr + sourceLength), num - bytesLeftToCopy); + + ptr += sourceLength; + if (ptr >= curPage.length) { + curPage = this.pages_[pageIndex++]; + ptr = 0; + } + + bytesLeftToCopy -= sourceLength; + } + + if (movePointers) { + this.movePointer_(num); + } + + return result; + } + + /** + * Reads the next n bytes as a sub-array. + * @param {number} n The number of bytes to read. Must be a positive integer. + * @return {Uint8Array} The subarray. + */ + readBytes(n) { + return this.peekBytes(n, true); + } + + /** + * Peeks at the next n bytes as an ASCII string but does not advance the pointer. + * @param {number} n The number of bytes to peek at. Must be a positive integer. + * @return {string} The next n bytes as a string. + */ + peekString(n) { + const num = parseInt(n, 10); + if (n !== num || num < 0) { + throw 'Error! Called peekString() with a non-positive integer'; + } else if (num === 0) { + return ''; + } + + const totalBytesLeft = this.getNumBytesLeft(); + if (num > totalBytesLeft) { + throw 'Error! Overflowed the byte stream while peekString()! n=' + num + + ', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft(); + } + + let result = new Array(num); + let curPage = this.bytes; + let pageIndex = 0; + let ptr = this.ptr; + for (let i = 0; i < num; ++i) { + result[i] = String.fromCharCode(curPage[ptr++]); + if (ptr >= curPage.length) { + curPage = this.pages_[pageIndex++]; + ptr = 0; + } + } + + return result.join(''); + } + + /** + * Returns the next n bytes as an ASCII string and advances the stream pointer + * n bytes. + * @param {number} n The number of bytes to read. Must be a positive integer. + * @return {string} The next n bytes as a string. + */ + readString(n) { + const strToReturn = this.peekString(n); + this.movePointer_(n); + return strToReturn; + } + + /** + * Feeds more bytes into the back of the stream. + * @param {ArrayBuffer} ab + */ + push(ab) { + if (!(ab instanceof ArrayBuffer)) { + throw 'Error! ByteStream.push() called with an invalid ArrayBuffer object'; + } + + this.pages_.push(new Uint8Array(ab)); + // If the pointer is at the end of the current page of bytes, this will advance + // to the next page. + this.movePointer_(0); + } + + /** + * Creates a new ByteStream from this ByteStream that can be read / peeked. + * @return {ByteStream} A clone of this ByteStream. + */ + tee() { + const clone = new ByteStream(this.bytes.buffer); + clone.bytes = this.bytes; + clone.ptr = this.ptr; + clone.pages_ = this.pages_.slice(); + clone.bytesRead_ = this.bytesRead_; + return clone; + } + } + + return ByteStream; +})(); diff --git a/io/bitstream-worker.js b/io/bitstream-worker.js index 5b31930..90500a9 100644 --- a/io/bitstream-worker.js +++ b/io/bitstream-worker.js @@ -287,5 +287,4 @@ bitjs.io.BitStream = } return BitStream; - })(); diff --git a/io/bitstream.js b/io/bitstream.js index 0ac4606..525cf7e 100644 --- a/io/bitstream.js +++ b/io/bitstream.js @@ -285,5 +285,4 @@ export const BitStream = } return BitStream; - })(); diff --git a/io/bytebuffer-worker.js b/io/bytebuffer-worker.js new file mode 100644 index 0000000..8e2420f --- /dev/null +++ b/io/bytebuffer-worker.js @@ -0,0 +1,121 @@ +// THIS IS A GENERATED FILE! DO NOT EDIT, INSTEAD EDIT THE FILE IN bitjs/build/io. +var bitjs = bitjs || {}; +bitjs.io = bitjs.io || {}; +bitjs.io.ByteBuffer = +/* + * bytebuffer-def.js + * + * Provides a writer for bytes. + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Google Inc. + * Copyright(c) 2011 antimatter15 + */ + +(function () { + /** + * A write-only Byte buffer which uses a Uint8 Typed Array as a backing store. + */ + class ByteBuffer { + /** + * @param {number} numBytes The number of bytes to allocate. + */ + constructor(numBytes) { + if (typeof numBytes != typeof 1 || numBytes <= 0) { + throw "Error! ByteBuffer initialized with '" + numBytes + "'"; + } + this.data = new Uint8Array(numBytes); + this.ptr = 0; + } + + + /** + * @param {number} b The byte to insert. + */ + insertByte(b) { + // TODO: throw if byte is invalid? + this.data[this.ptr++] = b; + } + + /** + * @param {Array.|Uint8Array|Int8Array} bytes The bytes to insert. + */ + insertBytes(bytes) { + // TODO: throw if bytes is invalid? + this.data.set(bytes, this.ptr); + this.ptr += bytes.length; + } + + /** + * Writes an unsigned number into the next n bytes. If the number is too large + * to fit into n bytes or is negative, an error is thrown. + * @param {number} num The unsigned number to write. + * @param {number} numBytes The number of bytes to write the number into. + */ + writeNumber(num, numBytes) { + if (numBytes < 1 || !numBytes) { + throw 'Trying to write into too few bytes: ' + numBytes; + } + if (num < 0) { + throw 'Trying to write a negative number (' + num + + ') as an unsigned number to an ArrayBuffer'; + } + if (num > (Math.pow(2, numBytes * 8) - 1)) { + throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes'; + } + + // Roll 8-bits at a time into an array of bytes. + const bytes = []; + while (numBytes-- > 0) { + const eightBits = num & 255; + bytes.push(eightBits); + num >>= 8; + } + + this.insertBytes(bytes); + } + + /** + * Writes a signed number into the next n bytes. If the number is too large + * to fit into n bytes, an error is thrown. + * @param {number} num The signed number to write. + * @param {number} numBytes The number of bytes to write the number into. + */ + writeSignedNumber(num, numBytes) { + if (numBytes < 1) { + throw 'Trying to write into too few bytes: ' + numBytes; + } + + const HALF = Math.pow(2, (numBytes * 8) - 1); + if (num >= HALF || num < -HALF) { + throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes'; + } + + // Roll 8-bits at a time into an array of bytes. + const bytes = []; + while (numBytes-- > 0) { + const eightBits = num & 255; + bytes.push(eightBits); + num >>= 8; + } + + this.insertBytes(bytes); + } + + /** + * @param {string} str The ASCII string to write. + */ + writeASCIIString(str) { + for (let i = 0; i < str.length; ++i) { + const curByte = str.charCodeAt(i); + if (curByte < 0 || curByte > 255) { + throw 'Trying to write a non-ASCII string!'; + } + this.insertByte(curByte); + } + }; + } + + return ByteBuffer; +})(); diff --git a/io/bytebuffer.js b/io/bytebuffer.js index 71f7401..8426ce7 100644 --- a/io/bytebuffer.js +++ b/io/bytebuffer.js @@ -1,5 +1,7 @@ +// THIS IS A GENERATED FILE! DO NOT EDIT, INSTEAD EDIT THE FILE IN bitjs/build/io. +export const ByteBuffer = /* - * bytestream.js + * bytebuffer-def.js * * Provides a writer for bytes. * @@ -9,109 +11,109 @@ * Copyright(c) 2011 antimatter15 */ -var bitjs = bitjs || {}; -bitjs.io = bitjs.io || {}; - - -/** - * A write-only Byte buffer which uses a Uint8 Typed Array as a backing store. - */ -bitjs.io.ByteBuffer = class { +(function () { /** - * @param {number} numBytes The number of bytes to allocate. + * A write-only Byte buffer which uses a Uint8 Typed Array as a backing store. */ - constructor(numBytes) { - if (typeof numBytes != typeof 1 || numBytes <= 0) { - throw "Error! ByteBuffer initialized with '" + numBytes + "'"; - } - this.data = new Uint8Array(numBytes); - this.ptr = 0; - } - - - /** - * @param {number} b The byte to insert. - */ - insertByte(b) { - // TODO: throw if byte is invalid? - this.data[this.ptr++] = b; - } - - /** - * @param {Array.|Uint8Array|Int8Array} bytes The bytes to insert. - */ - insertBytes(bytes) { - // TODO: throw if bytes is invalid? - this.data.set(bytes, this.ptr); - this.ptr += bytes.length; - } - - /** - * Writes an unsigned number into the next n bytes. If the number is too large - * to fit into n bytes or is negative, an error is thrown. - * @param {number} num The unsigned number to write. - * @param {number} numBytes The number of bytes to write the number into. - */ - writeNumber(num, numBytes) { - if (numBytes < 1 || !numBytes) { - throw 'Trying to write into too few bytes: ' + numBytes; - } - if (num < 0) { - throw 'Trying to write a negative number (' + num + - ') as an unsigned number to an ArrayBuffer'; - } - if (num > (Math.pow(2, numBytes * 8) - 1)) { - throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes'; - } - - // Roll 8-bits at a time into an array of bytes. - const bytes = []; - while (numBytes-- > 0) { - const eightBits = num & 255; - bytes.push(eightBits); - num >>= 8; - } - - this.insertBytes(bytes); - } - - /** - * Writes a signed number into the next n bytes. If the number is too large - * to fit into n bytes, an error is thrown. - * @param {number} num The signed number to write. - * @param {number} numBytes The number of bytes to write the number into. - */ - writeSignedNumber(num, numBytes) { - if (numBytes < 1) { - throw 'Trying to write into too few bytes: ' + numBytes; - } - - const HALF = Math.pow(2, (numBytes * 8) - 1); - if (num >= HALF || num < -HALF) { - throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes'; - } - - // Roll 8-bits at a time into an array of bytes. - const bytes = []; - while (numBytes-- > 0) { - const eightBits = num & 255; - bytes.push(eightBits); - num >>= 8; - } - - this.insertBytes(bytes); - } - - /** - * @param {string} str The ASCII string to write. - */ - writeASCIIString(str) { - for (let i = 0; i < str.length; ++i) { - const curByte = str.charCodeAt(i); - if (curByte < 0 || curByte > 255) { - throw 'Trying to write a non-ASCII string!'; + class ByteBuffer { + /** + * @param {number} numBytes The number of bytes to allocate. + */ + constructor(numBytes) { + if (typeof numBytes != typeof 1 || numBytes <= 0) { + throw "Error! ByteBuffer initialized with '" + numBytes + "'"; } - this.insertByte(curByte); + this.data = new Uint8Array(numBytes); + this.ptr = 0; } - }; -} + + + /** + * @param {number} b The byte to insert. + */ + insertByte(b) { + // TODO: throw if byte is invalid? + this.data[this.ptr++] = b; + } + + /** + * @param {Array.|Uint8Array|Int8Array} bytes The bytes to insert. + */ + insertBytes(bytes) { + // TODO: throw if bytes is invalid? + this.data.set(bytes, this.ptr); + this.ptr += bytes.length; + } + + /** + * Writes an unsigned number into the next n bytes. If the number is too large + * to fit into n bytes or is negative, an error is thrown. + * @param {number} num The unsigned number to write. + * @param {number} numBytes The number of bytes to write the number into. + */ + writeNumber(num, numBytes) { + if (numBytes < 1 || !numBytes) { + throw 'Trying to write into too few bytes: ' + numBytes; + } + if (num < 0) { + throw 'Trying to write a negative number (' + num + + ') as an unsigned number to an ArrayBuffer'; + } + if (num > (Math.pow(2, numBytes * 8) - 1)) { + throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes'; + } + + // Roll 8-bits at a time into an array of bytes. + const bytes = []; + while (numBytes-- > 0) { + const eightBits = num & 255; + bytes.push(eightBits); + num >>= 8; + } + + this.insertBytes(bytes); + } + + /** + * Writes a signed number into the next n bytes. If the number is too large + * to fit into n bytes, an error is thrown. + * @param {number} num The signed number to write. + * @param {number} numBytes The number of bytes to write the number into. + */ + writeSignedNumber(num, numBytes) { + if (numBytes < 1) { + throw 'Trying to write into too few bytes: ' + numBytes; + } + + const HALF = Math.pow(2, (numBytes * 8) - 1); + if (num >= HALF || num < -HALF) { + throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes'; + } + + // Roll 8-bits at a time into an array of bytes. + const bytes = []; + while (numBytes-- > 0) { + const eightBits = num & 255; + bytes.push(eightBits); + num >>= 8; + } + + this.insertBytes(bytes); + } + + /** + * @param {string} str The ASCII string to write. + */ + writeASCIIString(str) { + for (let i = 0; i < str.length; ++i) { + const curByte = str.charCodeAt(i); + if (curByte < 0 || curByte > 255) { + throw 'Trying to write a non-ASCII string!'; + } + this.insertByte(curByte); + } + }; + } + + return ByteBuffer; +})(); diff --git a/io/bytestream-worker.js b/io/bytestream-worker.js new file mode 100644 index 0000000..93e8520 --- /dev/null +++ b/io/bytestream-worker.js @@ -0,0 +1,312 @@ +// THIS IS A GENERATED FILE! DO NOT EDIT, INSTEAD EDIT THE FILE IN bitjs/build/io. +var bitjs = bitjs || {}; +bitjs.io = bitjs.io || {}; +bitjs.io.ByteStream = +/* + * bytestream-def.js + * + * Provides readers for byte streams. + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Google Inc. + * Copyright(c) 2011 antimatter15 + */ + +(function () { + /** + * This object allows you to peek and consume bytes as numbers and strings out + * of a stream. More bytes can be pushed into the back of the stream via the + * push() method. + */ + class ByteStream { + /** + * @param {ArrayBuffer} ab The ArrayBuffer object. + * @param {number=} opt_offset The offset into the ArrayBuffer + * @param {number=} opt_length The length of this ByteStream + */ + constructor(ab, opt_offset, opt_length) { + if (!(ab instanceof ArrayBuffer)) { + throw 'Error! BitArray constructed with an invalid ArrayBuffer object'; + } + + const offset = opt_offset || 0; + const length = opt_length || ab.byteLength; + + /** + * The current page of bytes in the stream. + * @type {Uint8Array} + * @private + */ + this.bytes = new Uint8Array(ab, offset, length); + + /** + * The next pages of bytes in the stream. + * @type {Array} + * @private + */ + this.pages_ = []; + + /** + * The byte in the current page that we will read next. + * @type {Number} + * @private + */ + this.ptr = 0; + + /** + * An ever-increasing number. + * @type {Number} + * @private + */ + this.bytesRead_ = 0; + } + + /** + * Returns how many bytes have been read in the stream since the beginning of time. + */ + getNumBytesRead() { + return this.bytesRead_; + } + + /** + * Returns how many bytes are currently in the stream left to be read. + */ + getNumBytesLeft() { + const bytesInCurrentPage = (this.bytes.byteLength - this.ptr); + return this.pages_.reduce((acc, arr) => acc + arr.length, bytesInCurrentPage); + } + + /** + * Move the pointer ahead n bytes. If the pointer is at the end of the current array + * of bytes and we have another page of bytes, point at the new page. This is a private + * method, no validation is done. + * @param {number} n Number of bytes to increment. + * @private + */ + movePointer_(n) { + this.ptr += n; + this.bytesRead_ += n; + while (this.ptr >= this.bytes.length && this.pages_.length > 0) { + this.ptr -= this.bytes.length; + this.bytes = this.pages_.shift(); + } + } + + /** + * Peeks at the next n bytes as an unsigned number but does not advance the + * pointer. + * @param {number} n The number of bytes to peek at. Must be a positive integer. + * @return {number} The n bytes interpreted as an unsigned number. + */ + peekNumber(n) { + const num = parseInt(n, 10); + if (n !== num || num < 0) { + throw 'Error! Called peekNumber() with a non-positive integer'; + } else if (num === 0) { + return 0; + } + + if (n > 4) { + throw 'Error! Called peekNumber(' + n + + ') but this method can only reliably read numbers up to 4 bytes long'; + } + + if (this.getNumBytesLeft() < num) { + throw 'Error! Overflowed the byte stream while peekNumber()! n=' + num + + ', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft(); + } + + let result = 0; + let curPage = this.bytes; + let pageIndex = 0; + let ptr = this.ptr; + for (let i = 0; i < num; ++i) { + result |= (curPage[ptr++] << (i * 8)); + + if (ptr >= curPage.length) { + curPage = this.pages_[pageIndex++]; + ptr = 0; + } + } + + return result; + } + + + /** + * Returns the next n bytes as an unsigned number (or -1 on error) + * and advances the stream pointer n bytes. + * @param {number} n The number of bytes to read. Must be a positive integer. + * @return {number} The n bytes interpreted as an unsigned number. + */ + readNumber(n) { + const num = this.peekNumber(n); + this.movePointer_(n); + return num; + } + + + /** + * Returns the next n bytes as a signed number but does not advance the + * pointer. + * @param {number} n The number of bytes to read. Must be a positive integer. + * @return {number} The bytes interpreted as a signed number. + */ + peekSignedNumber(n) { + let num = this.peekNumber(n); + const HALF = Math.pow(2, (n * 8) - 1); + const FULL = HALF * 2; + + if (num >= HALF) num -= FULL; + + return num; + } + + + /** + * Returns the next n bytes as a signed number and advances the stream pointer. + * @param {number} n The number of bytes to read. Must be a positive integer. + * @return {number} The bytes interpreted as a signed number. + */ + readSignedNumber(n) { + const num = this.peekSignedNumber(n); + this.movePointer_(n); + return num; + } + + + /** + * This returns n bytes as a sub-array, advancing the pointer if movePointers + * is true. + * @param {number} n The number of bytes to read. Must be a positive integer. + * @param {boolean} movePointers Whether to move the pointers. + * @return {Uint8Array} The subarray. + */ + peekBytes(n, movePointers) { + const num = parseInt(n, 10); + if (n !== num || num < 0) { + throw 'Error! Called peekBytes() with a non-positive integer'; + } else if (num === 0) { + return new Uint8Array(); + } + + const totalBytesLeft = this.getNumBytesLeft(); + if (num > totalBytesLeft) { + throw 'Error! Overflowed the byte stream during peekBytes! n=' + num + + ', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft(); + } + + const result = new Uint8Array(num); + let curPage = this.bytes; + let ptr = this.ptr; + let bytesLeftToCopy = num; + let pageIndex = 0; + while (bytesLeftToCopy > 0) { + const bytesLeftInPage = curPage.length - ptr; + const sourceLength = Math.min(bytesLeftToCopy, bytesLeftInPage); + + result.set(curPage.subarray(ptr, ptr + sourceLength), num - bytesLeftToCopy); + + ptr += sourceLength; + if (ptr >= curPage.length) { + curPage = this.pages_[pageIndex++]; + ptr = 0; + } + + bytesLeftToCopy -= sourceLength; + } + + if (movePointers) { + this.movePointer_(num); + } + + return result; + } + + /** + * Reads the next n bytes as a sub-array. + * @param {number} n The number of bytes to read. Must be a positive integer. + * @return {Uint8Array} The subarray. + */ + readBytes(n) { + return this.peekBytes(n, true); + } + + /** + * Peeks at the next n bytes as an ASCII string but does not advance the pointer. + * @param {number} n The number of bytes to peek at. Must be a positive integer. + * @return {string} The next n bytes as a string. + */ + peekString(n) { + const num = parseInt(n, 10); + if (n !== num || num < 0) { + throw 'Error! Called peekString() with a non-positive integer'; + } else if (num === 0) { + return ''; + } + + const totalBytesLeft = this.getNumBytesLeft(); + if (num > totalBytesLeft) { + throw 'Error! Overflowed the byte stream while peekString()! n=' + num + + ', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft(); + } + + let result = new Array(num); + let curPage = this.bytes; + let pageIndex = 0; + let ptr = this.ptr; + for (let i = 0; i < num; ++i) { + result[i] = String.fromCharCode(curPage[ptr++]); + if (ptr >= curPage.length) { + curPage = this.pages_[pageIndex++]; + ptr = 0; + } + } + + return result.join(''); + } + + /** + * Returns the next n bytes as an ASCII string and advances the stream pointer + * n bytes. + * @param {number} n The number of bytes to read. Must be a positive integer. + * @return {string} The next n bytes as a string. + */ + readString(n) { + const strToReturn = this.peekString(n); + this.movePointer_(n); + return strToReturn; + } + + /** + * Feeds more bytes into the back of the stream. + * @param {ArrayBuffer} ab + */ + push(ab) { + if (!(ab instanceof ArrayBuffer)) { + throw 'Error! ByteStream.push() called with an invalid ArrayBuffer object'; + } + + this.pages_.push(new Uint8Array(ab)); + // If the pointer is at the end of the current page of bytes, this will advance + // to the next page. + this.movePointer_(0); + } + + /** + * Creates a new ByteStream from this ByteStream that can be read / peeked. + * @return {ByteStream} A clone of this ByteStream. + */ + tee() { + const clone = new ByteStream(this.bytes.buffer); + clone.bytes = this.bytes; + clone.ptr = this.ptr; + clone.pages_ = this.pages_.slice(); + clone.bytesRead_ = this.bytesRead_; + return clone; + } + } + + return ByteStream; +})(); diff --git a/io/bytestream.js b/io/bytestream.js index cb5df36..1cc605c 100644 --- a/io/bytestream.js +++ b/io/bytestream.js @@ -1,5 +1,7 @@ +// THIS IS A GENERATED FILE! DO NOT EDIT, INSTEAD EDIT THE FILE IN bitjs/build/io. +export const ByteStream = /* - * bytestream.js + * bytestream-def.js * * Provides readers for byte streams. * @@ -9,300 +11,300 @@ * Copyright(c) 2011 antimatter15 */ -var bitjs = bitjs || {}; -bitjs.io = bitjs.io || {}; - - -/** - * This object allows you to peek and consume bytes as numbers and strings out - * of a stream. More bytes can be pushed into the back of the stream via the - * push() method. - */ -bitjs.io.ByteStream = class { +(function () { /** - * @param {ArrayBuffer} ab The ArrayBuffer object. - * @param {number=} opt_offset The offset into the ArrayBuffer - * @param {number=} opt_length The length of this BitStream + * This object allows you to peek and consume bytes as numbers and strings out + * of a stream. More bytes can be pushed into the back of the stream via the + * push() method. */ - constructor(ab, opt_offset, opt_length) { - if (!(ab instanceof ArrayBuffer)) { - throw 'Error! BitArray constructed with an invalid ArrayBuffer object'; - } + class ByteStream { + /** + * @param {ArrayBuffer} ab The ArrayBuffer object. + * @param {number=} opt_offset The offset into the ArrayBuffer + * @param {number=} opt_length The length of this ByteStream + */ + constructor(ab, opt_offset, opt_length) { + if (!(ab instanceof ArrayBuffer)) { + throw 'Error! BitArray constructed with an invalid ArrayBuffer object'; + } - const offset = opt_offset || 0; - const length = opt_length || ab.byteLength; + const offset = opt_offset || 0; + const length = opt_length || ab.byteLength; + + /** + * The current page of bytes in the stream. + * @type {Uint8Array} + * @private + */ + this.bytes = new Uint8Array(ab, offset, length); + + /** + * The next pages of bytes in the stream. + * @type {Array} + * @private + */ + this.pages_ = []; + + /** + * The byte in the current page that we will read next. + * @type {Number} + * @private + */ + this.ptr = 0; + + /** + * An ever-increasing number. + * @type {Number} + * @private + */ + this.bytesRead_ = 0; + } /** - * The current page of bytes in the stream. - * @type {Uint8Array} - * @private + * Returns how many bytes have been read in the stream since the beginning of time. */ - this.bytes = new Uint8Array(ab, offset, length); + getNumBytesRead() { + return this.bytesRead_; + } /** - * The next pages of bytes in the stream. - * @type {Array} - * @private + * Returns how many bytes are currently in the stream left to be read. */ - this.pages_ = []; + getNumBytesLeft() { + const bytesInCurrentPage = (this.bytes.byteLength - this.ptr); + return this.pages_.reduce((acc, arr) => acc + arr.length, bytesInCurrentPage); + } /** - * The byte in the current page that we will read next. - * @type {Number} + * Move the pointer ahead n bytes. If the pointer is at the end of the current array + * of bytes and we have another page of bytes, point at the new page. This is a private + * method, no validation is done. + * @param {number} n Number of bytes to increment. * @private */ - this.ptr = 0; - - /** - * An ever-increasing number. - * @type {Number} - * @private - */ - this.bytesRead_ = 0; - } - - /** - * Returns how many bytes have been read in the stream since the beginning of time. - */ - getNumBytesRead() { - return this.bytesRead_; - } - - /** - * Returns how many bytes are currently in the stream left to be read. - */ - getNumBytesLeft() { - const bytesInCurrentPage = (this.bytes.byteLength - this.ptr); - return this.pages_.reduce((acc, arr) => acc + arr.length, bytesInCurrentPage); - } - - /** - * Move the pointer ahead n bytes. If the pointer is at the end of the current array - * of bytes and we have another page of bytes, point at the new page. This is a private - * method, no validation is done. - * @param {number} n Number of bytes to increment. - * @private - */ - movePointer_(n) { - this.ptr += n; - this.bytesRead_ += n; - while (this.ptr >= this.bytes.length && this.pages_.length > 0) { - this.ptr -= this.bytes.length; - this.bytes = this.pages_.shift(); - } - } - - /** - * Peeks at the next n bytes as an unsigned number but does not advance the - * pointer. - * @param {number} n The number of bytes to peek at. Must be a positive integer. - * @return {number} The n bytes interpreted as an unsigned number. - */ - peekNumber(n) { - const num = parseInt(n, 10); - if (n !== num || num < 0) { - throw 'Error! Called peekNumber() with a non-positive integer'; - } else if (num === 0) { - return 0; - } - - if (n > 4) { - throw 'Error! Called peekNumber(' + n + - ') but this method can only reliably read numbers up to 4 bytes long'; - } - - if (this.getNumBytesLeft() < num) { - throw 'Error! Overflowed the byte stream while peekNumber()! n=' + num + - ', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft(); - } - - let result = 0; - let curPage = this.bytes; - let pageIndex = 0; - let ptr = this.ptr; - for (let i = 0; i < num; ++i) { - result |= (curPage[ptr++] << (i * 8)); - - if (ptr >= curPage.length) { - curPage = this.pages_[pageIndex++]; - ptr = 0; + movePointer_(n) { + this.ptr += n; + this.bytesRead_ += n; + while (this.ptr >= this.bytes.length && this.pages_.length > 0) { + this.ptr -= this.bytes.length; + this.bytes = this.pages_.shift(); } } - return result; - } - - - /** - * Returns the next n bytes as an unsigned number (or -1 on error) - * and advances the stream pointer n bytes. - * @param {number} n The number of bytes to read. Must be a positive integer. - * @return {number} The n bytes interpreted as an unsigned number. - */ - readNumber(n) { - const num = this.peekNumber(n); - this.movePointer_(n); - return num; - } - - - /** - * Returns the next n bytes as a signed number but does not advance the - * pointer. - * @param {number} n The number of bytes to read. Must be a positive integer. - * @return {number} The bytes interpreted as a signed number. - */ - peekSignedNumber(n) { - let num = this.peekNumber(n); - const HALF = Math.pow(2, (n * 8) - 1); - const FULL = HALF * 2; - - if (num >= HALF) num -= FULL; - - return num; - } - - - /** - * Returns the next n bytes as a signed number and advances the stream pointer. - * @param {number} n The number of bytes to read. Must be a positive integer. - * @return {number} The bytes interpreted as a signed number. - */ - readSignedNumber(n) { - const num = this.peekSignedNumber(n); - this.movePointer_(n); - return num; - } - - - /** - * This returns n bytes as a sub-array, advancing the pointer if movePointers - * is true. - * @param {number} n The number of bytes to read. Must be a positive integer. - * @param {boolean} movePointers Whether to move the pointers. - * @return {Uint8Array} The subarray. - */ - peekBytes(n, movePointers) { - const num = parseInt(n, 10); - if (n !== num || num < 0) { - throw 'Error! Called peekBytes() with a non-positive integer'; - } else if (num === 0) { - return new Uint8Array(); - } - - const totalBytesLeft = this.getNumBytesLeft(); - if (num > totalBytesLeft) { - throw 'Error! Overflowed the byte stream during peekBytes! n=' + num + - ', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft(); - } - - const result = new Uint8Array(num); - let curPage = this.bytes; - let ptr = this.ptr; - let bytesLeftToCopy = num; - let pageIndex = 0; - while (bytesLeftToCopy > 0) { - const bytesLeftInPage = curPage.length - ptr; - const sourceLength = Math.min(bytesLeftToCopy, bytesLeftInPage); - - result.set(curPage.subarray(ptr, ptr + sourceLength), num - bytesLeftToCopy); - - ptr += sourceLength; - if (ptr >= curPage.length) { - curPage = this.pages_[pageIndex++]; - ptr = 0; + /** + * Peeks at the next n bytes as an unsigned number but does not advance the + * pointer. + * @param {number} n The number of bytes to peek at. Must be a positive integer. + * @return {number} The n bytes interpreted as an unsigned number. + */ + peekNumber(n) { + const num = parseInt(n, 10); + if (n !== num || num < 0) { + throw 'Error! Called peekNumber() with a non-positive integer'; + } else if (num === 0) { + return 0; } - bytesLeftToCopy -= sourceLength; - } - - if (movePointers) { - this.movePointer_(num); - } - - return result; - } - - /** - * Reads the next n bytes as a sub-array. - * @param {number} n The number of bytes to read. Must be a positive integer. - * @return {Uint8Array} The subarray. - */ - readBytes(n) { - return this.peekBytes(n, true); - } - - /** - * Peeks at the next n bytes as an ASCII string but does not advance the pointer. - * @param {number} n The number of bytes to peek at. Must be a positive integer. - * @return {string} The next n bytes as a string. - */ - peekString(n) { - const num = parseInt(n, 10); - if (n !== num || num < 0) { - throw 'Error! Called peekString() with a non-positive integer'; - } else if (num === 0) { - return ''; - } - - const totalBytesLeft = this.getNumBytesLeft(); - if (num > totalBytesLeft) { - throw 'Error! Overflowed the byte stream while peekString()! n=' + num + - ', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft(); - } - - let result = new Array(num); - let curPage = this.bytes; - let pageIndex = 0; - let ptr = this.ptr; - for (let i = 0; i < num; ++i) { - result[i] = String.fromCharCode(curPage[ptr++]); - if (ptr >= curPage.length) { - curPage = this.pages_[pageIndex++]; - ptr = 0; + if (n > 4) { + throw 'Error! Called peekNumber(' + n + + ') but this method can only reliably read numbers up to 4 bytes long'; } + + if (this.getNumBytesLeft() < num) { + throw 'Error! Overflowed the byte stream while peekNumber()! n=' + num + + ', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft(); + } + + let result = 0; + let curPage = this.bytes; + let pageIndex = 0; + let ptr = this.ptr; + for (let i = 0; i < num; ++i) { + result |= (curPage[ptr++] << (i * 8)); + + if (ptr >= curPage.length) { + curPage = this.pages_[pageIndex++]; + ptr = 0; + } + } + + return result; } - return result.join(''); - } - /** - * Returns the next n bytes as an ASCII string and advances the stream pointer - * n bytes. - * @param {number} n The number of bytes to read. Must be a positive integer. - * @return {string} The next n bytes as a string. - */ - readString(n) { - const strToReturn = this.peekString(n); - this.movePointer_(n); - return strToReturn; - } - - /** - * Feeds more bytes into the back of the stream. - * @param {ArrayBuffer} ab - */ - push(ab) { - if (!(ab instanceof ArrayBuffer)) { - throw 'Error! ByteStream.push() called with an invalid ArrayBuffer object'; + /** + * Returns the next n bytes as an unsigned number (or -1 on error) + * and advances the stream pointer n bytes. + * @param {number} n The number of bytes to read. Must be a positive integer. + * @return {number} The n bytes interpreted as an unsigned number. + */ + readNumber(n) { + const num = this.peekNumber(n); + this.movePointer_(n); + return num; } - this.pages_.push(new Uint8Array(ab)); - // If the pointer is at the end of the current page of bytes, this will advance - // to the next page. - this.movePointer_(0); + + /** + * Returns the next n bytes as a signed number but does not advance the + * pointer. + * @param {number} n The number of bytes to read. Must be a positive integer. + * @return {number} The bytes interpreted as a signed number. + */ + peekSignedNumber(n) { + let num = this.peekNumber(n); + const HALF = Math.pow(2, (n * 8) - 1); + const FULL = HALF * 2; + + if (num >= HALF) num -= FULL; + + return num; + } + + + /** + * Returns the next n bytes as a signed number and advances the stream pointer. + * @param {number} n The number of bytes to read. Must be a positive integer. + * @return {number} The bytes interpreted as a signed number. + */ + readSignedNumber(n) { + const num = this.peekSignedNumber(n); + this.movePointer_(n); + return num; + } + + + /** + * This returns n bytes as a sub-array, advancing the pointer if movePointers + * is true. + * @param {number} n The number of bytes to read. Must be a positive integer. + * @param {boolean} movePointers Whether to move the pointers. + * @return {Uint8Array} The subarray. + */ + peekBytes(n, movePointers) { + const num = parseInt(n, 10); + if (n !== num || num < 0) { + throw 'Error! Called peekBytes() with a non-positive integer'; + } else if (num === 0) { + return new Uint8Array(); + } + + const totalBytesLeft = this.getNumBytesLeft(); + if (num > totalBytesLeft) { + throw 'Error! Overflowed the byte stream during peekBytes! n=' + num + + ', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft(); + } + + const result = new Uint8Array(num); + let curPage = this.bytes; + let ptr = this.ptr; + let bytesLeftToCopy = num; + let pageIndex = 0; + while (bytesLeftToCopy > 0) { + const bytesLeftInPage = curPage.length - ptr; + const sourceLength = Math.min(bytesLeftToCopy, bytesLeftInPage); + + result.set(curPage.subarray(ptr, ptr + sourceLength), num - bytesLeftToCopy); + + ptr += sourceLength; + if (ptr >= curPage.length) { + curPage = this.pages_[pageIndex++]; + ptr = 0; + } + + bytesLeftToCopy -= sourceLength; + } + + if (movePointers) { + this.movePointer_(num); + } + + return result; + } + + /** + * Reads the next n bytes as a sub-array. + * @param {number} n The number of bytes to read. Must be a positive integer. + * @return {Uint8Array} The subarray. + */ + readBytes(n) { + return this.peekBytes(n, true); + } + + /** + * Peeks at the next n bytes as an ASCII string but does not advance the pointer. + * @param {number} n The number of bytes to peek at. Must be a positive integer. + * @return {string} The next n bytes as a string. + */ + peekString(n) { + const num = parseInt(n, 10); + if (n !== num || num < 0) { + throw 'Error! Called peekString() with a non-positive integer'; + } else if (num === 0) { + return ''; + } + + const totalBytesLeft = this.getNumBytesLeft(); + if (num > totalBytesLeft) { + throw 'Error! Overflowed the byte stream while peekString()! n=' + num + + ', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft(); + } + + let result = new Array(num); + let curPage = this.bytes; + let pageIndex = 0; + let ptr = this.ptr; + for (let i = 0; i < num; ++i) { + result[i] = String.fromCharCode(curPage[ptr++]); + if (ptr >= curPage.length) { + curPage = this.pages_[pageIndex++]; + ptr = 0; + } + } + + return result.join(''); + } + + /** + * Returns the next n bytes as an ASCII string and advances the stream pointer + * n bytes. + * @param {number} n The number of bytes to read. Must be a positive integer. + * @return {string} The next n bytes as a string. + */ + readString(n) { + const strToReturn = this.peekString(n); + this.movePointer_(n); + return strToReturn; + } + + /** + * Feeds more bytes into the back of the stream. + * @param {ArrayBuffer} ab + */ + push(ab) { + if (!(ab instanceof ArrayBuffer)) { + throw 'Error! ByteStream.push() called with an invalid ArrayBuffer object'; + } + + this.pages_.push(new Uint8Array(ab)); + // If the pointer is at the end of the current page of bytes, this will advance + // to the next page. + this.movePointer_(0); + } + + /** + * Creates a new ByteStream from this ByteStream that can be read / peeked. + * @return {ByteStream} A clone of this ByteStream. + */ + tee() { + const clone = new ByteStream(this.bytes.buffer); + clone.bytes = this.bytes; + clone.ptr = this.ptr; + clone.pages_ = this.pages_.slice(); + clone.bytesRead_ = this.bytesRead_; + return clone; + } } - /** - * Creates a new ByteStream from this ByteStream that can be read / peeked. - * @return {bitjs.io.ByteStream} A clone of this ByteStream. - */ - tee() { - const clone = new bitjs.io.ByteStream(this.bytes.buffer); - clone.bytes = this.bytes; - clone.ptr = this.ptr; - clone.pages_ = this.pages_.slice(); - clone.bytesRead_ = this.bytesRead_; - return clone; - } -} + return ByteStream; +})(); diff --git a/tests/io-bytebuffer-test.html b/tests/io-bytebuffer-test.html index 68a2129..8369564 100644 --- a/tests/io-bytebuffer-test.html +++ b/tests/io-bytebuffer-test.html @@ -3,9 +3,8 @@ Unit tests for bitjs.io.ByteBufferBitStream - - - + + - +