dom/system/gonk/worker_buf.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

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 };

mercurial