1
0
Fork 0
mirror of https://github.com/codedread/bitjs synced 2025-10-03 09:39:16 +02:00

Make ByteStream pushable (allow multiple pages of bytes)

This commit is contained in:
codedread 2018-01-20 23:38:52 -08:00
parent f9f0bc10de
commit 26d06e8d3b
2 changed files with 168 additions and 33 deletions

View file

@ -34,6 +34,7 @@ bitjs.io.ByteStream = class {
const length = opt_length || ab.byteLength; const length = opt_length || ab.byteLength;
this.bytes = new Uint8Array(ab, offset, length); this.bytes = new Uint8Array(ab, offset, length);
this.ptr = 0; this.ptr = 0;
this.pages_ = [];
} }
/** /**
@ -41,7 +42,23 @@ bitjs.io.ByteStream = class {
* @private * @private
*/ */
getBytesLeft_() { getBytesLeft_() {
return (this.bytes.byteLength - this.ptr); 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;
while (this.ptr >= this.bytes.length && this.pages_.length > 0) {
this.ptr -= this.bytes.length;
this.bytes = this.pages_.shift();
}
} }
/** /**
@ -52,26 +69,33 @@ bitjs.io.ByteStream = class {
* @return {number} The n bytes interpreted as an unsigned number. * @return {number} The n bytes interpreted as an unsigned number.
*/ */
peekNumber(n) { peekNumber(n) {
let num = parseInt(n, 10); const num = parseInt(n, 10);
if (n !== num || num < 0) { if (n !== num || num < 0) {
throw 'Error! Called peekNumber() with a non-positive integer'; throw 'Error! Called peekNumber() with a non-positive integer';
} else if (num === 0) { } else if (num === 0) {
return 0; return 0;
} }
let result = 0; // TODO: Throw an error if n > 4.
// read from last byte to first byte and roll them in
let curByte = this.ptr + num - 1;
while (curByte >= this.ptr) {
if (curByte >= this.bytes.byteLength) {
throw 'Error! Overflowed the byte stream while peekNumber()! n=' + num +
', ptr=' + this.ptr + ', bytes.length=' + this.bytes.length;
}
result <<= 8; if (this.getBytesLeft_() < num) {
result |= this.bytes[curByte]; throw 'Error! Overflowed the byte stream while peekNumber()! n=' + num +
--curByte; ', ptr=' + this.ptr + ', bytes.length=' + this.bytes.length;
} }
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;
} }
@ -84,7 +108,7 @@ bitjs.io.ByteStream = class {
*/ */
readNumber(n) { readNumber(n) {
const num = this.peekNumber(n); const num = this.peekNumber(n);
this.ptr += n; this.movePointer_(n);
return num; return num;
} }
@ -113,7 +137,7 @@ bitjs.io.ByteStream = class {
*/ */
readSignedNumber(n) { readSignedNumber(n) {
const num = this.peekSignedNumber(n); const num = this.peekSignedNumber(n);
this.ptr += n; this.movePointer_(n);
return num; return num;
} }
@ -126,23 +150,33 @@ bitjs.io.ByteStream = class {
* @return {Uint8Array} The subarray. * @return {Uint8Array} The subarray.
*/ */
peekBytes(n, movePointers) { peekBytes(n, movePointers) {
let num = parseInt(n, 10); const num = parseInt(n, 10);
if (n !== num || num <= 0) { if (n !== num || num <= 0) {
throw 'Error! Called peekBytes() with a non-positive integer'; throw 'Error! Called peekBytes() with a non-positive integer';
} else if (num === 0) { } else if (num === 0) {
return new Uint8Array(); return new Uint8Array();
} }
if (num > this.getBytesLeft_()) { const totalBytesLeft = this.getBytesLeft_();
//if (this.ptr + num > this.bytes.byteLength) { if (num > totalBytesLeft) {
throw 'Error! Overflowed the byte stream! n=' + num + ', ptr=' + this.ptr + throw 'Error! Overflowed the byte stream! n=' + num + ', ptr=' + this.ptr +
', bytes.length=' + this.bytes.length; ', bytes.length=' + this.bytes.length;
} }
const result = this.bytes.subarray(this.ptr, this.ptr + num); const result = new Uint8Array(num);
let curPage = this.bytes;
let pageIndex = 0;
let ptr = this.ptr;
for (let i = 0; i < num; ++i) {
result[i] = curPage[ptr++];
if (ptr >= curPage.length) {
curPage = this.pages_[pageIndex++];
ptr = 0;
}
}
if (movePointers) { if (movePointers) {
this.ptr += num; this.movePointer_(num);
} }
return result; return result;
@ -163,25 +197,32 @@ bitjs.io.ByteStream = class {
* @return {string} The next n bytes as a string. * @return {string} The next n bytes as a string.
*/ */
peekString(n) { peekString(n) {
let num = parseInt(n, 10); const num = parseInt(n, 10);
if (n !== num || num < 0) { if (n !== num || num < 0) {
throw 'Error! Called peekString() with a non-positive integer'; throw 'Error! Called peekString() with a non-positive integer';
} else if (num === 0) { } else if (num === 0) {
return ''; return '';
} }
// TODO: return error if n would go past the end of the stream. const totalBytesLeft = this.getBytesLeft_();
if (num > totalBytesLeft) {
let result = ""; throw 'Error! Overflowed the byte stream while peekString()! n=' + num +
for (let p = this.ptr, end = this.ptr + n; p < end; ++p) { ', ptr=' + this.ptr + ', bytes.length=' + this.bytes.length;
if (p >= this.bytes.byteLength) {
throw 'Error! Overflowed the byte stream while peekString()! n=' + num +
', ptr=' + this.ptr + ', bytes.length=' + this.bytes.length;
}
result += String.fromCharCode(this.bytes[p]);
} }
return result;
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('');
} }
/** /**
@ -192,7 +233,22 @@ bitjs.io.ByteStream = class {
*/ */
readString(n) { readString(n) {
const strToReturn = this.peekString(n); const strToReturn = this.peekString(n);
this.ptr += n; this.movePointer_(n);
return strToReturn; return strToReturn;
} }
/**
* Feeds more bytes into the back of the stream.
* @param {ArrayBuffer} ab
*/
push(ab) {
if (!(ab instanceof ArrayBuffer)) {
throw 'Error! 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);
}
} }

View file

@ -75,6 +75,85 @@
assertThrows(() => stream.readString(5), assertThrows(() => stream.readString(5),
'Did not throw when trying to readString past end of stream'); 'Did not throw when trying to readString past end of stream');
}, },
testPushThenReadNumber() {
array = new Uint8Array(1);
array[0] = (1234 & 0xff);
const stream = new bitjs.io.ByteStream(array.buffer);
const anotherArray = new Uint8Array(1);
anotherArray[0] = ((1234 >> 8) & 0xff);
stream.push(anotherArray.buffer);
assertEquals(1234, stream.readNumber(2), 'Could not read number across pages');
},
testReadBytesThenPushThenReadByte() {
for (let i = 0; i < 4; ++i) array[i] = i;
const stream = new bitjs.io.ByteStream(array.buffer);
const bytes = stream.readBytes(4);
assertThrows(() => stream.readBytes(1),
'Did not throw when trying to readBytes past end of stream');
const anotherArray = new Uint8Array(1);
anotherArray[0] = 4;
stream.push(anotherArray.buffer);
assertEquals(4, stream.readNumber(1), 'Could not read in byte after pushing');
},
testPushThenReadBytesAcrossOnePage() {
for (let i = 0; i < 4; ++i) array[i] = i;
const stream = new bitjs.io.ByteStream(array.buffer);
const anotherArray = new Uint8Array(1);
anotherArray[0] = 4;
stream.push(anotherArray.buffer);
const bytes = stream.readBytes(5);
assertEquals(5, bytes.length, 'Could not read bytes across pages');
for (let i = 0; i < 5; ++i) {
assertEquals(i, bytes[i], 'Byte ' + i + ' not right');
}
},
testPushThenReadBytesAcrossMultiplePages() {
for (let i = 0; i < 4; ++i) array[i] = i;
const stream = new bitjs.io.ByteStream(array.buffer);
const anotherArray = new Uint8Array(4);
for (let i = 0; i < 4; ++i) anotherArray[i] = i + 4;
const yetAnotherArray = new Uint8Array(4);
for (let i = 0; i < 4; ++i) yetAnotherArray[i] = i + 8;
stream.push(anotherArray.buffer);
stream.push(yetAnotherArray.buffer);
const bytes = stream.readBytes(12);
assertEquals(12, bytes.length, 'Could not read bytes across multiple pages');
for (let i = 0; i < 12; ++i) {
assertEquals(i, bytes[i], 'Byte ' + i + ' not right');
}
},
testPushThenReadStringAcrossMultiplePages() {
for (let i = 0; i < 4; ++i) array[i] = 65 + i;
const stream = new bitjs.io.ByteStream(array.buffer);
const anotherArray = new Uint8Array(4);
for (let i = 0; i < 4; ++i) anotherArray[i] = 69 + i;
const yetAnotherArray = new Uint8Array(4);
for (let i = 0; i < 4; ++i) yetAnotherArray[i] = 73 + i;
stream.push(anotherArray.buffer);
stream.push(yetAnotherArray.buffer);
const str = stream.readString(12);
assertEquals('ABCDEFGHIJKL', str, 'String was ' + str);
}
}, },
}); });
</script> </script>