libdot: Implement common array helper functions

Introduces a collection of helper functions for (typed) arrays under
lib.array. Some of the functions have been extracted from existing
nassh code.

The new functions are:

* arrayBigEndianToUint32: Convert an array of four unsigned bytes into
   an unsigned 32-bit integer (big endian).
* uint32ToArrayBigEndian: Convert an unsigned 32-bit integer into an
   array of four unsigned bytes (big endian).
* concatTyped: Concatenate an arbitrary number of typed arrays of the
   same type into a new typed array of this type.
* compare: Compare two array-like objects entrywise.

BUG=chromium:712699
Change-Id: I13000ab571e26d0ce417e51fc059fe5d507d683e
Reviewed-on: https://chromium-review.googlesource.com/569158
Tested-by: Mike Frysinger <vapier@chromium.org>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
diff --git a/hterm/concat/hterm_deps.concat b/hterm/concat/hterm_deps.concat
index e13b52c..6fa693b 100644
--- a/hterm/concat/hterm_deps.concat
+++ b/hterm/concat/hterm_deps.concat
@@ -6,6 +6,7 @@
 
 libdot/js/lib.js
 libdot/js/lib_polyfill.js
+libdot/js/lib_array.js
 libdot/js/lib_colors.js
 libdot/js/lib_f.js
 libdot/js/lib_message_manager.js
diff --git a/libdot/html/lib_test.html b/libdot/html/lib_test.html
index df0ff17..b3f580f 100644
--- a/libdot/html/lib_test.html
+++ b/libdot/html/lib_test.html
@@ -4,6 +4,7 @@
     <!-- libdot js files under test; keep in dep order -->
     <script src='../js/lib.js'></script>
     <script src='../js/lib_polyfill.js'></script>
+    <script src='../js/lib_array.js'></script>
     <script src='../js/lib_f.js'></script>
     <script src='../js/lib_colors.js'></script>
     <script src='../js/lib_message_manager.js'></script>
@@ -18,6 +19,7 @@
     <script src='../js/lib_test.js'></script>
 
     <!-- libdot js tests -->
+    <script src='../js/lib_array_tests.js'></script>
     <script src='../js/lib_colors_tests.js'></script>
     <script src='../js/lib_f_tests.js'></script>
     <script src='../js/lib_message_manager_tests.js'></script>
diff --git a/libdot/js/lib_array.js b/libdot/js/lib_array.js
new file mode 100644
index 0000000..a59e499
--- /dev/null
+++ b/libdot/js/lib_array.js
@@ -0,0 +1,89 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+/**
+ * @fileoverview Helper functions for (typed) arrays.
+ */
+
+lib.array = {};
+
+/**
+ * Convert an array of four unsigned bytes into an unsigned 32-bit integer (big
+ * endian).
+ *
+ * @param {!Array.<!number>} array
+ * @returns {!number}
+ */
+lib.array.arrayBigEndianToUint32 = function(array) {
+  const maybeSigned =
+      (array[0] << 24) | (array[1] << 16) | (array[2] << 8) | (array[3] << 0);
+  // Interpret the result of the bit operations as an unsigned integer.
+  return maybeSigned >>> 0;
+};
+
+/**
+ * Convert an unsigned 32-bit integer into an array of four unsigned bytes (big
+ * endian).
+ *
+ * @param {!number} uint32
+ * @returns {!Array.<!number>}
+ */
+lib.array.uint32ToArrayBigEndian = function(uint32) {
+  return [
+    (uint32 >>> 24) & 0xFF,
+    (uint32 >>> 16) & 0xFF,
+    (uint32 >>> 8) & 0xFF,
+    (uint32 >>> 0) & 0xFF,
+  ];
+};
+
+/**
+ * Concatenate an arbitrary number of typed arrays of the same type into a new
+ * typed array of this type.
+ *
+ * @template TYPED_ARRAY
+ * @param {...!TYPED_ARRAY} arrays
+ * @returns {!TYPED_ARRAY}
+ */
+lib.array.concatTyped = function(...arrays) {
+  let resultLength = 0;
+  for (const array of arrays) {
+    resultLength += array.length;
+  }
+  const result = new arrays[0].constructor(resultLength);
+  let pos = 0;
+  for (const array of arrays) {
+    result.set(array, pos);
+    pos += array.length;
+  }
+  return result;
+};
+
+/**
+ * Compare two array-like objects entrywise.
+ *
+ * @template ARRAY_LIKE
+ * @param {?ARRAY_LIKE} a
+ * @param {?ARRAY_LIKE} b
+ * @returns {!boolean} true if both arrays are null or they agree entrywise;
+ *     false otherwise.
+ */
+lib.array.compare = function(a, b) {
+  if (a === null || b === null) {
+    return a === null && b === null;
+  }
+
+  if (a.length !== b.length) {
+    return false;
+  }
+
+  for (let i = 0; i < a.length; i++) {
+    if (a[i] !== b[i]) {
+      return false;
+    }
+  }
+  return true;
+};
diff --git a/libdot/js/lib_array_tests.js b/libdot/js/lib_array_tests.js
new file mode 100644
index 0000000..19b05ce
--- /dev/null
+++ b/libdot/js/lib_array_tests.js
@@ -0,0 +1,102 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+/**
+ * @fileoverview Test suite for array helper functions.
+ */
+
+lib.array.Tests = new lib.TestManager.Suite('lib.array.Tests');
+
+lib.array.Tests.addTest('arrayBigEndianToUint32', function(result, cx) {
+  const subtests = [
+    [[0, 0, 0, 0], 0, 'zero'],
+    [[255, 255, 255, 255], 4294967295, 'max'],
+    [new Uint8Array([0x12, 0x34, 0x56, 0x78]), 305419896, 'big endian'],
+  ];
+
+  subtests.forEach((test) => {
+    result.assertEQ(
+        lib.array.arrayBigEndianToUint32(test[0]), test[1], test[2]);
+  });
+
+  result.pass();
+});
+
+lib.array.Tests.addTest('uint32ToArrayBigEndian', function(result, cx) {
+  const subtests = [
+    [0, [0, 0, 0, 0], 'zero'],
+    [4294967295, [255, 255, 255, 255], 'max'],
+    [305419896, [0x12, 0x34, 0x56, 0x78], 'big endian'],
+  ];
+
+  subtests.forEach((test) => {
+    result.assertEQ(
+        lib.array.uint32ToArrayBigEndian(test[0]), test[1], test[2]);
+  });
+
+  result.pass();
+});
+
+lib.array.Tests.addTest('concatTyped', function(result, cx) {
+  const subtests = [
+    [[new Uint8Array([]), new Uint8Array([])], new Uint8Array([]), 'empty'],
+    [
+      [
+        new Uint16Array([1, 2]),
+        new Uint16Array([3, 4]),
+      ],
+      new Uint16Array([1, 2, 3, 4]),
+      'two arrays',
+    ],
+    [
+      [
+        new Int32Array([1, 2]),
+        new Int32Array([3, 4]),
+        new Int32Array([5, 6]),
+      ],
+      new Int32Array([1, 2, 3, 4, 5, 6]),
+      'three arrays',
+    ],
+  ];
+
+  subtests.forEach((test) => {
+    const concatenated = lib.array.concatTyped(...test[0]);
+    // Check whether result has the correct type.
+    result.assert(
+        concatenated instanceof test[1].constructor &&
+            test[1] instanceof concatenated.constructor,
+        'type');
+    result.assertEQ(Array.from(concatenated), Array.from(test[1]), test[2]);
+  });
+
+  result.pass();
+});
+
+lib.array.Tests.addTest('compare', function(result, cx) {
+  const subtests = [
+    [[null, null], true, 'both null'],
+    [[[], null], false, 'first null'],
+    [[null, []], false, 'second null'],
+    [[[], []], true, 'both empty'],
+    [[[], [1]], false, 'first empty'],
+    [[[1], []], false, 'second empty'],
+    [[[1, 2], [1, 2, 3]], false, 'first shorter'],
+    [[[1, 2, 3], [1, 2]], false, 'second shorter'],
+    [[[1, 2, 3], [1, 2, 4]], false, 'same length'],
+    [
+      [new Uint8Array([1, 2, 4]), new Uint8Array([1, 2, 4])],
+      true,
+      'typed array',
+    ],
+  ];
+
+  subtests.forEach((test) => {
+    result.assertEQ(
+        lib.array.compare(test[0][0], test[0][1]), test[1], test[2]);
+  });
+
+  result.pass();
+});
diff --git a/libdot/js/lib_colors.js b/libdot/js/lib_colors.js
index a1e724e..24de424 100644
--- a/libdot/js/lib_colors.js
+++ b/libdot/js/lib_colors.js
@@ -325,7 +325,7 @@
     var ary = color.match(lib.colors.re_.rgb);
     if (ary) {
       ary.shift();
-      ary.push(1);
+      ary.push('1');
       return ary;
     }
   }
diff --git a/libdot/js/lib_colors_tests.js b/libdot/js/lib_colors_tests.js
index ca35fda..1bd3379 100644
--- a/libdot/js/lib_colors_tests.js
+++ b/libdot/js/lib_colors_tests.js
@@ -193,8 +193,8 @@
     ['blah', null],
     ['rgb(1, 2)', null],
     // Then some reasonable data.
-    ['rgb(1,2,3)', [1, 2, 3, 1]],
-    ['rgba(0, 255, 10, 0)', [0, 255, 10, 0]],
+    ['rgb(1,2,3)', ['1', '2', '3', '1']],
+    ['rgba(0, 255, 10, 0)', ['0', '255', '10', '0']],
   ];
 
   data.forEach((ele) => {
diff --git a/libdot/js/lib_test_manager.js b/libdot/js/lib_test_manager.js
index b611ef4..e548743 100644
--- a/libdot/js/lib_test_manager.js
+++ b/libdot/js/lib_test_manager.js
@@ -963,23 +963,6 @@
 };
 
 /**
- * Check that two arrays are equal.
- */
-lib.TestManager.Result.prototype.arrayEQ_ = function(actual, expected) {
-  if (!actual || !expected)
-    return (!actual && !expected);
-
-  if (actual.length != expected.length)
-    return false;
-
-  for (var i = 0; i < actual.length; ++i)
-    if (actual[i] != expected[i])
-      return false;
-
-  return true;
-};
-
-/**
  * Assert that an actual value is exactly equal to the expected value.
  *
  * This uses the JavaScript '===' operator in order to avoid type coercion.
@@ -1016,7 +999,7 @@
 
   // Deal with common object types since JavaScript can't.
   if (expected instanceof Array)
-    if (this.arrayEQ_(actual, expected))
+    if (lib.array.compare(actual, expected))
       return;
 
   var name = opt_name ? '[' + opt_name + ']' : '';
diff --git a/nassh/js/nassh_stream_google_relay.js b/nassh/js/nassh_stream_google_relay.js
index 2ef48d6..be8b852 100644
--- a/nassh/js/nassh_stream_google_relay.js
+++ b/nassh/js/nassh_stream_google_relay.js
@@ -432,10 +432,7 @@
     return;
 
   var u8 = new Uint8Array(e.data);
-  var ack = (u8[0] << 24) |
-            (u8[1] << 16) |
-            (u8[2] <<  8) |
-            (u8[3] <<  0);
+  var ack = lib.array.arrayBigEndianToUint32(u8);
 
   // Acks are unsigned 24 bits. Negative means error.
   if (ack < 0) {
diff --git a/nassh/js/nassh_stream_sshagent_relay.js b/nassh/js/nassh_stream_sshagent_relay.js
index 55e22a7..dad3470 100644
--- a/nassh/js/nassh_stream_sshagent_relay.js
+++ b/nassh/js/nassh_stream_sshagent_relay.js
@@ -33,10 +33,7 @@
     if (msg.data) {
       // Prepare header.
       var size = msg.data.length;
-      var hdr = [(size >>> 24) & 255,
-                 (size >>> 16) & 255,
-                 (size >>> 8) & 255,
-                 (size >>> 0) & 255];
+      var hdr = lib.array.uint32ToArrayBigEndian(size);
       // Append body.
       var bData = hdr.concat(msg.data);
 
@@ -89,10 +86,7 @@
   // Message header, 4 bytes of length.
   if (this.writeBuffer_.length < 4) return;
 
-  var size = ((this.writeBuffer_[0] & 255) << 24) +
-             ((this.writeBuffer_[1] & 255) << 16) +
-             ((this.writeBuffer_[2] & 255) << 8) +
-             ((this.writeBuffer_[3] & 255) << 0);
+  var size = lib.array.arrayBigEndianToUint32(this.writeBuffer_);
 
   // Message body.
   if (this.writeBuffer_.length < 4 + size) return;