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:
parent
f9f0bc10de
commit
26d06e8d3b
2 changed files with 168 additions and 33 deletions
122
io/bytestream.js
122
io/bytestream.js
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue