Bug 920801 - Port chat/ changes from Instantbird to comm-central - 6 - Bio 1981 - Make socket.jsm more binary friendly, r=Mic.
authorPatrick Cloke <clokep@gmail.com>
Mon, 15 Jul 2013 23:02:08 +0200
changeset 17615 4c9351bd59181e4fe1129af019010e1b70b9b30e
parent 17614 a30b5531163e4b056a6b2fb4a4ce900d0c89d85b
child 17616 db45cbc73ef1fa4e9f83e5cedd6e58d02f9ca3ae
push id181
push usermbanner@mozilla.com
push dateMon, 28 Apr 2014 08:56:28 +0000
reviewersMic
bugs920801
Bug 920801 - Port chat/ changes from Instantbird to comm-central - 6 - Bio 1981 - Make socket.jsm more binary friendly, r=Mic.
chat/modules/ArrayBufferUtils.jsm
chat/modules/moz.build
chat/modules/socket.jsm
chat/modules/test/test_ArrayBufferUtils.js
chat/modules/test/xpcshell.ini
new file mode 100644
--- /dev/null
+++ b/chat/modules/ArrayBufferUtils.jsm
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * JavaScript ArrayBuffers are missing a variety of useful methods which are
+ * provided by this module.
+ */
+
+const EXPORTED_SYMBOLS = ["copyBytes", "ArrayBufferToBytes",
+  "BytesToArrayBuffer", "StringToBytes", "ArrayBufferToString",
+  "ArrayBufferToHexString"];
+
+/*
+ * aTarget / aSource are ArrayBuffers.
+ *
+ * Note that this is very similar to ArrayBuffer.slice except that it allows
+ * for an offset in the target as well as the source.
+ */
+function copyBytes(aTarget, aSource, aTargetOffset = 0, aSourceOffset = 0,
+                   aLength = aSource.byteLength) {
+  // The rest just gets the data copied into it.
+  let view = new Uint8Array(aTarget, aTargetOffset);
+  view.set(new Uint8Array(aSource, aSourceOffset, aLength));
+}
+
+function ArrayBufferToBytes(aBuf) [b for (b of new Uint8Array(aBuf))];
+
+function BytesToArrayBuffer(aBytes = []) {
+  let buf = new ArrayBuffer(aBytes.length);
+  let view = new Uint8Array(buf);
+  view.set(aBytes);
+  return buf;
+}
+
+function StringToBytes(aString) [aString.charCodeAt(i) for (i in aString)];
+
+function ArrayBufferToString(aData)
+  [String.fromCharCode(b) for (b of new Uint8Array(aData))].join("");
+
+function ArrayBufferToHexString(aData)
+  "0x" + [("0" + b.toString(16)).slice(-2) for (b of new Uint8Array(aData))].join(" ");
--- a/chat/modules/moz.build
+++ b/chat/modules/moz.build
@@ -1,16 +1,17 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
 
 EXTRA_JS_MODULES += [
+    'ArrayBufferUtils.jsm',
     'imContentSink.jsm',
     'imServices.jsm',
     'imSmileys.jsm',
     'imStatusUtils.jsm',
     'imThemes.jsm',
     'imXPCOMUtils.jsm',
     'jsProtoHelper.jsm',
     'socket.jsm',
--- a/chat/modules/socket.jsm
+++ b/chat/modules/socket.jsm
@@ -74,16 +74,17 @@
  *     keep shifting the first element off and calling as setTimeout for the
  *     desired flood time?).
  */
 
 const EXPORTED_SYMBOLS = ["Socket"];
 
 const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource:///modules/ArrayBufferUtils.jsm");
 Cu.import("resource:///modules/imXPCOMUtils.jsm");
 
 // Network errors see: xpcom/base/nsError.h
 const NS_ERROR_MODULE_NETWORK = 2152398848;
 const NS_ERROR_CONNECTION_REFUSED = NS_ERROR_MODULE_NETWORK + 13;
 const NS_ERROR_NET_TIMEOUT = NS_ERROR_MODULE_NETWORK + 14;
 const NS_ERROR_NET_RESET = NS_ERROR_MODULE_NETWORK + 20;
 const NS_ERROR_UNKNOWN_HOST = NS_ERROR_MODULE_NETWORK + 30;
@@ -112,20 +113,16 @@ const ScriptableUnicodeConverter =
 const Socket = {
   // Use this to use binary mode for the
   binaryMode: false,
 
   // Set this for non-binary mode to automatically parse the stream into chunks
   // separated by delimiter.
   delimiter: "",
 
-  // Set this for binary mode to split after a certain number of bytes have
-  // been received.
-  inputSegmentSize: 0,
-
   // Set this for the segment size of outgoing binary streams.
   outputSegmentSize: 0,
 
   // Flags used by nsIProxyService when resolving a proxy.
   proxyFlags: Ci.nsIProtocolProxyService.RESOLVE_PREFER_SOCKS_PROXY,
 
   // Time (in seconds) for nsISocketTransport to continue trying before
   // reporting a failure, 0 is forever.
@@ -247,24 +244,19 @@ const Socket = {
       let stream = converter.convertToInputStream(aString);
       this._outputStream.writeFrom(stream, stream.available());
     } catch(e) {
       Cu.reportError(e);
     }
   },
 
   sendBinaryData: function(/* ArrayBuffer */ aData) {
-    this.LOG("Sending binary data data: <" + aData + ">");
-
-    let uint8 = Uint8Array(aData);
+    this.LOG("Sending binary data: <" + ArrayBufferToHexString(aData) + ">");
 
-    // Since there doesn't seem to be a uint8.get() method for the byte array
-    let byteArray = [];
-    for (let i = 0; i < uint8.byteLength; i++)
-      byteArray.push(uint8[i]);
+    let byteArray = ArrayBufferToBytes(aData);
     try {
       // Send the data as a byte array
       this._binaryOutputStream.writeByteArray(byteArray, byteArray.length);
     } catch(e) {
       Cu.reportError(e);
     }
   },
 
@@ -354,30 +346,31 @@ const Socket = {
   // Buffers the data, and parses it into discrete messages.
   onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
     if (this.binaryMode) {
       // Load the data from the stream
       this._incomingDataBuffer = this._incomingDataBuffer
                                      .concat(this._binaryInputStream
                                                  .readByteArray(aCount));
 
-      let size = this.inputSegmentSize || this._incomingDataBuffer.length;
-      this.LOG(size + " " + this._incomingDataBuffer.length);
-      while (this._incomingDataBuffer.length >= size) {
-        let buffer = new ArrayBuffer(size);
+      let size = this._incomingDataBuffer.length;
+
+      // Create a new ArrayBuffer.
+      let buffer = new ArrayBuffer(size);
+      let uintArray = new Uint8Array(buffer);
 
-        // Create a new ArraybufferView
-        let uintArray = new Uint8Array(buffer);
+      // Set the data into the array while saving the extra data.
+      uintArray.set(this._incomingDataBuffer);
 
-        // Set the data into the array while saving the extra data
-        uintArray.set(this._incomingDataBuffer.splice(0, size));
+      // Notify we've received data.
+      // Variable data size, the callee must return how much data was handled.
+      size = this.onBinaryDataReceived(buffer);
 
-        // Notify we've received data
-        this.onBinaryDataReceived(buffer);
-      }
+      // Remove the handled data.
+      this._incomingDataBuffer.splice(0, size);
     } else {
       if (this.delimiter) {
         // Load the data from the stream
         this._incomingDataBuffer += this._scriptableInputStream.read(aCount);
         let data = this._incomingDataBuffer.split(this.delimiter);
 
         // Store the (possibly) incomplete part
         this._incomingDataBuffer = data.pop();
@@ -504,19 +497,18 @@ const Socket = {
     if (this.readWriteTimeout) {
       this.transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE,
                                 this.readWriteTimeout);
     }
 
     this.transport.setEventSink(this, Services.tm.currentThread);
 
     // No limit on the output stream buffer
-    this._outputStream = this.transport.openOutputStream(0, // flags
-                                                         this.outputSegmentSize, // Use default segment size
-                                                         -1); // Segment count
+    this._outputStream =
+      this.transport.openOutputStream(0, this.outputSegmentSize, -1);
     if (!this._outputStream)
       throw "Error getting output stream.";
 
     this._inputStream = this.transport.openInputStream(0, // flags
                                                        0, // Use default segment size
                                                        0); // Use default segment count
     if (!this._inputStream)
       throw "Error getting input stream.";
new file mode 100644
--- /dev/null
+++ b/chat/modules/test/test_ArrayBufferUtils.js
@@ -0,0 +1,100 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource:///modules/ArrayBufferUtils.jsm");
+
+function do_check_arraybuffer_eq(a, b) {
+  let viewA = new Uint8Array(a);
+  let viewB = new Uint8Array(b);
+
+  let res = a.byteLength == b.byteLength;
+  for (let i = 0; i < viewA.byteLength; ++i)
+    res = res && viewA[i] == viewB[i];
+
+  do_check_true(res);
+}
+
+function do_check_array_eq(a, b) {
+  let res = a.length == b.length;
+  for (let i = 0; i < a.length; ++i)
+    res = res && a[i] == b[i];
+
+  do_check_true(res);
+}
+
+function test_ArrayBufferToBytes() {
+  let expectedBytes = [0, 1, 0, 10, 0, 100, 3, 232];
+  let expectedBuf = new ArrayBuffer(8);
+  let view = new DataView(expectedBuf);
+  view.setUint16(0, 1);
+  view.setUint16(2, 10);
+  view.setUint16(4, 100);
+  view.setUint16(6, 1000);
+
+  let bytes = ArrayBufferToBytes(expectedBuf);
+  do_check_array_eq(bytes, expectedBytes);
+
+  run_next_test();
+}
+
+function test_BytesToArrayBuffer() {
+  let expectedBytes = [0, 1, 0, 10, 0, 100, 3, 232];
+  let expectedBuf = new ArrayBuffer(8);
+  let view = new DataView(expectedBuf);
+  view.setUint16(0, 1);
+  view.setUint16(2, 10);
+  view.setUint16(4, 100);
+  view.setUint16(6, 1000);
+
+  let buf = BytesToArrayBuffer(expectedBytes);
+  do_check_arraybuffer_eq(buf, expectedBuf);
+
+  run_next_test();
+}
+
+function test_StringToBytes() {
+  let expectedBytes = [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33];
+  let bytes = StringToBytes("Hello world!");
+  do_check_array_eq(bytes, expectedBytes);
+
+  run_next_test();
+}
+
+function test_ArrayBufferToString() {
+  let testString = "Hello world!";
+  let byteString = StringToBytes(testString);
+
+  let buf = new ArrayBuffer(byteString.length);
+  let view = new DataView(buf);
+  for (let i = 0; i < byteString.length; ++i)
+    view.setUint8(i, byteString[i]);
+
+  let str = ArrayBufferToString(buf);
+  do_check_eq(str, testString);
+
+  run_next_test();
+}
+
+function test_ArrayBufferToHexString() {
+  let buf = new ArrayBuffer(4);
+  let view = new DataView(buf);
+  view.setUint8(0, 0x00);
+  view.setUint8(1, 0x10);
+  view.setUint8(2, 0x01);
+  view.setUint8(3, 0x11);
+
+  let str = ArrayBufferToHexString(buf);
+  do_check_eq(str, "0x00 10 01 11");
+
+  run_next_test();
+}
+
+function run_test() {
+  add_test(test_ArrayBufferToBytes);
+  add_test(test_BytesToArrayBuffer);
+  add_test(test_StringToBytes);
+  add_test(test_ArrayBufferToString);
+  add_test(test_ArrayBufferToHexString);
+
+  run_next_test();
+}
--- a/chat/modules/test/xpcshell.ini
+++ b/chat/modules/test/xpcshell.ini
@@ -1,6 +1,7 @@
 [DEFAULT]
 head =
 tail =
 run-sequentially = Avoid bustage.
 
+[test_ArrayBufferUtils.js]
 [test_filtering.js]