Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /**
7 * This object contains helpers buffering incoming data & deconstructing it
8 * into parcels as well as buffering outgoing data & constructing parcels.
9 * For that it maintains two buffers and corresponding uint8 views, indexes.
10 *
11 * The incoming buffer is a circular buffer where we store incoming data.
12 * As soon as a complete parcel is received, it is processed right away, so
13 * the buffer only needs to be large enough to hold one parcel.
14 *
15 * The outgoing buffer is to prepare outgoing parcels. The index is reset
16 * every time a parcel is sent.
17 */
19 let Buf = {
20 INT32_MAX: 2147483647,
21 UINT8_SIZE: 1,
22 UINT16_SIZE: 2,
23 UINT32_SIZE: 4,
24 PARCEL_SIZE_SIZE: 4,
25 PDU_HEX_OCTET_SIZE: 4,
27 incomingBufferLength: 1024,
28 incomingBuffer: null,
29 incomingBytes: null,
30 incomingWriteIndex: 0,
31 incomingReadIndex: 0,
32 readIncoming: 0,
33 readAvailable: 0,
34 currentParcelSize: 0,
36 outgoingBufferLength: 1024,
37 outgoingBuffer: null,
38 outgoingBytes: null,
39 outgoingIndex: 0,
40 outgoingBufferCalSizeQueue: null,
42 _init: function() {
43 this.incomingBuffer = new ArrayBuffer(this.incomingBufferLength);
44 this.outgoingBuffer = new ArrayBuffer(this.outgoingBufferLength);
46 this.incomingBytes = new Uint8Array(this.incomingBuffer);
47 this.outgoingBytes = new Uint8Array(this.outgoingBuffer);
49 // Track where incoming data is read from and written to.
50 this.incomingWriteIndex = 0;
51 this.incomingReadIndex = 0;
53 // Leave room for the parcel size for outgoing parcels.
54 this.outgoingIndex = this.PARCEL_SIZE_SIZE;
56 // How many bytes we've read for this parcel so far.
57 this.readIncoming = 0;
59 // How many bytes available as parcel data.
60 this.readAvailable = 0;
62 // Size of the incoming parcel. If this is zero, we're expecting a new
63 // parcel.
64 this.currentParcelSize = 0;
66 // Queue for storing outgoing override points
67 this.outgoingBufferCalSizeQueue = [];
68 },
70 /**
71 * Mark current outgoingIndex as start point for calculation length of data
72 * written to outgoingBuffer.
73 * Mark can be nested for here uses queue to remember marks.
74 *
75 * @param writeFunction
76 * Function to write data length into outgoingBuffer, this function is
77 * also used to allocate buffer for data length.
78 * Raw data size(in Uint8) is provided as parameter calling writeFunction.
79 * If raw data size is not in proper unit for writing, user can adjust
80 * the length value in writeFunction before writing.
81 **/
82 startCalOutgoingSize: function(writeFunction) {
83 let sizeInfo = {index: this.outgoingIndex,
84 write: writeFunction};
86 // Allocate buffer for data lemgtj.
87 writeFunction.call(0);
89 // Get size of data length buffer for it is not counted into data size.
90 sizeInfo.size = this.outgoingIndex - sizeInfo.index;
92 // Enqueue size calculation information.
93 this.outgoingBufferCalSizeQueue.push(sizeInfo);
94 },
96 /**
97 * Calculate data length since last mark, and write it into mark position.
98 **/
99 stopCalOutgoingSize: function() {
100 let sizeInfo = this.outgoingBufferCalSizeQueue.pop();
102 // Remember current outgoingIndex.
103 let currentOutgoingIndex = this.outgoingIndex;
104 // Calculate data length, in uint8.
105 let writeSize = this.outgoingIndex - sizeInfo.index - sizeInfo.size;
107 // Write data length to mark, use same function for allocating buffer to make
108 // sure there is no buffer overloading.
109 this.outgoingIndex = sizeInfo.index;
110 sizeInfo.write(writeSize);
112 // Restore outgoingIndex.
113 this.outgoingIndex = currentOutgoingIndex;
114 },
116 /**
117 * Grow the incoming buffer.
118 *
119 * @param min_size
120 * Minimum new size. The actual new size will be the the smallest
121 * power of 2 that's larger than this number.
122 */
123 growIncomingBuffer: function(min_size) {
124 if (DEBUG) {
125 debug("Current buffer of " + this.incomingBufferLength +
126 " can't handle incoming " + min_size + " bytes.");
127 }
128 let oldBytes = this.incomingBytes;
129 this.incomingBufferLength =
130 2 << Math.floor(Math.log(min_size)/Math.log(2));
131 if (DEBUG) debug("New incoming buffer size: " + this.incomingBufferLength);
132 this.incomingBuffer = new ArrayBuffer(this.incomingBufferLength);
133 this.incomingBytes = new Uint8Array(this.incomingBuffer);
134 if (this.incomingReadIndex <= this.incomingWriteIndex) {
135 // Read and write index are in natural order, so we can just copy
136 // the old buffer over to the bigger one without having to worry
137 // about the indexes.
138 this.incomingBytes.set(oldBytes, 0);
139 } else {
140 // The write index has wrapped around but the read index hasn't yet.
141 // Write whatever the read index has left to read until it would
142 // circle around to the beginning of the new buffer, and the rest
143 // behind that.
144 let head = oldBytes.subarray(this.incomingReadIndex);
145 let tail = oldBytes.subarray(0, this.incomingReadIndex);
146 this.incomingBytes.set(head, 0);
147 this.incomingBytes.set(tail, head.length);
148 this.incomingReadIndex = 0;
149 this.incomingWriteIndex += head.length;
150 }
151 if (DEBUG) {
152 debug("New incoming buffer size is " + this.incomingBufferLength);
153 }
154 },
156 /**
157 * Grow the outgoing buffer.
158 *
159 * @param min_size
160 * Minimum new size. The actual new size will be the the smallest
161 * power of 2 that's larger than this number.
162 */
163 growOutgoingBuffer: function(min_size) {
164 if (DEBUG) {
165 debug("Current buffer of " + this.outgoingBufferLength +
166 " is too small.");
167 }
168 let oldBytes = this.outgoingBytes;
169 this.outgoingBufferLength =
170 2 << Math.floor(Math.log(min_size)/Math.log(2));
171 this.outgoingBuffer = new ArrayBuffer(this.outgoingBufferLength);
172 this.outgoingBytes = new Uint8Array(this.outgoingBuffer);
173 this.outgoingBytes.set(oldBytes, 0);
174 if (DEBUG) {
175 debug("New outgoing buffer size is " + this.outgoingBufferLength);
176 }
177 },
179 /**
180 * Functions for reading data from the incoming buffer.
181 *
182 * These are all little endian, apart from readParcelSize();
183 */
185 /**
186 * Ensure position specified is readable.
187 *
188 * @param index
189 * Data position in incoming parcel, valid from 0 to
190 * currentParcelSize.
191 */
192 ensureIncomingAvailable: function(index) {
193 if (index >= this.currentParcelSize) {
194 throw new Error("Trying to read data beyond the parcel end!");
195 } else if (index < 0) {
196 throw new Error("Trying to read data before the parcel begin!");
197 }
198 },
200 /**
201 * Seek in current incoming parcel.
202 *
203 * @param offset
204 * Seek offset in relative to current position.
205 */
206 seekIncoming: function(offset) {
207 // Translate to 0..currentParcelSize
208 let cur = this.currentParcelSize - this.readAvailable;
210 let newIndex = cur + offset;
211 this.ensureIncomingAvailable(newIndex);
213 // ... incomingReadIndex -->|
214 // 0 new cur currentParcelSize
215 // |================|=======|====================|
216 // |<-- cur -->|<- readAvailable ->|
217 // |<-- newIndex -->|<-- new readAvailable -->|
218 this.readAvailable = this.currentParcelSize - newIndex;
220 // Translate back:
221 if (this.incomingReadIndex < cur) {
222 // The incomingReadIndex is wrapped.
223 newIndex += this.incomingBufferLength;
224 }
225 newIndex += (this.incomingReadIndex - cur);
226 newIndex %= this.incomingBufferLength;
227 this.incomingReadIndex = newIndex;
228 },
230 readUint8Unchecked: function() {
231 let value = this.incomingBytes[this.incomingReadIndex];
232 this.incomingReadIndex = (this.incomingReadIndex + 1) %
233 this.incomingBufferLength;
234 return value;
235 },
237 readUint8: function() {
238 // Translate to 0..currentParcelSize
239 let cur = this.currentParcelSize - this.readAvailable;
240 this.ensureIncomingAvailable(cur);
242 this.readAvailable--;
243 return this.readUint8Unchecked();
244 },
246 readUint8Array: function(length) {
247 // Translate to 0..currentParcelSize
248 let last = this.currentParcelSize - this.readAvailable;
249 last += (length - 1);
250 this.ensureIncomingAvailable(last);
252 let array = new Uint8Array(length);
253 for (let i = 0; i < length; i++) {
254 array[i] = this.readUint8Unchecked();
255 }
257 this.readAvailable -= length;
258 return array;
259 },
261 readUint16: function() {
262 return this.readUint8() | this.readUint8() << 8;
263 },
265 readInt32: function() {
266 return this.readUint8() | this.readUint8() << 8 |
267 this.readUint8() << 16 | this.readUint8() << 24;
268 },
270 readInt32List: function() {
271 let length = this.readInt32();
272 let ints = [];
273 for (let i = 0; i < length; i++) {
274 ints.push(this.readInt32());
275 }
276 return ints;
277 },
279 readString: function() {
280 let string_len = this.readInt32();
281 if (string_len < 0 || string_len >= this.INT32_MAX) {
282 return null;
283 }
284 let s = "";
285 for (let i = 0; i < string_len; i++) {
286 s += String.fromCharCode(this.readUint16());
287 }
288 // Strings are \0\0 delimited, but that isn't part of the length. And
289 // if the string length is even, the delimiter is two characters wide.
290 // It's insane, I know.
291 this.readStringDelimiter(string_len);
292 return s;
293 },
295 readStringList: function() {
296 let num_strings = this.readInt32();
297 let strings = [];
298 for (let i = 0; i < num_strings; i++) {
299 strings.push(this.readString());
300 }
301 return strings;
302 },
304 readStringDelimiter: function(length) {
305 let delimiter = this.readUint16();
306 if (!(length & 1)) {
307 delimiter |= this.readUint16();
308 }
309 if (DEBUG) {
310 if (delimiter !== 0) {
311 debug("Something's wrong, found string delimiter: " + delimiter);
312 }
313 }
314 },
316 readParcelSize: function() {
317 return this.readUint8Unchecked() << 24 |
318 this.readUint8Unchecked() << 16 |
319 this.readUint8Unchecked() << 8 |
320 this.readUint8Unchecked();
321 },
323 /**
324 * Functions for writing data to the outgoing buffer.
325 */
327 /**
328 * Ensure position specified is writable.
329 *
330 * @param index
331 * Data position in outgoing parcel, valid from 0 to
332 * outgoingBufferLength.
333 */
334 ensureOutgoingAvailable: function(index) {
335 if (index >= this.outgoingBufferLength) {
336 this.growOutgoingBuffer(index + 1);
337 }
338 },
340 writeUint8: function(value) {
341 this.ensureOutgoingAvailable(this.outgoingIndex);
343 this.outgoingBytes[this.outgoingIndex] = value;
344 this.outgoingIndex++;
345 },
347 writeUint16: function(value) {
348 this.writeUint8(value & 0xff);
349 this.writeUint8((value >> 8) & 0xff);
350 },
352 writeInt32: function(value) {
353 this.writeUint8(value & 0xff);
354 this.writeUint8((value >> 8) & 0xff);
355 this.writeUint8((value >> 16) & 0xff);
356 this.writeUint8((value >> 24) & 0xff);
357 },
359 writeString: function(value) {
360 if (value == null) {
361 this.writeInt32(-1);
362 return;
363 }
364 this.writeInt32(value.length);
365 for (let i = 0; i < value.length; i++) {
366 this.writeUint16(value.charCodeAt(i));
367 }
368 // Strings are \0\0 delimited, but that isn't part of the length. And
369 // if the string length is even, the delimiter is two characters wide.
370 // It's insane, I know.
371 this.writeStringDelimiter(value.length);
372 },
374 writeStringList: function(strings) {
375 this.writeInt32(strings.length);
376 for (let i = 0; i < strings.length; i++) {
377 this.writeString(strings[i]);
378 }
379 },
381 writeStringDelimiter: function(length) {
382 this.writeUint16(0);
383 if (!(length & 1)) {
384 this.writeUint16(0);
385 }
386 },
388 writeParcelSize: function(value) {
389 /**
390 * Parcel size will always be the first thing in the parcel byte
391 * array, but the last thing written. Store the current index off
392 * to a temporary to be reset after we write the size.
393 */
394 let currentIndex = this.outgoingIndex;
395 this.outgoingIndex = 0;
396 this.writeUint8((value >> 24) & 0xff);
397 this.writeUint8((value >> 16) & 0xff);
398 this.writeUint8((value >> 8) & 0xff);
399 this.writeUint8(value & 0xff);
400 this.outgoingIndex = currentIndex;
401 },
403 copyIncomingToOutgoing: function(length) {
404 if (!length || (length < 0)) {
405 return;
406 }
408 let translatedReadIndexEnd =
409 this.currentParcelSize - this.readAvailable + length - 1;
410 this.ensureIncomingAvailable(translatedReadIndexEnd);
412 let translatedWriteIndexEnd = this.outgoingIndex + length - 1;
413 this.ensureOutgoingAvailable(translatedWriteIndexEnd);
415 let newIncomingReadIndex = this.incomingReadIndex + length;
416 if (newIncomingReadIndex < this.incomingBufferLength) {
417 // Reading won't cause wrapping, go ahead with builtin copy.
418 this.outgoingBytes
419 .set(this.incomingBytes.subarray(this.incomingReadIndex,
420 newIncomingReadIndex),
421 this.outgoingIndex);
422 } else {
423 // Not so lucky.
424 newIncomingReadIndex %= this.incomingBufferLength;
425 this.outgoingBytes
426 .set(this.incomingBytes.subarray(this.incomingReadIndex,
427 this.incomingBufferLength),
428 this.outgoingIndex);
429 if (newIncomingReadIndex) {
430 let firstPartLength = this.incomingBufferLength - this.incomingReadIndex;
431 this.outgoingBytes.set(this.incomingBytes.subarray(0, newIncomingReadIndex),
432 this.outgoingIndex + firstPartLength);
433 }
434 }
436 this.incomingReadIndex = newIncomingReadIndex;
437 this.readAvailable -= length;
438 this.outgoingIndex += length;
439 },
441 /**
442 * Parcel management
443 */
445 /**
446 * Write incoming data to the circular buffer.
447 *
448 * @param incoming
449 * Uint8Array containing the incoming data.
450 */
451 writeToIncoming: function(incoming) {
452 // We don't have to worry about the head catching the tail since
453 // we process any backlog in parcels immediately, before writing
454 // new data to the buffer. So the only edge case we need to handle
455 // is when the incoming data is larger than the buffer size.
456 let minMustAvailableSize = incoming.length + this.readIncoming;
457 if (minMustAvailableSize > this.incomingBufferLength) {
458 this.growIncomingBuffer(minMustAvailableSize);
459 }
461 // We can let the typed arrays do the copying if the incoming data won't
462 // wrap around the edges of the circular buffer.
463 let remaining = this.incomingBufferLength - this.incomingWriteIndex;
464 if (remaining >= incoming.length) {
465 this.incomingBytes.set(incoming, this.incomingWriteIndex);
466 } else {
467 // The incoming data would wrap around it.
468 let head = incoming.subarray(0, remaining);
469 let tail = incoming.subarray(remaining);
470 this.incomingBytes.set(head, this.incomingWriteIndex);
471 this.incomingBytes.set(tail, 0);
472 }
473 this.incomingWriteIndex = (this.incomingWriteIndex + incoming.length) %
474 this.incomingBufferLength;
475 },
477 /**
478 * Process incoming data.
479 *
480 * @param incoming
481 * Uint8Array containing the incoming data.
482 */
483 processIncoming: function(incoming) {
484 if (DEBUG) {
485 debug("Received " + incoming.length + " bytes.");
486 debug("Already read " + this.readIncoming);
487 }
489 this.writeToIncoming(incoming);
490 this.readIncoming += incoming.length;
491 while (true) {
492 if (!this.currentParcelSize) {
493 // We're expecting a new parcel.
494 if (this.readIncoming < this.PARCEL_SIZE_SIZE) {
495 // We don't know how big the next parcel is going to be, need more
496 // data.
497 if (DEBUG) debug("Next parcel size unknown, going to sleep.");
498 return;
499 }
500 this.currentParcelSize = this.readParcelSize();
501 if (DEBUG) {
502 debug("New incoming parcel of size " + this.currentParcelSize);
503 }
504 // The size itself is not included in the size.
505 this.readIncoming -= this.PARCEL_SIZE_SIZE;
506 }
508 if (this.readIncoming < this.currentParcelSize) {
509 // We haven't read enough yet in order to be able to process a parcel.
510 if (DEBUG) debug("Read " + this.readIncoming + ", but parcel size is "
511 + this.currentParcelSize + ". Going to sleep.");
512 return;
513 }
515 // Alright, we have enough data to process at least one whole parcel.
516 // Let's do that.
517 let expectedAfterIndex = (this.incomingReadIndex + this.currentParcelSize)
518 % this.incomingBufferLength;
520 if (DEBUG) {
521 let parcel;
522 if (expectedAfterIndex < this.incomingReadIndex) {
523 let head = this.incomingBytes.subarray(this.incomingReadIndex);
524 let tail = this.incomingBytes.subarray(0, expectedAfterIndex);
525 parcel = Array.slice(head).concat(Array.slice(tail));
526 } else {
527 parcel = Array.slice(this.incomingBytes.subarray(
528 this.incomingReadIndex, expectedAfterIndex));
529 }
530 debug("Parcel (size " + this.currentParcelSize + "): " + parcel);
531 }
533 if (DEBUG) debug("We have at least one complete parcel.");
534 try {
535 this.readAvailable = this.currentParcelSize;
536 this.processParcel();
537 } catch (ex) {
538 if (DEBUG) debug("Parcel handling threw " + ex + "\n" + ex.stack);
539 }
541 // Ensure that the whole parcel was consumed.
542 if (this.incomingReadIndex != expectedAfterIndex) {
543 if (DEBUG) {
544 debug("Parcel handler didn't consume whole parcel, " +
545 Math.abs(expectedAfterIndex - this.incomingReadIndex) +
546 " bytes left over");
547 }
548 this.incomingReadIndex = expectedAfterIndex;
549 }
550 this.readIncoming -= this.currentParcelSize;
551 this.readAvailable = 0;
552 this.currentParcelSize = 0;
553 }
554 },
556 /**
557 * Communicate with the IPC thread.
558 */
559 sendParcel: function() {
560 // Compute the size of the parcel and write it to the front of the parcel
561 // where we left room for it. Note that he parcel size does not include
562 // the size itself.
563 let parcelSize = this.outgoingIndex - this.PARCEL_SIZE_SIZE;
564 this.writeParcelSize(parcelSize);
566 // This assumes that postRILMessage will make a copy of the ArrayBufferView
567 // right away!
568 let parcel = this.outgoingBytes.subarray(0, this.outgoingIndex);
569 if (DEBUG) debug("Outgoing parcel: " + Array.slice(parcel));
570 this.onSendParcel(parcel);
571 this.outgoingIndex = this.PARCEL_SIZE_SIZE;
572 },
574 getCurrentParcelSize: function() {
575 return this.currentParcelSize;
576 },
578 getReadAvailable: function() {
579 return this.readAvailable;
580 }
582 /**
583 * Process one parcel.
584 *
585 * |processParcel| is an implementation provided incoming parcel processing
586 * function invoked when we have received a complete parcel. Implementation
587 * may call multiple read functions to extract data from the incoming buffer.
588 */
589 //processParcel: function() {
590 // let something = this.readInt32();
591 // ...
592 //},
594 /**
595 * Write raw data out to underlying channel.
596 *
597 * |onSendParcel| is an implementation provided stream output function
598 * invoked when we're really going to write something out. We assume the
599 * data are completely copied to some output buffer in this call and may
600 * be destroyed when it's done.
601 *
602 * @param parcel
603 * An array of numeric octet data.
604 */
605 //onSendParcel: function(parcel) {
606 // ...
607 //}
608 };
610 module.exports = { Buf: Buf };