1 | class ArrayBufferStream {
|
2 | /**
|
3 | * ArrayBufferStream wraps the built-in javascript ArrayBuffer, adding the ability to access
|
4 | * data in it like a stream, tracking its position.
|
5 | * You can request to read a value from the front of the array, and it will keep track of the position
|
6 | * within the byte array, so that successive reads are consecutive.
|
7 | * The available types to read include:
|
8 | * Uint8, Uint8String, Int16, Uint16, Int32, Uint32
|
9 | * @param {ArrayBuffer} arrayBuffer - array to use as a stream
|
10 | * @param {number} start - the start position in the raw buffer. position
|
11 | * will be relative to the start value.
|
12 | * @param {number} end - the end position in the raw buffer. length and
|
13 | * bytes available will be relative to the end value.
|
14 | * @param {ArrayBufferStream} parent - if passed reuses the parent's
|
15 | * internal objects
|
16 | * @constructor
|
17 | */
|
18 | constructor (
|
19 | arrayBuffer, start = 0, end = arrayBuffer.byteLength,
|
20 | {
|
21 | _uint8View = new Uint8Array(arrayBuffer)
|
22 | } = {}
|
23 | ) {
|
24 | /**
|
25 | * Raw data buffer for stream to read.
|
26 | * @type {ArrayBufferStream}
|
27 | */
|
28 | this.arrayBuffer = arrayBuffer;
|
29 |
|
30 | /**
|
31 | * Start position in arrayBuffer. Read values are relative to the start
|
32 | * in the arrayBuffer.
|
33 | * @type {number}
|
34 | */
|
35 | this.start = start;
|
36 |
|
37 | /**
|
38 | * End position in arrayBuffer. Length and bytes available are relative
|
39 | * to the start, end, and _position in the arrayBuffer;
|
40 | * @type {number};
|
41 | */
|
42 | this.end = end;
|
43 |
|
44 | /**
|
45 | * Cached Uint8Array view of the arrayBuffer. Heavily used for reading
|
46 | * Uint8 values and Strings from the stream.
|
47 | * @type {Uint8Array}
|
48 | */
|
49 | this._uint8View = _uint8View;
|
50 |
|
51 | /**
|
52 | * Raw position in the arrayBuffer relative to the beginning of the
|
53 | * arrayBuffer.
|
54 | * @type {number}
|
55 | */
|
56 | this._position = start;
|
57 | }
|
58 |
|
59 | /**
|
60 | * Return a new ArrayBufferStream that is a slice of the existing one
|
61 | * @param {number} length - the number of bytes of extract
|
62 | * @return {ArrayBufferStream} the extracted stream
|
63 | */
|
64 | extract (length) {
|
65 | return new ArrayBufferStream(this.arrayBuffer, this._position, this._position + length, this);
|
66 | }
|
67 |
|
68 | /**
|
69 | * @return {number} the length of the stream in bytes
|
70 | */
|
71 | getLength () {
|
72 | return this.end - this.start;
|
73 | }
|
74 |
|
75 | /**
|
76 | * @return {number} the number of bytes available after the current position in the stream
|
77 | */
|
78 | getBytesAvailable () {
|
79 | return this.end - this._position;
|
80 | }
|
81 |
|
82 | /**
|
83 | * Position relative to the start value in the arrayBuffer of this
|
84 | * ArrayBufferStream.
|
85 | * @type {number}
|
86 | */
|
87 | get position () {
|
88 | return this._position - this.start;
|
89 | }
|
90 |
|
91 | /**
|
92 | * Set the position to read from in the arrayBuffer.
|
93 | * @type {number}
|
94 | * @param {number} value - new value to set position to
|
95 | */
|
96 | set position (value) {
|
97 | this._position = value + this.start;
|
98 | }
|
99 |
|
100 | /**
|
101 | * Read an unsigned 8 bit integer from the stream
|
102 | * @return {number} the next 8 bit integer in the stream
|
103 | */
|
104 | readUint8 () {
|
105 | const val = this._uint8View[this._position];
|
106 | this._position += 1;
|
107 | return val;
|
108 | }
|
109 |
|
110 | /**
|
111 | * Read a sequence of bytes of the given length and convert to a string.
|
112 | * This is a convenience method for use with short strings.
|
113 | * @param {number} length - the number of bytes to convert
|
114 | * @return {string} a String made by concatenating the chars in the input
|
115 | */
|
116 | readUint8String (length) {
|
117 | const arr = this._uint8View;
|
118 | let str = '';
|
119 | const end = this._position + length;
|
120 | for (let i = this._position; i < end; i++) {
|
121 | str += String.fromCharCode(arr[i]);
|
122 | }
|
123 | this._position += length;
|
124 | return str;
|
125 | }
|
126 |
|
127 | /**
|
128 | * Read a 16 bit integer from the stream
|
129 | * @return {number} the next 16 bit integer in the stream
|
130 | */
|
131 | readInt16 () {
|
132 | const val = new Int16Array(this.arrayBuffer, this._position, 1)[0];
|
133 | this._position += 2; // one 16 bit int is 2 bytes
|
134 | return val;
|
135 | }
|
136 |
|
137 | /**
|
138 | * Read an unsigned 16 bit integer from the stream
|
139 | * @return {number} the next unsigned 16 bit integer in the stream
|
140 | */
|
141 | readUint16 () {
|
142 | const val = new Uint16Array(this.arrayBuffer, this._position, 1)[0];
|
143 | this._position += 2; // one 16 bit int is 2 bytes
|
144 | return val;
|
145 | }
|
146 |
|
147 | /**
|
148 | * Read a 32 bit integer from the stream
|
149 | * @return {number} the next 32 bit integer in the stream
|
150 | */
|
151 | readInt32 () {
|
152 | let val;
|
153 | if (this._position % 4 === 0) {
|
154 | val = new Int32Array(this.arrayBuffer, this._position, 1)[0];
|
155 | } else {
|
156 | // Cannot read Int32 directly out because offset is not multiple of 4
|
157 | // Need to slice out the values first
|
158 | val = new Int32Array(
|
159 | this.arrayBuffer.slice(this._position, this._position + 4)
|
160 | )[0];
|
161 | }
|
162 | this._position += 4; // one 32 bit int is 4 bytes
|
163 | return val;
|
164 | }
|
165 |
|
166 | /**
|
167 | * Read an unsigned 32 bit integer from the stream
|
168 | * @return {number} the next unsigned 32 bit integer in the stream
|
169 | */
|
170 | readUint32 () {
|
171 | const val = new Uint32Array(this.arrayBuffer, this._position, 1)[0];
|
172 | this._position += 4; // one 32 bit int is 4 bytes
|
173 | return val;
|
174 | }
|
175 | }
|
176 |
|
177 | module.exports = ArrayBufferStream;
|