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;
this.bytes = new Uint8Array(ab, offset, length);
this.ptr = 0;
this.pages_ = [];
}
/**
@ -41,7 +42,23 @@ bitjs.io.ByteStream = class {
* @private
*/
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.
*/
peekNumber(n) {
let num = parseInt(n, 10);
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;
}
let result = 0;
// 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) {
// TODO: Throw an error if n > 4.
if (this.getBytesLeft_() < num) {
throw 'Error! Overflowed the byte stream while peekNumber()! n=' + num +
', ptr=' + this.ptr + ', bytes.length=' + this.bytes.length;
}
result <<= 8;
result |= this.bytes[curByte];
--curByte;
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;
}
@ -84,7 +108,7 @@ bitjs.io.ByteStream = class {
*/
readNumber(n) {
const num = this.peekNumber(n);
this.ptr += n;
this.movePointer_(n);
return num;
}
@ -113,7 +137,7 @@ bitjs.io.ByteStream = class {
*/
readSignedNumber(n) {
const num = this.peekSignedNumber(n);
this.ptr += n;
this.movePointer_(n);
return num;
}
@ -126,23 +150,33 @@ bitjs.io.ByteStream = class {
* @return {Uint8Array} The subarray.
*/
peekBytes(n, movePointers) {
let num = parseInt(n, 10);
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();
}
if (num > this.getBytesLeft_()) {
//if (this.ptr + num > this.bytes.byteLength) {
const totalBytesLeft = this.getBytesLeft_();
if (num > totalBytesLeft) {
throw 'Error! Overflowed the byte stream! n=' + num + ', ptr=' + this.ptr +
', 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) {
this.ptr += num;
this.movePointer_(num);
}
return result;
@ -163,25 +197,32 @@ bitjs.io.ByteStream = class {
* @return {string} The next n bytes as a string.
*/
peekString(n) {
let num = parseInt(n, 10);
const num = parseInt(n, 10);
if (n !== num || num < 0) {
throw 'Error! Called peekString() with a non-positive integer';
} else if (num === 0) {
return '';
}
// TODO: return error if n would go past the end of the stream.
let result = "";
for (let p = this.ptr, end = this.ptr + n; p < end; ++p) {
if (p >= this.bytes.byteLength) {
const totalBytesLeft = this.getBytesLeft_();
if (num > totalBytesLeft) {
throw 'Error! Overflowed the byte stream while peekString()! n=' + num +
', ptr=' + this.ptr + ', bytes.length=' + this.bytes.length;
}
result += String.fromCharCode(this.bytes[p]);
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;
}
return result.join('');
}
/**
@ -192,7 +233,22 @@ bitjs.io.ByteStream = class {
*/
readString(n) {
const strToReturn = this.peekString(n);
this.ptr += 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! 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),
'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>