Browse Source

Project import generated by Copybara.

PiperOrigin-RevId: 293139128
Protobuf Team 5 years ago
parent
commit
cc938ea39a
70 changed files with 25118 additions and 0 deletions
  1. 314 0
      js/experimental/benchmarks/code_size/apps_jspb/all_types_proto2.js
  2. 314 0
      js/experimental/benchmarks/code_size/apps_jspb/all_types_proto3.js
  3. 53 0
      js/experimental/benchmarks/code_size/apps_jspb/popular_types_proto2.js
  4. 53 0
      js/experimental/benchmarks/code_size/apps_jspb/popular_types_proto3.js
  5. 57 0
      js/experimental/benchmarks/code_size/code_size_base.js
  6. 227 0
      js/experimental/benchmarks/code_size/kernel/all_types.js
  7. 68 0
      js/experimental/benchmarks/code_size/kernel/popular_types.js
  8. 183 0
      js/experimental/runtime/bytestring.js
  9. 33 0
      js/experimental/runtime/bytestring_internal.js
  10. 277 0
      js/experimental/runtime/bytestring_test.js
  11. 403 0
      js/experimental/runtime/int64.js
  12. 213 0
      js/experimental/runtime/int64_test.js
  13. 708 0
      js/experimental/runtime/internal/checks.js
  14. 58 0
      js/experimental/runtime/internal/checks_test.js
  15. 79 0
      js/experimental/runtime/kernel/bool_test_pairs.js
  16. 256 0
      js/experimental/runtime/kernel/buffer_decoder.js
  17. 18 0
      js/experimental/runtime/kernel/buffer_decoder_helper.js
  18. 104 0
      js/experimental/runtime/kernel/buffer_decoder_test.js
  19. 91 0
      js/experimental/runtime/kernel/conformance/conformance_request.js
  20. 76 0
      js/experimental/runtime/kernel/conformance/conformance_response.js
  21. 103 0
      js/experimental/runtime/kernel/conformance/conformance_testee.js
  22. 62 0
      js/experimental/runtime/kernel/conformance/conformance_testee_runner_node.js
  23. 309 0
      js/experimental/runtime/kernel/conformance/test_all_types_proto2.js
  24. 310 0
      js/experimental/runtime/kernel/conformance/test_all_types_proto3.js
  25. 16 0
      js/experimental/runtime/kernel/conformance/wire_format.js
  26. 89 0
      js/experimental/runtime/kernel/double_test_pairs.js
  27. 196 0
      js/experimental/runtime/kernel/field.js
  28. 36 0
      js/experimental/runtime/kernel/fixed32_test_pairs.js
  29. 78 0
      js/experimental/runtime/kernel/float_test_pairs.js
  30. 192 0
      js/experimental/runtime/kernel/indexer.js
  31. 359 0
      js/experimental/runtime/kernel/indexer_test.js
  32. 71 0
      js/experimental/runtime/kernel/int32_test_pairs.js
  33. 59 0
      js/experimental/runtime/kernel/int64_test_pairs.js
  34. 24 0
      js/experimental/runtime/kernel/internal_message.js
  35. 3767 0
      js/experimental/runtime/kernel/lazy_accessor.js
  36. 266 0
      js/experimental/runtime/kernel/lazy_accessor_compatibility_test.js
  37. 7443 0
      js/experimental/runtime/kernel/lazy_accessor_repeated_test.js
  38. 2054 0
      js/experimental/runtime/kernel/lazy_accessor_test.js
  39. 59 0
      js/experimental/runtime/kernel/packed_bool_test_pairs.js
  40. 52 0
      js/experimental/runtime/kernel/packed_double_test_pairs.js
  41. 34 0
      js/experimental/runtime/kernel/packed_fixed32_test_pairs.js
  42. 34 0
      js/experimental/runtime/kernel/packed_float_test_pairs.js
  43. 33 0
      js/experimental/runtime/kernel/packed_int32_test_pairs.js
  44. 34 0
      js/experimental/runtime/kernel/packed_int64_test_pairs.js
  45. 34 0
      js/experimental/runtime/kernel/packed_sfixed32_test_pairs.js
  46. 53 0
      js/experimental/runtime/kernel/packed_sfixed64_test_pairs.js
  47. 33 0
      js/experimental/runtime/kernel/packed_sint32_test_pairs.js
  48. 34 0
      js/experimental/runtime/kernel/packed_sint64_test_pairs.js
  49. 33 0
      js/experimental/runtime/kernel/packed_uint32_test_pairs.js
  50. 464 0
      js/experimental/runtime/kernel/reader.js
  51. 423 0
      js/experimental/runtime/kernel/reader_test.js
  52. 46 0
      js/experimental/runtime/kernel/sfixed32_test_pairs.js
  53. 52 0
      js/experimental/runtime/kernel/sfixed64_test_pairs.js
  54. 57 0
      js/experimental/runtime/kernel/sint32_test_pairs.js
  55. 60 0
      js/experimental/runtime/kernel/sint64_test_pairs.js
  56. 133 0
      js/experimental/runtime/kernel/storage.js
  57. 165 0
      js/experimental/runtime/kernel/storage_test.js
  58. 116 0
      js/experimental/runtime/kernel/textencoding.js
  59. 113 0
      js/experimental/runtime/kernel/textencoding_test.js
  60. 116 0
      js/experimental/runtime/kernel/typed_arrays.js
  61. 191 0
      js/experimental/runtime/kernel/typed_arrays_test.js
  62. 61 0
      js/experimental/runtime/kernel/uint32_test_pairs.js
  63. 28 0
      js/experimental/runtime/kernel/uint8arrays.js
  64. 47 0
      js/experimental/runtime/kernel/uint8arrays_test.js
  65. 17 0
      js/experimental/runtime/kernel/wire_type.js
  66. 772 0
      js/experimental/runtime/kernel/writer.js
  67. 910 0
      js/experimental/runtime/kernel/writer_test.js
  68. 1763 0
      js/experimental/runtime/testing/binary/test_message.js
  69. 44 0
      js/experimental/runtime/testing/ensure_custom_equality_test.js
  70. 88 0
      js/experimental/runtime/testing/jasmine_protobuf.js

+ 314 - 0
js/experimental/benchmarks/code_size/apps_jspb/all_types_proto2.js

@@ -0,0 +1,314 @@
+/**
+ * @fileoverview The code size benchmark of apps JSPB for proto2 all types
+ */
+goog.module('protobuf.benchmark.code_size.apps_jspb.AllTypesProto2');
+
+// const ForeignEnum = goog.require('proto.proto2_unittest.ForeignEnum');
+const ForeignMessage = goog.require('proto.proto2_unittest.ForeignMessage');
+const TestAllTypes = goog.require('proto.proto2_unittest.TestAllTypes');
+const TestPackedTypes = goog.require('proto.proto2_unittest.TestPackedTypes');
+const {ensureCommonBaseLine} = goog.require('protobuf.benchmark.codeSize.codeSizeBase');
+
+ensureCommonBaseLine();
+
+/**
+ * The testing scenario is the same as kernel one.
+ * We have
+ *  1) add element to repeated fields
+ *  2) add element list to repeated fields
+ *  3) set fields
+ *  4) set repeated fields element
+ *  5) get fields
+ *  6) get repeated fields element
+ *  7) get repeated fields length
+ * @return {string}
+ */
+function accessAllTypes() {
+  const msgAllTypes = TestAllTypes.deserialize('');
+  const msgPackedTypes = TestPackedTypes.deserialize('');
+
+  msgPackedTypes.addPackedBool(true);
+  [true].forEach((e) => msgPackedTypes.addPackedBool(e));
+  msgAllTypes.addRepeatedBool(true, 1);
+  [true].forEach((e) => msgAllTypes.addRepeatedBool(e));
+  msgAllTypes.addRepeatedBytes('1', 1);
+  ['1'].forEach((e) => msgAllTypes.addRepeatedBytes(e));
+  msgPackedTypes.addPackedDouble(1.0);
+  [1.0].forEach((e) => msgPackedTypes.addPackedDouble(e));
+  msgAllTypes.addRepeatedDouble(1.0, 1);
+  [1.0].forEach((e) => msgAllTypes.addRepeatedDouble(e));
+  msgPackedTypes.addPackedFixed32(1, 1);
+  [1].forEach((e) => msgPackedTypes.addPackedFixed32(e));
+  msgAllTypes.addRepeatedFixed32(1, 1);
+  [1].forEach((e) => msgAllTypes.addRepeatedFixed32(e));
+  msgPackedTypes.addPackedFixed64(1, 1);
+  [1].forEach((e) => msgPackedTypes.addPackedFixed64(e));
+  msgAllTypes.addRepeatedFixed64(1, 1);
+  [1].forEach((e) => msgAllTypes.addRepeatedFixed64(e));
+  msgPackedTypes.addPackedFloat(1.0, 1);
+  [1.0].forEach((e) => msgPackedTypes.addPackedFloat(e));
+  msgAllTypes.addRepeatedFloat(1.0, 1);
+  [1.0].forEach((e) => msgAllTypes.addRepeatedFloat(e));
+  msgPackedTypes.addPackedInt32(1, 1);
+  [1].forEach((e) => msgPackedTypes.addPackedInt32(e));
+  msgAllTypes.addRepeatedInt32(1, 1);
+  [1].forEach((e) => msgAllTypes.addRepeatedInt32(e));
+  msgPackedTypes.addPackedInt64(1, 1);
+  [1].forEach((e) => msgPackedTypes.addPackedInt64(e));
+  msgAllTypes.addRepeatedInt64(1, 1);
+  [1].forEach((e) => msgAllTypes.addRepeatedInt64(e));
+  // msgPackedTypes.addPackedEnum(ForeignEnum.FOREIGN_BAR);
+  // [ForeignEnum.FOREIGN_BAR].forEach((e) => msgPackedTypes.addPackedEnum(e));
+  // msgAllTypes.addRepeatedForeignEnum(ForeignEnum.FOREIGN_BAR);
+  // [ForeignEnum.FOREIGN_BAR].forEach(
+  //     (e) => msgAllTypes.addRepeatedForeignEnum(e));
+  msgAllTypes.addRepeatedForeignMessage(ForeignMessage.deserialize(''), 1);
+  [ForeignMessage.deserialize('')].forEach(
+      (e) => msgAllTypes.addRepeatedForeignMessage(e));
+  msgPackedTypes.addPackedSfixed32(1, 1);
+  [1].forEach((e) => msgPackedTypes.addPackedSfixed32(e));
+  msgAllTypes.addRepeatedSfixed32(1, 1);
+  [1].forEach((e) => msgAllTypes.addRepeatedSfixed32(e));
+  msgPackedTypes.addPackedSfixed64(1, 1);
+  [1].forEach((e) => msgPackedTypes.addPackedSfixed64(e));
+  msgAllTypes.addRepeatedSfixed64(1, 1);
+  [1].forEach((e) => msgAllTypes.addRepeatedSfixed64(e));
+  msgPackedTypes.addPackedSint32(1, 1);
+  [1].forEach((e) => msgPackedTypes.addPackedSint32(e));
+  msgAllTypes.addRepeatedSint32(1, 1);
+  [1].forEach((e) => msgAllTypes.addRepeatedSint32(e));
+  msgPackedTypes.addPackedSint64(1, 1);
+  [1].forEach((e) => msgPackedTypes.addPackedSint64(e));
+  msgAllTypes.addRepeatedSint64(1, 1);
+  [1].forEach((e) => msgAllTypes.addRepeatedSint64(e));
+  msgAllTypes.addRepeatedString('', 1);
+  [''].forEach((e) => msgAllTypes.addRepeatedString(e));
+  msgPackedTypes.addPackedUint32(1, 1);
+  [1].forEach((e) => msgPackedTypes.addPackedUint32(e));
+  msgAllTypes.addRepeatedUint32(1, 1);
+  [1].forEach((e) => msgAllTypes.addRepeatedUint32(e));
+  msgPackedTypes.addPackedUint64(1, 1);
+  [1].forEach((e) => msgPackedTypes.addPackedUint64(e));
+  msgAllTypes.addRepeatedUint64(1, 1);
+  [1].forEach((e) => msgAllTypes.addRepeatedUint64(e));
+
+  msgAllTypes.setOptionalBool(true);
+  msgAllTypes.setOptionalBytes('');
+  msgAllTypes.setOptionalDouble(1.0);
+  msgAllTypes.setOptionalFixed32(1);
+  msgAllTypes.setOptionalFixed64(1);
+  msgAllTypes.setOptionalFloat(1.0);
+  msgAllTypes.setOptionalInt32(1);
+  msgAllTypes.setOptionalInt64(1);
+  // msgAllTypes.setOptionalForeignEnum(ForeignEnum.FOREIGN_BAR);
+  msgAllTypes.setOptionalForeignMessage(ForeignMessage.deserialize(''));
+  msgAllTypes.setOptionalSfixed32(1);
+  msgAllTypes.setOptionalSfixed64(1);
+  msgAllTypes.setOptionalSint32(1);
+  msgAllTypes.setOptionalSint64(1);
+  msgAllTypes.setOptionalString('');
+  msgAllTypes.setOptionalUint32(1);
+  msgAllTypes.setOptionalUint64(1);
+  msgPackedTypes.setPackedBoolList([true]);
+  let arrayVal;
+  arrayVal = msgPackedTypes.getPackedBoolList();
+  arrayVal[0] = true;
+  msgPackedTypes.setPackedBoolList(arrayVal);
+  msgAllTypes.setRepeatedBoolList([true]);
+  arrayVal = msgAllTypes.getRepeatedBoolList();
+  arrayVal[0] = true;
+  msgAllTypes.setRepeatedBoolList(arrayVal);
+  msgAllTypes.setRepeatedBytesList(['']);
+  arrayVal = msgAllTypes.getRepeatedBytesList();
+  arrayVal[0] = '';
+  msgAllTypes.setRepeatedBytesList(arrayVal);
+  msgPackedTypes.setPackedDoubleList([1.0]);
+  arrayVal = msgPackedTypes.getPackedDoubleList();
+  arrayVal[0] = 1.0;
+  msgPackedTypes.setPackedDoubleList(arrayVal);
+  msgAllTypes.setRepeatedDoubleList([1.0]);
+  arrayVal = msgAllTypes.getRepeatedDoubleList();
+  arrayVal[0] = 1.0;
+  msgAllTypes.setRepeatedDoubleList(arrayVal);
+  msgPackedTypes.setPackedFixed32List([1]);
+  arrayVal = msgPackedTypes.getPackedFixed32List();
+  arrayVal[0] = 1;
+  msgPackedTypes.setPackedFixed32List(arrayVal);
+  msgAllTypes.setRepeatedFixed32List([1]);
+  arrayVal = msgAllTypes.getRepeatedFixed32List();
+  arrayVal[0] = 1;
+  msgAllTypes.setRepeatedFixed32List(arrayVal);
+  msgPackedTypes.setPackedFixed64List([1]);
+  arrayVal = msgPackedTypes.getPackedFixed64List();
+  arrayVal[0] = 1;
+  msgPackedTypes.setPackedFixed64List(arrayVal);
+  msgAllTypes.setRepeatedFixed64List([1]);
+  arrayVal = msgAllTypes.getRepeatedFixed64List();
+  arrayVal[0] = 1;
+  msgAllTypes.setRepeatedFixed64List(arrayVal);
+  msgPackedTypes.setPackedFloatList([1.0]);
+  arrayVal = msgPackedTypes.getPackedFloatList();
+  arrayVal[0] = 1.0;
+  msgPackedTypes.setPackedFloatList(arrayVal);
+  msgAllTypes.setRepeatedFloatList([1.0]);
+  arrayVal = msgAllTypes.getRepeatedFloatList();
+  arrayVal[0] = 1.0;
+  msgAllTypes.setRepeatedFloatList(arrayVal);
+  msgPackedTypes.setPackedInt32List([1]);
+  arrayVal = msgPackedTypes.getPackedInt32List();
+  arrayVal[0] = 1;
+  msgPackedTypes.setPackedInt32List(arrayVal);
+  msgAllTypes.setRepeatedInt32List([1]);
+  arrayVal = msgAllTypes.getRepeatedInt32List();
+  arrayVal[0] = 1;
+  msgAllTypes.setRepeatedInt32List(arrayVal);
+  msgPackedTypes.setPackedInt64List([1]);
+  arrayVal = msgPackedTypes.getPackedInt64List();
+  arrayVal[0] = 1;
+  msgPackedTypes.setPackedInt64List(arrayVal);
+  msgAllTypes.setRepeatedInt64List([1]);
+  arrayVal = msgAllTypes.getRepeatedInt64List();
+  arrayVal[0] = 1;
+  msgAllTypes.setRepeatedInt64List(arrayVal);
+  // msgPackedTypes.setPackedEnumList([ForeignEnum.FOREIGN_BAR]);
+  // arrayVal = msgPackedTypes.getPackedEnumList();
+  // arrayVal[0] = ForeignEnum.FOREIGN_BAR;
+  // msgPackedTypes.setPackedEnumList(arrayVal);
+  // msgAllTypes.setRepeatedForeignEnumList([ForeignEnum.FOREIGN_BAR]);
+  // arrayVal = msgAllTypes.getRepeatedForeignEnumList();
+  // arrayVal[0] = ForeignEnum.FOREIGN_BAR;
+  // msgAllTypes.setRepeatedForeignEnumList(arrayVal);
+  msgAllTypes.setRepeatedForeignMessageList([ForeignMessage.deserialize('')]);
+  arrayVal = msgAllTypes.getRepeatedForeignMessageList();
+  arrayVal[0] = ForeignMessage.deserialize('');
+  msgAllTypes.setRepeatedForeignMessageList(arrayVal);
+  msgPackedTypes.setPackedSfixed32List([1]);
+  arrayVal = msgPackedTypes.getPackedSfixed32List();
+  arrayVal[0] = 1;
+  msgPackedTypes.setPackedSfixed32List(arrayVal);
+  msgAllTypes.setRepeatedSfixed32List([1]);
+  arrayVal = msgAllTypes.getRepeatedSfixed32List();
+  arrayVal[0] = 1;
+  msgAllTypes.setRepeatedSfixed32List(arrayVal);
+  msgPackedTypes.setPackedSfixed64List([1]);
+  arrayVal = msgPackedTypes.getPackedSfixed64List();
+  arrayVal[0] = 1;
+  msgPackedTypes.setPackedSfixed64List(arrayVal);
+  msgAllTypes.setRepeatedSfixed64List([1]);
+  arrayVal = msgAllTypes.getRepeatedSfixed64List();
+  arrayVal[0] = 1;
+  msgAllTypes.setRepeatedSfixed64List(arrayVal);
+  msgPackedTypes.setPackedSint32List([1]);
+  arrayVal = msgPackedTypes.getPackedSint32List();
+  arrayVal[0] = 1;
+  msgPackedTypes.setPackedSint32List(arrayVal);
+  msgAllTypes.setRepeatedSint32List([1]);
+  arrayVal = msgAllTypes.getRepeatedSint32List();
+  arrayVal[0] = 1;
+  msgAllTypes.setRepeatedSint32List(arrayVal);
+  msgPackedTypes.setPackedSint64List([1]);
+  arrayVal = msgPackedTypes.getPackedSint64List();
+  arrayVal[0] = 1;
+  msgPackedTypes.setPackedSint64List(arrayVal);
+  msgAllTypes.setRepeatedSint64List([1]);
+  arrayVal = msgAllTypes.getRepeatedSint64List();
+  arrayVal[0] = 1;
+  msgAllTypes.setRepeatedSint64List(arrayVal);
+  msgPackedTypes.setPackedUint32List([1]);
+  arrayVal = msgPackedTypes.getPackedUint32List();
+  arrayVal[0] = 1;
+  msgPackedTypes.setPackedUint32List(arrayVal);
+  msgAllTypes.setRepeatedUint32List([1]);
+  arrayVal = msgAllTypes.getRepeatedUint32List();
+  arrayVal[0] = 1;
+  msgAllTypes.setRepeatedUint32List(arrayVal);
+  msgPackedTypes.setPackedUint64List([1]);
+  arrayVal = msgPackedTypes.getPackedUint64List();
+  arrayVal[0] = 1;
+  msgPackedTypes.setPackedUint64List(arrayVal);
+  msgAllTypes.setRepeatedUint64List([1]);
+  arrayVal = msgAllTypes.getRepeatedUint64List();
+  arrayVal[0] = 1;
+  msgAllTypes.setRepeatedUint64List(arrayVal);
+
+  let s = '';
+  s += msgAllTypes.getOptionalBool() || false;
+  s += msgAllTypes.getOptionalBytes() || '';
+  // s += msgAllTypes.getOptionalBytes_asB64() || "";
+  // s += msgAllTypes.getOptionalBytes_asU8() || new Uint8Array([]);
+  s += msgAllTypes.getOptionalDouble() || 0.0;
+  s += msgAllTypes.getOptionalFixed32() || 0;
+  s += msgAllTypes.getOptionalFixed64() || 0;
+  s += msgAllTypes.getOptionalFloat() || 0.0;
+  s += msgAllTypes.getOptionalInt32() || 0;
+  s += msgAllTypes.getOptionalInt64() || 0;
+  // s += msgAllTypes.getOptionalForeignEnum() || ForeignEnum.FOREIGN_BAR;
+  s += msgAllTypes.getOptionalForeignMessage();
+  s += msgAllTypes.getOptionalSfixed32() || 0;
+  s += msgAllTypes.getOptionalSfixed64() || 0;
+  s += msgAllTypes.getOptionalSint32() || 0;
+  s += msgAllTypes.getOptionalSint64() || 0;
+  s += msgAllTypes.getOptionalString() || '';
+  s += msgAllTypes.getOptionalUint32() || 0;
+  s += msgAllTypes.getOptionalUint64() || 0;
+  s += msgAllTypes.getRepeatedBoolList();
+  s += msgAllTypes.getRepeatedBoolList()[0];
+  s += msgAllTypes.getRepeatedBoolList().length;
+  s += msgAllTypes.getRepeatedBytesList();
+  s += msgAllTypes.getRepeatedBytesList()[0];
+  s += msgAllTypes.getRepeatedBytesList().length;
+  s += msgAllTypes.getRepeatedBytesList_asB64();
+  s += msgAllTypes.getRepeatedBytesList_asU8();
+  s += msgAllTypes.getRepeatedDoubleList();
+  s += msgAllTypes.getRepeatedDoubleList()[0];
+  s += msgAllTypes.getRepeatedDoubleList().length;
+  s += msgAllTypes.getRepeatedFixed32List();
+  s += msgAllTypes.getRepeatedFixed32List()[0];
+  s += msgAllTypes.getRepeatedFixed32List().length;
+  s += msgAllTypes.getRepeatedFixed64List();
+  s += msgAllTypes.getRepeatedFixed64List()[0];
+  s += msgAllTypes.getRepeatedFixed64List().length;
+  s += msgAllTypes.getRepeatedFloatList();
+  s += msgAllTypes.getRepeatedFloatList()[0];
+  s += msgAllTypes.getRepeatedFloatList().length;
+  s += msgAllTypes.getRepeatedInt32List();
+  s += msgAllTypes.getRepeatedInt32List()[0];
+  s += msgAllTypes.getRepeatedInt32List().length;
+  s += msgAllTypes.getRepeatedInt64List();
+  s += msgAllTypes.getRepeatedInt64List()[0];
+  s += msgAllTypes.getRepeatedInt64List().length;
+  // s += msgAllTypes.getRepeatedForeignEnumList();
+  // s += msgAllTypes.getRepeatedForeignEnumList()[0];
+  // s += msgAllTypes.getRepeatedForeignEnumList().length;
+  s += msgAllTypes.getRepeatedForeignMessageList();
+  s += msgAllTypes.getRepeatedForeignMessageList()[0];
+  s += msgAllTypes.getRepeatedForeignMessageList().length;
+  s += msgAllTypes.getRepeatedSfixed32List();
+  s += msgAllTypes.getRepeatedSfixed32List()[0];
+  s += msgAllTypes.getRepeatedSfixed32List().length;
+  s += msgAllTypes.getRepeatedSfixed64List();
+  s += msgAllTypes.getRepeatedSfixed64List()[0];
+  s += msgAllTypes.getRepeatedSfixed64List().length;
+  s += msgAllTypes.getRepeatedSint32List();
+  s += msgAllTypes.getRepeatedSint32List()[0];
+  s += msgAllTypes.getRepeatedSint32List().length;
+  s += msgAllTypes.getRepeatedSint64List();
+  s += msgAllTypes.getRepeatedSint64List()[0];
+  s += msgAllTypes.getRepeatedSint64List().length;
+  s += msgAllTypes.getRepeatedStringList();
+  s += msgAllTypes.getRepeatedStringList()[0];
+  s += msgAllTypes.getRepeatedStringList().length;
+  s += msgAllTypes.getRepeatedUint32List();
+  s += msgAllTypes.getRepeatedUint32List()[0];
+  s += msgAllTypes.getRepeatedUint32List().length;
+  s += msgAllTypes.getRepeatedUint64List();
+  s += msgAllTypes.getRepeatedUint64List()[0];
+  s += msgAllTypes.getRepeatedUint64List().length;
+
+  s += msgAllTypes.serialize();
+  s += msgPackedTypes.serialize();
+
+  return s;
+}
+
+goog.global['__hiddenTest'] += accessAllTypes();

+ 314 - 0
js/experimental/benchmarks/code_size/apps_jspb/all_types_proto3.js

@@ -0,0 +1,314 @@
+/**
+ * @fileoverview The code size benchmark of apps JSPB for proto3 all types
+ */
+goog.module('protobuf.benchmark.code_size.apps_jspb.AllTypesProto3');
+
+// const ForeignEnum = goog.require('proto.proto3_unittest.ForeignEnum');
+const ForeignMessage = goog.require('proto.proto3_unittest.ForeignMessage');
+const TestAllTypes = goog.require('proto.proto3_unittest.TestAllTypes');
+const TestPackedTypes = goog.require('proto.proto3_unittest.TestPackedTypes');
+const {ensureCommonBaseLine} = goog.require('protobuf.benchmark.codeSize.codeSizeBase');
+
+ensureCommonBaseLine();
+
+/**
+ * The testing scenario is the same as kernel one.
+ * We have
+ *  1) add element to repeated fields
+ *  2) add element list to repeated fields
+ *  3) set fields
+ *  4) set repeated fields element
+ *  5) get fields
+ *  6) get repeated fields element
+ *  7) get repeated fields length
+ * @return {string}
+ */
+function accessAllTypes() {
+  const msgAllTypes = TestAllTypes.deserialize('');
+  const msgPackedTypes = TestPackedTypes.deserialize('');
+
+  msgPackedTypes.addPackedBool(true);
+  [true].forEach((e) => msgPackedTypes.addPackedBool(e));
+  msgAllTypes.addRepeatedBool(true, 1);
+  [true].forEach((e) => msgAllTypes.addRepeatedBool(e));
+  msgAllTypes.addRepeatedBytes('1', 1);
+  ['1'].forEach((e) => msgAllTypes.addRepeatedBytes(e));
+  msgPackedTypes.addPackedDouble(1.0);
+  [1.0].forEach((e) => msgPackedTypes.addPackedDouble(e));
+  msgAllTypes.addRepeatedDouble(1.0, 1);
+  [1.0].forEach((e) => msgAllTypes.addRepeatedDouble(e));
+  msgPackedTypes.addPackedFixed32(1, 1);
+  [1].forEach((e) => msgPackedTypes.addPackedFixed32(e));
+  msgAllTypes.addRepeatedFixed32(1, 1);
+  [1].forEach((e) => msgAllTypes.addRepeatedFixed32(e));
+  msgPackedTypes.addPackedFixed64(1, 1);
+  [1].forEach((e) => msgPackedTypes.addPackedFixed64(e));
+  msgAllTypes.addRepeatedFixed64(1, 1);
+  [1].forEach((e) => msgAllTypes.addRepeatedFixed64(e));
+  msgPackedTypes.addPackedFloat(1.0, 1);
+  [1.0].forEach((e) => msgPackedTypes.addPackedFloat(e));
+  msgAllTypes.addRepeatedFloat(1.0, 1);
+  [1.0].forEach((e) => msgAllTypes.addRepeatedFloat(e));
+  msgPackedTypes.addPackedInt32(1, 1);
+  [1].forEach((e) => msgPackedTypes.addPackedInt32(e));
+  msgAllTypes.addRepeatedInt32(1, 1);
+  [1].forEach((e) => msgAllTypes.addRepeatedInt32(e));
+  msgPackedTypes.addPackedInt64(1, 1);
+  [1].forEach((e) => msgPackedTypes.addPackedInt64(e));
+  msgAllTypes.addRepeatedInt64(1, 1);
+  [1].forEach((e) => msgAllTypes.addRepeatedInt64(e));
+  // msgPackedTypes.addPackedEnum(ForeignEnum.FOREIGN_BAR);
+  // [ForeignEnum.FOREIGN_BAR].forEach((e) => msgPackedTypes.addPackedEnum(e));
+  // msgAllTypes.addRepeatedForeignEnum(ForeignEnum.FOREIGN_BAR);
+  // [ForeignEnum.FOREIGN_BAR].forEach(
+  //     (e) => msgAllTypes.addRepeatedForeignEnum(e));
+  msgAllTypes.addRepeatedForeignMessage(ForeignMessage.deserialize(''), 1);
+  [ForeignMessage.deserialize('')].forEach(
+      (e) => msgAllTypes.addRepeatedForeignMessage(e));
+  msgPackedTypes.addPackedSfixed32(1, 1);
+  [1].forEach((e) => msgPackedTypes.addPackedSfixed32(e));
+  msgAllTypes.addRepeatedSfixed32(1, 1);
+  [1].forEach((e) => msgAllTypes.addRepeatedSfixed32(e));
+  msgPackedTypes.addPackedSfixed64(1, 1);
+  [1].forEach((e) => msgPackedTypes.addPackedSfixed64(e));
+  msgAllTypes.addRepeatedSfixed64(1, 1);
+  [1].forEach((e) => msgAllTypes.addRepeatedSfixed64(e));
+  msgPackedTypes.addPackedSint32(1, 1);
+  [1].forEach((e) => msgPackedTypes.addPackedSint32(e));
+  msgAllTypes.addRepeatedSint32(1, 1);
+  [1].forEach((e) => msgAllTypes.addRepeatedSint32(e));
+  msgPackedTypes.addPackedSint64(1, 1);
+  [1].forEach((e) => msgPackedTypes.addPackedSint64(e));
+  msgAllTypes.addRepeatedSint64(1, 1);
+  [1].forEach((e) => msgAllTypes.addRepeatedSint64(e));
+  msgAllTypes.addRepeatedString('', 1);
+  [''].forEach((e) => msgAllTypes.addRepeatedString(e));
+  msgPackedTypes.addPackedUint32(1, 1);
+  [1].forEach((e) => msgPackedTypes.addPackedUint32(e));
+  msgAllTypes.addRepeatedUint32(1, 1);
+  [1].forEach((e) => msgAllTypes.addRepeatedUint32(e));
+  msgPackedTypes.addPackedUint64(1, 1);
+  [1].forEach((e) => msgPackedTypes.addPackedUint64(e));
+  msgAllTypes.addRepeatedUint64(1, 1);
+  [1].forEach((e) => msgAllTypes.addRepeatedUint64(e));
+
+  msgAllTypes.setOptionalBool(true);
+  msgAllTypes.setOptionalBytes('');
+  msgAllTypes.setOptionalDouble(1.0);
+  msgAllTypes.setOptionalFixed32(1);
+  msgAllTypes.setOptionalFixed64(1);
+  msgAllTypes.setOptionalFloat(1.0);
+  msgAllTypes.setOptionalInt32(1);
+  msgAllTypes.setOptionalInt64(1);
+  // msgAllTypes.setOptionalForeignEnum(ForeignEnum.FOREIGN_BAR);
+  msgAllTypes.setOptionalForeignMessage(ForeignMessage.deserialize(''));
+  msgAllTypes.setOptionalSfixed32(1);
+  msgAllTypes.setOptionalSfixed64(1);
+  msgAllTypes.setOptionalSint32(1);
+  msgAllTypes.setOptionalSint64(1);
+  msgAllTypes.setOptionalString('');
+  msgAllTypes.setOptionalUint32(1);
+  msgAllTypes.setOptionalUint64(1);
+  msgPackedTypes.setPackedBoolList([true]);
+  let arrayVal;
+  arrayVal = msgPackedTypes.getPackedBoolList();
+  arrayVal[0] = true;
+  msgPackedTypes.setPackedBoolList(arrayVal);
+  msgAllTypes.setRepeatedBoolList([true]);
+  arrayVal = msgAllTypes.getRepeatedBoolList();
+  arrayVal[0] = true;
+  msgAllTypes.setRepeatedBoolList(arrayVal);
+  msgAllTypes.setRepeatedBytesList(['']);
+  arrayVal = msgAllTypes.getRepeatedBytesList();
+  arrayVal[0] = '';
+  msgAllTypes.setRepeatedBytesList(arrayVal);
+  msgPackedTypes.setPackedDoubleList([1.0]);
+  arrayVal = msgPackedTypes.getPackedDoubleList();
+  arrayVal[0] = 1.0;
+  msgPackedTypes.setPackedDoubleList(arrayVal);
+  msgAllTypes.setRepeatedDoubleList([1.0]);
+  arrayVal = msgAllTypes.getRepeatedDoubleList();
+  arrayVal[0] = 1.0;
+  msgAllTypes.setRepeatedDoubleList(arrayVal);
+  msgPackedTypes.setPackedFixed32List([1]);
+  arrayVal = msgPackedTypes.getPackedFixed32List();
+  arrayVal[0] = 1;
+  msgPackedTypes.setPackedFixed32List(arrayVal);
+  msgAllTypes.setRepeatedFixed32List([1]);
+  arrayVal = msgAllTypes.getRepeatedFixed32List();
+  arrayVal[0] = 1;
+  msgAllTypes.setRepeatedFixed32List(arrayVal);
+  msgPackedTypes.setPackedFixed64List([1]);
+  arrayVal = msgPackedTypes.getPackedFixed64List();
+  arrayVal[0] = 1;
+  msgPackedTypes.setPackedFixed64List(arrayVal);
+  msgAllTypes.setRepeatedFixed64List([1]);
+  arrayVal = msgAllTypes.getRepeatedFixed64List();
+  arrayVal[0] = 1;
+  msgAllTypes.setRepeatedFixed64List(arrayVal);
+  msgPackedTypes.setPackedFloatList([1.0]);
+  arrayVal = msgPackedTypes.getPackedFloatList();
+  arrayVal[0] = 1.0;
+  msgPackedTypes.setPackedFloatList(arrayVal);
+  msgAllTypes.setRepeatedFloatList([1.0]);
+  arrayVal = msgAllTypes.getRepeatedFloatList();
+  arrayVal[0] = 1.0;
+  msgAllTypes.setRepeatedFloatList(arrayVal);
+  msgPackedTypes.setPackedInt32List([1]);
+  arrayVal = msgPackedTypes.getPackedInt32List();
+  arrayVal[0] = 1;
+  msgPackedTypes.setPackedInt32List(arrayVal);
+  msgAllTypes.setRepeatedInt32List([1]);
+  arrayVal = msgAllTypes.getRepeatedInt32List();
+  arrayVal[0] = 1;
+  msgAllTypes.setRepeatedInt32List(arrayVal);
+  msgPackedTypes.setPackedInt64List([1]);
+  arrayVal = msgPackedTypes.getPackedInt64List();
+  arrayVal[0] = 1;
+  msgPackedTypes.setPackedInt64List(arrayVal);
+  msgAllTypes.setRepeatedInt64List([1]);
+  arrayVal = msgAllTypes.getRepeatedInt64List();
+  arrayVal[0] = 1;
+  msgAllTypes.setRepeatedInt64List(arrayVal);
+  // msgPackedTypes.setPackedEnumList([ForeignEnum.FOREIGN_BAR]);
+  // arrayVal = msgPackedTypes.getPackedEnumList();
+  // arrayVal[0] = ForeignEnum.FOREIGN_BAR;
+  // msgPackedTypes.setPackedEnumList(arrayVal);
+  // msgAllTypes.setRepeatedForeignEnumList([ForeignEnum.FOREIGN_BAR]);
+  // arrayVal = msgAllTypes.getRepeatedForeignEnumList();
+  // arrayVal[0] = ForeignEnum.FOREIGN_BAR;
+  // msgAllTypes.setRepeatedForeignEnumList(arrayVal);
+  msgAllTypes.setRepeatedForeignMessageList([ForeignMessage.deserialize('')]);
+  arrayVal = msgAllTypes.getRepeatedForeignMessageList();
+  arrayVal[0] = ForeignMessage.deserialize('');
+  msgAllTypes.setRepeatedForeignMessageList(arrayVal);
+  msgPackedTypes.setPackedSfixed32List([1]);
+  arrayVal = msgPackedTypes.getPackedSfixed32List();
+  arrayVal[0] = 1;
+  msgPackedTypes.setPackedSfixed32List(arrayVal);
+  msgAllTypes.setRepeatedSfixed32List([1]);
+  arrayVal = msgAllTypes.getRepeatedSfixed32List();
+  arrayVal[0] = 1;
+  msgAllTypes.setRepeatedSfixed32List(arrayVal);
+  msgPackedTypes.setPackedSfixed64List([1]);
+  arrayVal = msgPackedTypes.getPackedSfixed64List();
+  arrayVal[0] = 1;
+  msgPackedTypes.setPackedSfixed64List(arrayVal);
+  msgAllTypes.setRepeatedSfixed64List([1]);
+  arrayVal = msgAllTypes.getRepeatedSfixed64List();
+  arrayVal[0] = 1;
+  msgAllTypes.setRepeatedSfixed64List(arrayVal);
+  msgPackedTypes.setPackedSint32List([1]);
+  arrayVal = msgPackedTypes.getPackedSint32List();
+  arrayVal[0] = 1;
+  msgPackedTypes.setPackedSint32List(arrayVal);
+  msgAllTypes.setRepeatedSint32List([1]);
+  arrayVal = msgAllTypes.getRepeatedSint32List();
+  arrayVal[0] = 1;
+  msgAllTypes.setRepeatedSint32List(arrayVal);
+  msgPackedTypes.setPackedSint64List([1]);
+  arrayVal = msgPackedTypes.getPackedSint64List();
+  arrayVal[0] = 1;
+  msgPackedTypes.setPackedSint64List(arrayVal);
+  msgAllTypes.setRepeatedSint64List([1]);
+  arrayVal = msgAllTypes.getRepeatedSint64List();
+  arrayVal[0] = 1;
+  msgAllTypes.setRepeatedSint64List(arrayVal);
+  msgPackedTypes.setPackedUint32List([1]);
+  arrayVal = msgPackedTypes.getPackedUint32List();
+  arrayVal[0] = 1;
+  msgPackedTypes.setPackedUint32List(arrayVal);
+  msgAllTypes.setRepeatedUint32List([1]);
+  arrayVal = msgAllTypes.getRepeatedUint32List();
+  arrayVal[0] = 1;
+  msgAllTypes.setRepeatedUint32List(arrayVal);
+  msgPackedTypes.setPackedUint64List([1]);
+  arrayVal = msgPackedTypes.getPackedUint64List();
+  arrayVal[0] = 1;
+  msgPackedTypes.setPackedUint64List(arrayVal);
+  msgAllTypes.setRepeatedUint64List([1]);
+  arrayVal = msgAllTypes.getRepeatedUint64List();
+  arrayVal[0] = 1;
+  msgAllTypes.setRepeatedUint64List(arrayVal);
+
+  let s = '';
+  s += msgAllTypes.getOptionalBool() || false;
+  s += msgAllTypes.getOptionalBytes() || '';
+  // s += msgAllTypes.getOptionalBytes_asB64() || "";
+  // s += msgAllTypes.getOptionalBytes_asU8() || new Uint8Array([]);
+  s += msgAllTypes.getOptionalDouble() || 0.0;
+  s += msgAllTypes.getOptionalFixed32() || 0;
+  s += msgAllTypes.getOptionalFixed64() || 0;
+  s += msgAllTypes.getOptionalFloat() || 0.0;
+  s += msgAllTypes.getOptionalInt32() || 0;
+  s += msgAllTypes.getOptionalInt64() || 0;
+  // s += msgAllTypes.getOptionalForeignEnum() || ForeignEnum.FOREIGN_BAR;
+  s += msgAllTypes.getOptionalForeignMessage();
+  s += msgAllTypes.getOptionalSfixed32() || 0;
+  s += msgAllTypes.getOptionalSfixed64() || 0;
+  s += msgAllTypes.getOptionalSint32() || 0;
+  s += msgAllTypes.getOptionalSint64() || 0;
+  s += msgAllTypes.getOptionalString() || '';
+  s += msgAllTypes.getOptionalUint32() || 0;
+  s += msgAllTypes.getOptionalUint64() || 0;
+  s += msgAllTypes.getRepeatedBoolList();
+  s += msgAllTypes.getRepeatedBoolList()[0];
+  s += msgAllTypes.getRepeatedBoolList().length;
+  s += msgAllTypes.getRepeatedBytesList();
+  s += msgAllTypes.getRepeatedBytesList()[0];
+  s += msgAllTypes.getRepeatedBytesList().length;
+  s += msgAllTypes.getRepeatedBytesList_asB64();
+  s += msgAllTypes.getRepeatedBytesList_asU8();
+  s += msgAllTypes.getRepeatedDoubleList();
+  s += msgAllTypes.getRepeatedDoubleList()[0];
+  s += msgAllTypes.getRepeatedDoubleList().length;
+  s += msgAllTypes.getRepeatedFixed32List();
+  s += msgAllTypes.getRepeatedFixed32List()[0];
+  s += msgAllTypes.getRepeatedFixed32List().length;
+  s += msgAllTypes.getRepeatedFixed64List();
+  s += msgAllTypes.getRepeatedFixed64List()[0];
+  s += msgAllTypes.getRepeatedFixed64List().length;
+  s += msgAllTypes.getRepeatedFloatList();
+  s += msgAllTypes.getRepeatedFloatList()[0];
+  s += msgAllTypes.getRepeatedFloatList().length;
+  s += msgAllTypes.getRepeatedInt32List();
+  s += msgAllTypes.getRepeatedInt32List()[0];
+  s += msgAllTypes.getRepeatedInt32List().length;
+  s += msgAllTypes.getRepeatedInt64List();
+  s += msgAllTypes.getRepeatedInt64List()[0];
+  s += msgAllTypes.getRepeatedInt64List().length;
+  // s += msgAllTypes.getRepeatedForeignEnumList();
+  // s += msgAllTypes.getRepeatedForeignEnumList()[0];
+  // s += msgAllTypes.getRepeatedForeignEnumList().length;
+  s += msgAllTypes.getRepeatedForeignMessageList();
+  s += msgAllTypes.getRepeatedForeignMessageList()[0];
+  s += msgAllTypes.getRepeatedForeignMessageList().length;
+  s += msgAllTypes.getRepeatedSfixed32List();
+  s += msgAllTypes.getRepeatedSfixed32List()[0];
+  s += msgAllTypes.getRepeatedSfixed32List().length;
+  s += msgAllTypes.getRepeatedSfixed64List();
+  s += msgAllTypes.getRepeatedSfixed64List()[0];
+  s += msgAllTypes.getRepeatedSfixed64List().length;
+  s += msgAllTypes.getRepeatedSint32List();
+  s += msgAllTypes.getRepeatedSint32List()[0];
+  s += msgAllTypes.getRepeatedSint32List().length;
+  s += msgAllTypes.getRepeatedSint64List();
+  s += msgAllTypes.getRepeatedSint64List()[0];
+  s += msgAllTypes.getRepeatedSint64List().length;
+  s += msgAllTypes.getRepeatedStringList();
+  s += msgAllTypes.getRepeatedStringList()[0];
+  s += msgAllTypes.getRepeatedStringList().length;
+  s += msgAllTypes.getRepeatedUint32List();
+  s += msgAllTypes.getRepeatedUint32List()[0];
+  s += msgAllTypes.getRepeatedUint32List().length;
+  s += msgAllTypes.getRepeatedUint64List();
+  s += msgAllTypes.getRepeatedUint64List()[0];
+  s += msgAllTypes.getRepeatedUint64List().length;
+
+  s += msgAllTypes.serialize();
+  s += msgPackedTypes.serialize();
+
+  return s;
+}
+
+goog.global['__hiddenTest'] += accessAllTypes();

+ 53 - 0
js/experimental/benchmarks/code_size/apps_jspb/popular_types_proto2.js

@@ -0,0 +1,53 @@
+/**
+ * @fileoverview The code size benchmark of apps JSPB for proto2 popular types.
+ */
+goog.module('protobuf.benchmark.code_size.apps_jspb.PopularTypesProto2');
+
+// const ForeignEnum = goog.require('proto.proto2_unittest.ForeignEnum');
+const ForeignMessage = goog.require('proto.proto2_unittest.ForeignMessage');
+const TestAllTypes = goog.require('proto.proto2_unittest.TestAllTypes');
+const {ensureCommonBaseLine} = goog.require('protobuf.benchmark.codeSize.codeSizeBase');
+
+ensureCommonBaseLine();
+
+/**
+ * @return {string}
+ */
+function accessPopularTypes() {
+  const msgAllTypes = TestAllTypes.deserialize('');
+  msgAllTypes.addRepeatedForeignMessage(ForeignMessage.deserialize(''), 1);
+  [ForeignMessage.deserialize('')].forEach(
+      (e) => msgAllTypes.addRepeatedForeignMessage(e));
+
+  msgAllTypes.setOptionalString('');
+  msgAllTypes.setOptionalInt32(1);
+  msgAllTypes.setOptionalForeignMessage(ForeignMessage.deserialize(''));
+  msgAllTypes.setOptionalBool(true);
+  // msgAllTypes.setOptionalForeignEnum(ForeignEnum.FOREIGN_BAR);
+  msgAllTypes.setOptionalInt64(1);
+  msgAllTypes.setOptionalDouble(1.0);
+  msgAllTypes.setRepeatedForeignMessageList([ForeignMessage.deserialize('')]);
+  let arrayVal = msgAllTypes.getRepeatedForeignMessageList();
+  arrayVal[0] = ForeignMessage.deserialize('');
+  msgAllTypes.setRepeatedForeignMessageList(arrayVal);
+  msgAllTypes.setOptionalUint64(1);
+
+  let s = '';
+  s += msgAllTypes.getOptionalString();
+  s += msgAllTypes.getOptionalInt32();
+  s += msgAllTypes.getOptionalForeignMessage();
+  s += msgAllTypes.getOptionalBool();
+  // s += msgAllTypes.getOptionalForeignEnum();
+  s += msgAllTypes.getOptionalInt64();
+  s += msgAllTypes.getOptionalDouble();
+  s += msgAllTypes.getRepeatedForeignMessageList();
+  s += msgAllTypes.getRepeatedForeignMessageList()[0];
+  s += msgAllTypes.getRepeatedForeignMessageList().length;
+  s += msgAllTypes.getOptionalUint64();
+
+  s += msgAllTypes.serialize();
+
+  return s;
+}
+
+goog.global['__hiddenTest'] += accessPopularTypes();

+ 53 - 0
js/experimental/benchmarks/code_size/apps_jspb/popular_types_proto3.js

@@ -0,0 +1,53 @@
+/**
+ * @fileoverview The code size benchmark of apps JSPB for proto3 popular types.
+ */
+goog.module('protobuf.benchmark.code_size.apps_jspb.PopularTypesProto3');
+
+// const ForeignEnum = goog.require('proto.proto3_unittest.ForeignEnum');
+const ForeignMessage = goog.require('proto.proto3_unittest.ForeignMessage');
+const TestAllTypes = goog.require('proto.proto3_unittest.TestAllTypes');
+const {ensureCommonBaseLine} = goog.require('protobuf.benchmark.codeSize.codeSizeBase');
+
+ensureCommonBaseLine();
+
+/**
+ * @return {string}
+ */
+function accessPopularTypes() {
+  const msgAllTypes = TestAllTypes.deserialize('');
+  msgAllTypes.addRepeatedForeignMessage(ForeignMessage.deserialize(''), 1);
+  [ForeignMessage.deserialize('')].forEach(
+      (e) => msgAllTypes.addRepeatedForeignMessage(e));
+
+  msgAllTypes.setOptionalString('');
+  msgAllTypes.setOptionalInt32(1);
+  msgAllTypes.setOptionalForeignMessage(ForeignMessage.deserialize(''));
+  msgAllTypes.setOptionalBool(true);
+  // msgAllTypes.setOptionalForeignEnum(ForeignEnum.FOREIGN_BAR);
+  msgAllTypes.setOptionalInt64(1);
+  msgAllTypes.setOptionalDouble(1.0);
+  msgAllTypes.setRepeatedForeignMessageList([ForeignMessage.deserialize('')]);
+  let arrayVal = msgAllTypes.getRepeatedForeignMessageList();
+  arrayVal[0] = ForeignMessage.deserialize('');
+  msgAllTypes.setRepeatedForeignMessageList(arrayVal);
+  msgAllTypes.setOptionalUint64(1);
+
+  let s = '';
+  s += msgAllTypes.getOptionalString();
+  s += msgAllTypes.getOptionalInt32();
+  s += msgAllTypes.getOptionalForeignMessage();
+  s += msgAllTypes.getOptionalBool();
+  // s += msgAllTypes.getOptionalForeignEnum();
+  s += msgAllTypes.getOptionalInt64();
+  s += msgAllTypes.getOptionalDouble();
+  s += msgAllTypes.getRepeatedForeignMessageList();
+  s += msgAllTypes.getRepeatedForeignMessageList()[0];
+  s += msgAllTypes.getRepeatedForeignMessageList().length;
+  s += msgAllTypes.getOptionalUint64();
+
+  s += msgAllTypes.serialize();
+
+  return s;
+}
+
+goog.global['__hiddenTest'] += accessPopularTypes();

+ 57 - 0
js/experimental/benchmarks/code_size/code_size_base.js

@@ -0,0 +1,57 @@
+/**
+ * @fileoverview Ensures types are live that would be live in a typical g3
+ * JS program.
+ *
+ * Making certain constructs live ensures that we compare against the same
+ * baseline for all code size benchmarks. This increases the size
+ * of our benchmarks, but note that this size in a regular app would be
+ * attributes to other places.
+ */
+goog.module('protobuf.benchmark.codeSize.codeSizeBase');
+
+
+/**
+ * Ensures that the array iterator polyfill is live.
+ * @return {string}
+ */
+function useArrayIterator() {
+  let a = [];
+  let s = '';
+  for (let value of a) {
+    s += value;
+  }
+  return s;
+}
+
+/**
+ * Ensures that the symbol iterator polyfill is live.
+ * @return {string}
+ */
+function useSymbolIterator() {
+  /**
+   * @implements {Iterable}
+   */
+  class Foo {
+    /** @return {!Iterator} */
+    [Symbol.iterator]() {}
+  }
+
+  let foo = new Foo();
+  let s = '';
+  for (let value of foo) {
+    s += value;
+  }
+  return s;
+}
+
+/**
+ * Ensures certain base libs are live so we can have an apples to apples
+ * comparison for code size of different implementations
+ */
+function ensureCommonBaseLine() {
+  goog.global['__hiddenTest'] += useArrayIterator();
+  goog.global['__hiddenTest'] += useSymbolIterator();
+}
+
+
+exports = {ensureCommonBaseLine};

+ 227 - 0
js/experimental/benchmarks/code_size/kernel/all_types.js

@@ -0,0 +1,227 @@
+/**
+ * @fileoverview The code size benchmark of binary kernel for accessing all
+ * types setter and getter.
+ */
+goog.module('protobuf.benchmark.KernelCodeSizeBenchmarkAllTypes');
+
+const ByteString = goog.require('protobuf.ByteString');
+const Int64 = goog.require('protobuf.Int64');
+const LazyAccessor = goog.require('protobuf.binary.LazyAccessor');
+const TestMessage = goog.require('protobuf.testing.binary.TestMessage');
+const {ensureCommonBaseLine} = goog.require('protobuf.benchmark.codeSize.codeSizeBase');
+
+ensureCommonBaseLine();
+
+
+/**
+ * @return {string}
+ */
+function accessAllTypes() {
+  const message = new TestMessage(LazyAccessor.createEmpty());
+
+  message.addPackedBoolElement(1, true);
+  message.addPackedBoolIterable(1, [true]);
+  message.addUnpackedBoolElement(1, true);
+  message.addUnpackedBoolIterable(1, [true]);
+  message.addRepeatedBytesElement(1, ByteString.EMPTY);
+  message.addRepeatedBytesIterable(1, [ByteString.EMPTY]);
+  message.addPackedDoubleElement(1, 1.0);
+  message.addPackedDoubleIterable(1, [1.0]);
+  message.addUnpackedDoubleElement(1, 1.0);
+  message.addUnpackedDoubleIterable(1, [1.0]);
+  message.addPackedFixed32Element(1, 1);
+  message.addPackedFixed32Iterable(1, [1]);
+  message.addUnpackedFixed32Element(1, 1);
+  message.addUnpackedFixed32Iterable(1, [1]);
+  message.addPackedFixed64Element(1, Int64.fromBits(0, 1));
+  message.addPackedFixed64Iterable(1, [Int64.fromBits(0, 1)]);
+  message.addUnpackedFixed64Element(1, Int64.fromBits(0, 1));
+  message.addUnpackedFixed64Iterable(1, [Int64.fromBits(0, 1)]);
+  message.addPackedFloatElement(1, 1.0);
+  message.addPackedFloatIterable(1, [1.0]);
+  message.addUnpackedFloatElement(1, 1.0);
+  message.addUnpackedFloatIterable(1, [1.0]);
+  message.addPackedInt32Element(1, 1);
+  message.addPackedInt32Iterable(1, [1]);
+  message.addUnpackedInt32Element(1, 1);
+  message.addUnpackedInt32Iterable(1, [1]);
+  message.addPackedInt64Element(1, Int64.fromBits(0, 1));
+  message.addPackedInt64Iterable(1, [Int64.fromBits(0, 1)]);
+  message.addUnpackedInt64Element(1, Int64.fromBits(0, 1));
+  message.addUnpackedInt64Iterable(1, [Int64.fromBits(0, 1)]);
+  message.addRepeatedMessageElement(1, message, TestMessage.instanceCreator);
+  message.addRepeatedMessageIterable(1, [message], TestMessage.instanceCreator);
+  message.addPackedSfixed32Element(1, 1);
+  message.addPackedSfixed32Iterable(1, [1]);
+  message.addUnpackedSfixed32Element(1, 1);
+  message.addUnpackedSfixed32Iterable(1, [1]);
+  message.addPackedSfixed64Element(1, Int64.fromBits(0, 1));
+  message.addPackedSfixed64Iterable(1, [Int64.fromBits(0, 1)]);
+  message.addUnpackedSfixed64Element(1, Int64.fromBits(0, 1));
+  message.addUnpackedSfixed64Iterable(1, [Int64.fromBits(0, 1)]);
+  message.addPackedSint32Element(1, 1);
+  message.addPackedSint32Iterable(1, [1]);
+  message.addUnpackedSint32Element(1, 1);
+  message.addUnpackedSint32Iterable(1, [1]);
+  message.addPackedSint64Element(1, Int64.fromBits(0, 1));
+  message.addPackedSint64Iterable(1, [Int64.fromBits(0, 1)]);
+  message.addUnpackedSint64Element(1, Int64.fromBits(0, 1));
+  message.addUnpackedSint64Iterable(1, [Int64.fromBits(0, 1)]);
+  message.addRepeatedStringElement(1, '');
+  message.addRepeatedStringIterable(1, ['']);
+  message.addPackedUint32Element(1, 1);
+  message.addPackedUint32Iterable(1, [1]);
+  message.addUnpackedUint32Element(1, 1);
+  message.addUnpackedUint32Iterable(1, [1]);
+  message.addPackedUint64Element(1, Int64.fromBits(0, 1));
+  message.addPackedUint64Iterable(1, [Int64.fromBits(0, 1)]);
+  message.addUnpackedUint64Element(1, Int64.fromBits(0, 1));
+  message.addUnpackedUint64Iterable(1, [Int64.fromBits(0, 1)]);
+
+  message.setBool(1, true);
+  message.setBytes(1, ByteString.EMPTY);
+  message.setDouble(1, 1.0);
+  message.setFixed32(1, 1);
+  message.setFixed64(1, Int64.fromBits(0, 1));
+  message.setFloat(1, 1.0);
+  message.setInt32(1, 1);
+  message.setInt64(1, Int64.fromBits(0, 1));
+  message.setMessage(1, message);
+  message.setSfixed32(1, 1);
+  message.setSfixed64(1, Int64.fromBits(0, 1));
+  message.setSint32(1, 1);
+  message.setSint64(1, Int64.fromBits(0, 1));
+  message.setString(1, 'abc');
+  message.setUint32(1, 1);
+  message.setUint64(1, Int64.fromBits(0, 1));
+  message.setPackedBoolElement(1, 0, true);
+  message.setPackedBoolIterable(1, [true]);
+  message.setUnpackedBoolElement(1, 0, true);
+  message.setUnpackedBoolIterable(1, [true]);
+  message.setRepeatedBytesElement(1, 0, ByteString.EMPTY);
+  message.setRepeatedBytesIterable(1, [ByteString.EMPTY]);
+  message.setPackedDoubleElement(1, 0, 1.0);
+  message.setPackedDoubleIterable(1, [1.0]);
+  message.setUnpackedDoubleElement(1, 0, 1.0);
+  message.setUnpackedDoubleIterable(1, [1.0]);
+  message.setPackedFixed32Element(1, 0, 1);
+  message.setPackedFixed32Iterable(1, [1]);
+  message.setUnpackedFixed32Element(1, 0, 1);
+  message.setUnpackedFixed32Iterable(1, [1]);
+  message.setPackedFixed64Element(1, 0, Int64.fromBits(0, 1));
+  message.setPackedFixed64Iterable(1, [Int64.fromBits(0, 1)]);
+  message.setUnpackedFixed64Element(1, 0, Int64.fromBits(0, 1));
+  message.setUnpackedFixed64Iterable(1, [Int64.fromBits(0, 1)]);
+  message.setPackedFloatElement(1, 0, 1.0);
+  message.setPackedFloatIterable(1, [1.0]);
+  message.setUnpackedFloatElement(1, 0, 1.0);
+  message.setUnpackedFloatIterable(1, [1.0]);
+  message.setPackedInt32Element(1, 0, 1);
+  message.setPackedInt32Iterable(1, [1]);
+  message.setUnpackedInt32Element(1, 0, 1);
+  message.setUnpackedInt32Iterable(1, [1]);
+  message.setPackedInt64Element(1, 0, Int64.fromBits(0, 1));
+  message.setPackedInt64Iterable(1, [Int64.fromBits(0, 1)]);
+  message.setUnpackedInt64Element(1, 0, Int64.fromBits(0, 1));
+  message.setUnpackedInt64Iterable(1, [Int64.fromBits(0, 1)]);
+  message.setRepeatedMessageElement(1, message, TestMessage.instanceCreator, 0);
+  message.setRepeatedMessageIterable(1, [message]);
+  message.setPackedSfixed32Element(1, 0, 1);
+  message.setPackedSfixed32Iterable(1, [1]);
+  message.setUnpackedSfixed32Element(1, 0, 1);
+  message.setUnpackedSfixed32Iterable(1, [1]);
+  message.setPackedSfixed64Element(1, 0, Int64.fromBits(0, 1));
+  message.setPackedSfixed64Iterable(1, [Int64.fromBits(0, 1)]);
+  message.setUnpackedSfixed64Element(1, 0, Int64.fromBits(0, 1));
+  message.setUnpackedSfixed64Iterable(1, [Int64.fromBits(0, 1)]);
+  message.setRepeatedStringElement(1, 0, '');
+  message.setRepeatedStringIterable(1, ['']);
+  message.setPackedSint32Element(1, 0, 1);
+  message.setPackedSint32Iterable(1, [1]);
+  message.setUnpackedSint32Element(1, 0, 1);
+  message.setUnpackedSint32Iterable(1, [1]);
+  message.setPackedSint64Element(1, 0, Int64.fromBits(0, 1));
+  message.setPackedSint64Iterable(1, [Int64.fromBits(0, 1)]);
+  message.setUnpackedSint64Element(1, 0, Int64.fromBits(0, 1));
+  message.setUnpackedSint64Iterable(1, [Int64.fromBits(0, 1)]);
+  message.setPackedUint32Element(1, 0, 1);
+  message.setPackedUint32Iterable(1, [1]);
+  message.setUnpackedUint32Element(1, 0, 1);
+  message.setUnpackedUint32Iterable(1, [1]);
+  message.setPackedUint64Element(1, 0, Int64.fromBits(0, 1));
+  message.setPackedUint64Iterable(1, [Int64.fromBits(0, 1)]);
+  message.setUnpackedUint64Element(1, 0, Int64.fromBits(0, 1));
+  message.setUnpackedUint64Iterable(1, [Int64.fromBits(0, 1)]);
+
+  let s = '';
+  s += message.getBoolWithDefault(1);
+  s += message.getBytesWithDefault(1);
+  s += message.getDoubleWithDefault(1);
+  s += message.getFixed32WithDefault(1);
+  s += message.getFixed64WithDefault(1);
+  s += message.getFloatWithDefault(1);
+  s += message.getInt32WithDefault(1);
+  s += message.getInt64WithDefault(1);
+  s += message.getMessage(1, TestMessage.instanceCreator);
+  s += message.getSfixed32WithDefault(1);
+  s += message.getSfixed64WithDefault(1);
+  s += message.getSint32WithDefault(1);
+  s += message.getSint64WithDefault(1);
+  s += message.getStringWithDefault(1);
+  s += message.getUint32WithDefault(1);
+  s += message.getUint64WithDefault(1);
+  s += message.getRepeatedBoolElement(1, 0);
+  s += message.getRepeatedBoolIterable(1);
+  s += message.getRepeatedBoolSize(1);
+  s += message.getRepeatedBytesElement(1, 0);
+  s += message.getRepeatedBytesIterable(1);
+  s += message.getRepeatedBytesSize(1);
+  s += message.getRepeatedDoubleElement(1, 0);
+  s += message.getRepeatedDoubleIterable(1);
+  s += message.getRepeatedDoubleSize(1);
+  s += message.getRepeatedFixed32Element(1, 0);
+  s += message.getRepeatedFixed32Iterable(1);
+  s += message.getRepeatedFixed32Size(1);
+  s += message.getRepeatedFixed64Element(1, 0);
+  s += message.getRepeatedFixed64Iterable(1);
+  s += message.getRepeatedFixed64Size(1);
+  s += message.getRepeatedFloatElement(1, 0);
+  s += message.getRepeatedFloatIterable(1);
+  s += message.getRepeatedFloatSize(1);
+  s += message.getRepeatedInt32Element(1, 0);
+  s += message.getRepeatedInt32Iterable(1);
+  s += message.getRepeatedInt32Size(1);
+  s += message.getRepeatedInt64Element(1, 0);
+  s += message.getRepeatedInt64Iterable(1);
+  s += message.getRepeatedInt64Size(1);
+  s += message.getRepeatedMessageElement(1, TestMessage.instanceCreator, 0);
+  s += message.getRepeatedMessageIterable(1, TestMessage.instanceCreator);
+  s += message.getRepeatedMessageSize(1, TestMessage.instanceCreator);
+  s += message.getRepeatedSfixed32Element(1, 0);
+  s += message.getRepeatedSfixed32Iterable(1);
+  s += message.getRepeatedSfixed32Size(1);
+  s += message.getRepeatedSfixed64Element(1, 0);
+  s += message.getRepeatedSfixed64Iterable(1);
+  s += message.getRepeatedSfixed64Size(1);
+  s += message.getRepeatedSint32Element(1, 0);
+  s += message.getRepeatedSint32Iterable(1);
+  s += message.getRepeatedSint32Size(1);
+  s += message.getRepeatedSint64Element(1, 0);
+  s += message.getRepeatedSint64Iterable(1);
+  s += message.getRepeatedSint64Size(1);
+  s += message.getRepeatedStringElement(1, 0);
+  s += message.getRepeatedStringIterable(1);
+  s += message.getRepeatedStringSize(1);
+  s += message.getRepeatedUint32Element(1, 0);
+  s += message.getRepeatedUint32Iterable(1);
+  s += message.getRepeatedUint32Size(1);
+  s += message.getRepeatedUint64Element(1, 0);
+  s += message.getRepeatedUint64Iterable(1);
+  s += message.getRepeatedUint64Size(1);
+
+  s += message.serialize();
+
+  return s;
+}
+
+goog.global['__hiddenTest'] += accessAllTypes();

+ 68 - 0
js/experimental/benchmarks/code_size/kernel/popular_types.js

@@ -0,0 +1,68 @@
+/**
+ * @fileoverview The code size benchmark of binary kernel for accessing all
+ * popular types setter and getter.
+ *
+ * The types are those whose usage are more than 1%:
+ *
+ * ('STRING__LABEL_OPTIONAL', '29.7214%')
+ * ('INT32__LABEL_OPTIONAL', '17.7277%')
+ * ('MESSAGE__LABEL_OPTIONAL', '15.6462%')
+ * ('BOOL__LABEL_OPTIONAL', '13.0038%')
+ * ('ENUM__LABEL_OPTIONAL', '11.4466%')
+ * ('INT64__LABEL_OPTIONAL', '3.2198%')
+ * ('DOUBLE__LABEL_OPTIONAL', '1.357%')
+ * ('MESSAGE__LABEL_REPEATED', '1.2775%')
+ * ('FIXED32__LABEL_REQUIRED', '1.2%')
+ * ('UINT64__LABEL_OPTIONAL', '1.1771%')
+ * ('STRING__LABEL_REQUIRED', '1.0785%')
+ *
+ */
+goog.module('protobuf.benchmark.KernelCodeSizeBenchmarkPopularTypes');
+
+const Int64 = goog.require('protobuf.Int64');
+const LazyAccessor = goog.require('protobuf.binary.LazyAccessor');
+const TestMessage = goog.require('protobuf.testing.binary.TestMessage');
+const {ensureCommonBaseLine} = goog.require('protobuf.benchmark.codeSize.codeSizeBase');
+
+ensureCommonBaseLine();
+
+
+/**
+ * @return {string}
+ */
+function accessAllTypes() {
+  const message = new TestMessage(LazyAccessor.createEmpty());
+
+  message.addRepeatedMessageElement(1, message, TestMessage.instanceCreator);
+  message.addRepeatedMessageIterable(1, [message], TestMessage.instanceCreator);
+
+  message.setString(1, 'abc');
+  message.setInt32(1, 1);
+  message.setMessage(1, message);
+  message.setBool(1, true);
+  message.setInt64(1, Int64.fromBits(0, 1));
+  message.setDouble(1, 1.0);
+  message.setRepeatedMessageElement(1, message, TestMessage.instanceCreator, 0);
+  message.setRepeatedMessageIterable(1, [message]);
+  message.setUint64(1, Int64.fromBits(0, 1));
+
+
+  let s = '';
+  s += message.getStringWithDefault(1);
+  s += message.getInt32WithDefault(1);
+  s += message.getMessage(1, TestMessage.instanceCreator);
+  s += message.getMessageOrNull(1, TestMessage.instanceCreator);
+  s += message.getBoolWithDefault(1);
+  s += message.getInt64WithDefault(1);
+  s += message.getDoubleWithDefault(1);
+  s += message.getRepeatedMessageElement(1, TestMessage.instanceCreator, 0);
+  s += message.getRepeatedMessageIterable(1, TestMessage.instanceCreator);
+  s += message.getRepeatedMessageSize(1, TestMessage.instanceCreator);
+  s += message.getUint64WithDefault(1);
+
+  s += message.serialize();
+
+  return s;
+}
+
+goog.global['__hiddenTest'] += accessAllTypes();

+ 183 - 0
js/experimental/runtime/bytestring.js

@@ -0,0 +1,183 @@
+/**
+ * @fileoverview Provides ByteString as a basic data type for protos.
+ */
+goog.module('protobuf.ByteString');
+
+const base64 = goog.require('goog.crypt.base64');
+const {arrayBufferSlice, cloneArrayBufferView, hashUint8Array, uint8ArrayEqual} = goog.require('protobuf.binary.typedArrays');
+
+/**
+ * Immutable sequence of bytes.
+ *
+ * Bytes can be obtained as an ArrayBuffer or a base64 encoded string.
+ * @final
+ */
+class ByteString {
+  /**
+   * @param {?Uint8Array} bytes
+   * @param {?string} base64
+   * @private
+   */
+  constructor(bytes, base64) {
+    /** @private {?Uint8Array}*/
+    this.bytes_ = bytes;
+    /** @private {?string} */
+    this.base64_ = base64;
+    /** @private {number} */
+    this.hashCode_ = 0;
+  }
+
+  /**
+   * Constructs a ByteString instance from a base64 string.
+   * @param {string} value
+   * @return {!ByteString}
+   */
+  static fromBase64String(value) {
+    if (value == null) {
+      throw new Error('value must not be null');
+    }
+    return new ByteString(/* bytes */ null, value);
+  }
+
+  /**
+   * Constructs a ByteString from an array buffer.
+   * @param {!ArrayBuffer} bytes
+   * @param {number=} start
+   * @param {number=} end
+   * @return {!ByteString}
+   */
+  static fromArrayBuffer(bytes, start = 0, end = undefined) {
+    return new ByteString(
+        new Uint8Array(arrayBufferSlice(bytes, start, end)), /* base64 */ null);
+  }
+
+  /**
+   * Constructs a ByteString from any ArrayBufferView (e.g. DataView,
+   * TypedArray, Uint8Array, etc.).
+   * @param {!ArrayBufferView} bytes
+   * @return {!ByteString}
+   */
+  static fromArrayBufferView(bytes) {
+    return new ByteString(cloneArrayBufferView(bytes), /* base64 */ null);
+  }
+
+  /**
+   * Constructs a ByteString from an Uint8Array. DON'T MODIFY the underlying
+   * ArrayBuffer, since the ByteString directly uses it without making a copy.
+   *
+   * This method exists so that internal APIs can construct a ByteString without
+   * paying the penalty of copying an ArrayBuffer when that ArrayBuffer is not
+   * supposed to change. It is exposed to a limited number of internal classes
+   * through bytestring_internal.js.
+   *
+   * @param {!Uint8Array} bytes
+   * @return {!ByteString}
+   * @package
+   */
+  static fromUint8ArrayUnsafe(bytes) {
+    return new ByteString(bytes, /* base64 */ null);
+  }
+
+  /**
+   * Returns this ByteString as an ArrayBuffer.
+   * @return {!ArrayBuffer}
+   */
+  toArrayBuffer() {
+    const bytes = this.ensureBytes_();
+    return arrayBufferSlice(
+        bytes.buffer, bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
+  }
+
+  /**
+   * Returns this ByteString as an Uint8Array. DON'T MODIFY the returned array,
+   * since the ByteString holds the reference to the same array.
+   *
+   * This method exists so that internal APIs can get contents of a ByteString
+   * without paying the penalty of copying an ArrayBuffer. It is exposed to a
+   * limited number of internal classes through bytestring_internal.js.
+   * @return {!Uint8Array}
+   * @package
+   */
+  toUint8ArrayUnsafe() {
+    return this.ensureBytes_();
+  }
+
+  /**
+   * Returns this ByteString as a base64 encoded string.
+   * @return {string}
+   */
+  toBase64String() {
+    return this.ensureBase64String_();
+  }
+
+  /**
+   * Returns true for Bytestrings that contain identical values.
+   * @param {*} other
+   * @return {boolean}
+   */
+  equals(other) {
+    if (this === other) {
+      return true;
+    }
+
+    if (!(other instanceof ByteString)) {
+      return false;
+    }
+
+    const otherByteString = /** @type {!ByteString} */ (other);
+    return uint8ArrayEqual(this.ensureBytes_(), otherByteString.ensureBytes_());
+  }
+
+  /**
+   * Returns a number (int32) that is suitable for using in hashed structures.
+   * @return {number}
+   */
+  hashCode() {
+    if (this.hashCode_ == 0) {
+      this.hashCode_ = hashUint8Array(this.ensureBytes_());
+    }
+    return this.hashCode_;
+  }
+
+  /**
+   * Returns true if the bytestring is empty.
+   * @return {boolean}
+   */
+  isEmpty() {
+    if (this.bytes_ != null && this.bytes_.byteLength == 0) {
+      return true;
+    }
+    if (this.base64_ != null && this.base64_.length == 0) {
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * @return {!Uint8Array}
+   * @private
+   */
+  ensureBytes_() {
+    if (this.bytes_) {
+      return this.bytes_;
+    }
+    return this.bytes_ = base64.decodeStringToUint8Array(
+               /** @type {string} */ (this.base64_));
+  }
+
+  /**
+   * @return {string}
+   * @private
+   */
+  ensureBase64String_() {
+    if (this.base64_ == null) {
+      this.base64_ = base64.encodeByteArray(this.bytes_);
+    }
+    return this.base64_;
+  }
+}
+
+/** @const {!ByteString} */
+ByteString.EMPTY = new ByteString(new Uint8Array(0), null);
+
+exports = ByteString;

+ 33 - 0
js/experimental/runtime/bytestring_internal.js

@@ -0,0 +1,33 @@
+/**
+ * @fileoverview Exposes internal only functions for ByteString. The
+ * corresponding BUILD rule restricts access to this file to only the binary
+ * kernel and APIs directly using the binary kernel.
+ */
+goog.module('protobuf.byteStringInternal');
+
+const ByteString = goog.require('protobuf.ByteString');
+
+/**
+ * Constructs a ByteString from an Uint8Array. DON'T MODIFY the underlying
+ * ArrayBuffer, since the ByteString directly uses it without making a copy.
+ * @param {!Uint8Array} bytes
+ * @return {!ByteString}
+ */
+function byteStringFromUint8ArrayUnsafe(bytes) {
+  return ByteString.fromUint8ArrayUnsafe(bytes);
+}
+
+/**
+ * Returns this ByteString as an Uint8Array. DON'T MODIFY the returned array,
+ * since the ByteString holds the reference to the same array.
+ * @param {!ByteString} bytes
+ * @return {!Uint8Array}
+ */
+function byteStringToUint8ArrayUnsafe(bytes) {
+  return bytes.toUint8ArrayUnsafe();
+}
+
+exports = {
+  byteStringFromUint8ArrayUnsafe,
+  byteStringToUint8ArrayUnsafe,
+};

+ 277 - 0
js/experimental/runtime/bytestring_test.js

@@ -0,0 +1,277 @@
+goog.module('proto.im.integration.ByteStringFieldsTest');
+goog.setTestOnly();
+
+const ByteString = goog.require('protobuf.ByteString');
+const {arrayBufferSlice} = goog.require('protobuf.binary.typedArrays');
+
+const /** !ArrayBuffer */ TEST_BYTES = new Uint8Array([1, 2, 3, 4]).buffer;
+const /** !ByteString */ TEST_STRING = ByteString.fromArrayBuffer(TEST_BYTES);
+const /** !ArrayBuffer */ PREFIXED_TEST_BYTES =
+    new Uint8Array([0, 1, 2, 3, 4]).buffer;
+const /** string */ HALLO_IN_BASE64 = 'aGFsbG8=';
+const /** string */ HALLO_IN_BASE64_WITH_SPACES = 'a G F s b G 8=';
+const /** !ArrayBufferView */ BYTES_WITH_HALLO = new Uint8Array([
+  'h'.charCodeAt(0),
+  'a'.charCodeAt(0),
+  'l'.charCodeAt(0),
+  'l'.charCodeAt(0),
+  'o'.charCodeAt(0),
+]);
+
+describe('ByteString does', () => {
+  it('create bytestring from buffer', () => {
+    const byteString =
+        ByteString.fromArrayBuffer(arrayBufferSlice(TEST_BYTES, 0));
+    expect(byteString.toArrayBuffer()).toEqual(TEST_BYTES);
+    expect(byteString.toUint8ArrayUnsafe()).toEqual(new Uint8Array(TEST_BYTES));
+  });
+
+  it('create bytestring from ArrayBufferView', () => {
+    const byteString =
+        ByteString.fromArrayBufferView(new Uint8Array(TEST_BYTES));
+    expect(byteString.toArrayBuffer()).toEqual(TEST_BYTES);
+    expect(byteString.toUint8ArrayUnsafe()).toEqual(new Uint8Array(TEST_BYTES));
+  });
+
+  it('create bytestring from subarray', () => {
+    const byteString = ByteString.fromArrayBufferView(
+        new Uint8Array(TEST_BYTES, /* offset */ 1, /* length */ 2));
+    const expected = new Uint8Array([2, 3]);
+    expect(new Uint8Array(byteString.toArrayBuffer())).toEqual(expected);
+    expect(byteString.toUint8ArrayUnsafe()).toEqual(expected);
+  });
+
+  it('create bytestring from Uint8Array (unsafe)', () => {
+    const array = new Uint8Array(TEST_BYTES);
+    const byteString = ByteString.fromUint8ArrayUnsafe(array);
+    expect(byteString.toArrayBuffer()).toEqual(array.buffer);
+    expect(byteString.toUint8ArrayUnsafe()).toBe(array);
+  });
+
+  it('create bytestring from base64 string', () => {
+    const byteString = ByteString.fromBase64String(HALLO_IN_BASE64);
+    expect(byteString.toBase64String()).toEqual(HALLO_IN_BASE64);
+    expect(byteString.toArrayBuffer()).toEqual(BYTES_WITH_HALLO.buffer);
+    expect(byteString.toUint8ArrayUnsafe()).toEqual(BYTES_WITH_HALLO);
+  });
+
+  it('preserve immutability if underlying buffer changes: from buffer', () => {
+    const buffer = new ArrayBuffer(4);
+    const array = new Uint8Array(buffer);
+    array[0] = 1;
+    array[1] = 2;
+    array[2] = 3;
+    array[3] = 4;
+
+    const byteString = ByteString.fromArrayBuffer(buffer);
+    const otherBuffer = byteString.toArrayBuffer();
+
+    expect(otherBuffer).not.toBe(buffer);
+    expect(new Uint8Array(otherBuffer)).toEqual(array);
+
+    // modify the original buffer
+    array[0] = 5;
+    // Are we still returning the original bytes?
+    expect(new Uint8Array(byteString.toArrayBuffer())).toEqual(new Uint8Array([
+      1, 2, 3, 4
+    ]));
+  });
+
+  it('preserve immutability if underlying buffer changes: from ArrayBufferView',
+     () => {
+       const buffer = new ArrayBuffer(4);
+       const array = new Uint8Array(buffer);
+       array[0] = 1;
+       array[1] = 2;
+       array[2] = 3;
+       array[3] = 4;
+
+       const byteString = ByteString.fromArrayBufferView(array);
+       const otherBuffer = byteString.toArrayBuffer();
+
+       expect(otherBuffer).not.toBe(buffer);
+       expect(new Uint8Array(otherBuffer)).toEqual(array);
+
+       // modify the original buffer
+       array[0] = 5;
+       // Are we still returning the original bytes?
+       expect(new Uint8Array(byteString.toArrayBuffer()))
+           .toEqual(new Uint8Array([1, 2, 3, 4]));
+     });
+
+  it('mutate if underlying buffer changes: from Uint8Array (unsafe)', () => {
+    const buffer = new ArrayBuffer(4);
+    const array = new Uint8Array(buffer);
+    array[0] = 1;
+    array[1] = 2;
+    array[2] = 3;
+    array[3] = 4;
+
+    const byteString = ByteString.fromUint8ArrayUnsafe(array);
+    const otherBuffer = byteString.toArrayBuffer();
+
+    expect(otherBuffer).not.toBe(buffer);
+    expect(new Uint8Array(otherBuffer)).toEqual(array);
+
+    // modify the original buffer
+    array[0] = 5;
+    // We are no longer returning the original bytes
+    expect(new Uint8Array(byteString.toArrayBuffer())).toEqual(new Uint8Array([
+      5, 2, 3, 4
+    ]));
+  });
+
+  it('preserve immutability for returned buffers: toArrayBuffer', () => {
+    const byteString = ByteString.fromArrayBufferView(new Uint8Array(4));
+    const buffer1 = byteString.toArrayBuffer();
+    const buffer2 = byteString.toArrayBuffer();
+
+    expect(buffer1).toEqual(buffer2);
+
+    const array1 = new Uint8Array(buffer1);
+    array1[0] = 1;
+
+    expect(buffer1).not.toEqual(buffer2);
+  });
+
+  it('does not preserve immutability for returned buffers: toUint8ArrayUnsafe',
+     () => {
+       const byteString = ByteString.fromUint8ArrayUnsafe(new Uint8Array(4));
+       const array1 = byteString.toUint8ArrayUnsafe();
+       const array2 = byteString.toUint8ArrayUnsafe();
+
+       expect(array1).toEqual(array2);
+       array1[0] = 1;
+
+       expect(array1).toEqual(array2);
+     });
+
+  it('throws when created with null ArrayBufferView', () => {
+    expect(
+        () => ByteString.fromArrayBufferView(
+            /** @type {!ArrayBufferView} */ (/** @type{*} */ (null))))
+        .toThrow();
+  });
+
+  it('throws when created with null buffer', () => {
+    expect(
+        () => ByteString.fromBase64String(
+            /** @type {string} */ (/** @type{*} */ (null))))
+        .toThrow();
+  });
+
+  it('convert base64 to ArrayBuffer', () => {
+    const other = ByteString.fromBase64String(HALLO_IN_BASE64);
+    expect(BYTES_WITH_HALLO).toEqual(new Uint8Array(other.toArrayBuffer()));
+  });
+
+  it('convert base64 with spaces to ArrayBuffer', () => {
+    const other = ByteString.fromBase64String(HALLO_IN_BASE64_WITH_SPACES);
+    expect(new Uint8Array(other.toArrayBuffer())).toEqual(BYTES_WITH_HALLO);
+  });
+
+  it('convert bytes to base64', () => {
+    const other = ByteString.fromArrayBufferView(BYTES_WITH_HALLO);
+    expect(HALLO_IN_BASE64).toEqual(other.toBase64String());
+  });
+
+  it('equal empty bytetring', () => {
+    const empty = ByteString.fromArrayBuffer(new ArrayBuffer(0));
+    expect(ByteString.EMPTY.equals(empty)).toEqual(true);
+  });
+
+  it('equal empty bytestring constructed from ArrayBufferView', () => {
+    const empty = ByteString.fromArrayBufferView(new Uint8Array(0));
+    expect(ByteString.EMPTY.equals(empty)).toEqual(true);
+  });
+
+  it('equal empty bytestring constructed from Uint8Array (unsafe)', () => {
+    const empty = ByteString.fromUint8ArrayUnsafe(new Uint8Array(0));
+    expect(ByteString.EMPTY.equals(empty)).toEqual(true);
+  });
+
+  it('equal empty bytestring constructed from base64', () => {
+    const empty = ByteString.fromBase64String('');
+    expect(ByteString.EMPTY.equals(empty)).toEqual(true);
+  });
+
+  it('equal other instance', () => {
+    const other = ByteString.fromArrayBuffer(arrayBufferSlice(TEST_BYTES, 0));
+    expect(TEST_STRING.equals(other)).toEqual(true);
+  });
+
+  it('not equal different instance', () => {
+    const other =
+        ByteString.fromArrayBuffer(new Uint8Array([1, 2, 3, 4, 5]).buffer);
+    expect(TEST_STRING.equals(other)).toEqual(false);
+  });
+
+  it('equal other instance constructed from ArrayBufferView', () => {
+    const other =
+        ByteString.fromArrayBufferView(new Uint8Array(PREFIXED_TEST_BYTES, 1));
+    expect(TEST_STRING.equals(other)).toEqual(true);
+  });
+
+  it('not equal different instance constructed from ArrayBufferView', () => {
+    const other =
+        ByteString.fromArrayBufferView(new Uint8Array([1, 2, 3, 4, 5]));
+    expect(TEST_STRING.equals(other)).toEqual(false);
+  });
+
+  it('equal other instance constructed from Uint8Array (unsafe)', () => {
+    const other =
+        ByteString.fromUint8ArrayUnsafe(new Uint8Array(PREFIXED_TEST_BYTES, 1));
+    expect(TEST_STRING.equals(other)).toEqual(true);
+  });
+
+  it('not equal different instance constructed from Uint8Array (unsafe)',
+     () => {
+       const other =
+           ByteString.fromUint8ArrayUnsafe(new Uint8Array([1, 2, 3, 4, 5]));
+       expect(TEST_STRING.equals(other)).toEqual(false);
+     });
+
+  it('have same hashcode for empty bytes', () => {
+    const empty = ByteString.fromArrayBuffer(new ArrayBuffer(0));
+    expect(ByteString.EMPTY.hashCode()).toEqual(empty.hashCode());
+  });
+
+  it('have same hashcode for test bytes', () => {
+    const other = ByteString.fromArrayBuffer(arrayBufferSlice(TEST_BYTES, 0));
+    expect(TEST_STRING.hashCode()).toEqual(other.hashCode());
+  });
+
+  it('have same hashcode for test bytes', () => {
+    const other = ByteString.fromArrayBufferView(
+        new Uint8Array(arrayBufferSlice(TEST_BYTES, 0)));
+    expect(TEST_STRING.hashCode()).toEqual(other.hashCode());
+  });
+
+  it('have same hashcode for different instance constructed with base64',
+     () => {
+       const other = ByteString.fromBase64String(HALLO_IN_BASE64);
+       expect(ByteString.fromArrayBufferView(BYTES_WITH_HALLO).hashCode())
+           .toEqual(other.hashCode());
+     });
+
+  it('preserves the length of a Uint8Array', () => {
+    const original = new Uint8Array([105, 183, 51, 251, 253, 118, 247]);
+    const afterByteString = new Uint8Array(
+        ByteString.fromArrayBufferView(original).toArrayBuffer());
+    expect(afterByteString).toEqual(original);
+  });
+
+  it('preserves the length of a base64 value', () => {
+    const expected = new Uint8Array([105, 183, 51, 251, 253, 118, 247]);
+    const afterByteString = new Uint8Array(
+        ByteString.fromBase64String('abcz+/129w').toArrayBuffer());
+    expect(afterByteString).toEqual(expected);
+  });
+
+  it('preserves the length of a base64 value with padding', () => {
+    const expected = new Uint8Array([105, 183, 51, 251, 253, 118, 247]);
+    const afterByteString = new Uint8Array(
+        ByteString.fromBase64String('abcz+/129w==').toArrayBuffer());
+    expect(afterByteString).toEqual(expected);
+  });
+});

+ 403 - 0
js/experimental/runtime/int64.js

@@ -0,0 +1,403 @@
+/**
+ * @fileoverview Protobufs Int64 representation.
+ */
+goog.module('protobuf.Int64');
+
+const Long = goog.require('goog.math.Long');
+const {assert} = goog.require('goog.asserts');
+
+/**
+ * A container for protobufs Int64/Uint64 data type.
+ * @final
+ */
+class Int64 {
+  /** @return {!Int64} */
+  static getZero() {
+    return ZERO;
+  }
+
+  /** @return {!Int64} */
+  static getMinValue() {
+    return MIN_VALUE;
+  }
+
+  /** @return {!Int64} */
+  static getMaxValue() {
+    return MAX_VALUE;
+  }
+
+  /**
+   * Constructs a Int64 given two 32 bit numbers
+   * @param {number} lowBits
+   * @param {number} highBits
+   * @return {!Int64}
+   */
+  static fromBits(lowBits, highBits) {
+    return new Int64(lowBits, highBits);
+  }
+
+  /**
+   * Constructs an Int64 from a signed 32 bit number.
+   * @param {number} value
+   * @return {!Int64}
+   */
+  static fromInt(value) {
+    // TODO: Use our own checking system here.
+    assert(value === (value | 0), 'value should be a 32-bit integer');
+    // Right shift 31 bits so all high bits are equal to the sign bit.
+    // Note: cannot use >> 32, because (1 >> 32) = 1 (!).
+    const signExtendedHighBits = value >> 31;
+    return new Int64(value, signExtendedHighBits);
+  }
+
+  /**
+   * Constructs an Int64 from a number (over 32 bits).
+   * @param {number} value
+   * @return {!Int64}
+   */
+  static fromNumber(value) {
+    if (value > 0) {
+      return new Int64(value, value / TWO_PWR_32_DBL);
+    } else if (value < 0) {
+      return negate(-value, -value / TWO_PWR_32_DBL);
+    }
+    return ZERO;
+  }
+
+  /**
+   * Construct an Int64 from a signed decimal string.
+   * @param {string} value
+   * @return {!Int64}
+   */
+  static fromDecimalString(value) {
+    // TODO: Use our own checking system here.
+    assert(value.length > 0);
+    // The basic Number conversion loses precision, but we can use it for
+    // a quick validation that the format is correct and it is an integer.
+    assert(Math.floor(Number(value)).toString().length == value.length);
+    return decimalStringToInt64(value);
+  }
+
+  /**
+   * Construct an Int64 from a signed hexadecimal string.
+   * @param {string} value
+   * @return {!Int64}
+   */
+  static fromHexString(value) {
+    // TODO: Use our own checking system here.
+    assert(value.length > 0);
+    assert(value.slice(0, 2) == '0x' || value.slice(0, 3) == '-0x');
+    const minus = value[0] === '-';
+    // Strip the 0x or -0x prefix.
+    value = value.slice(minus ? 3 : 2);
+    const lowBits = parseInt(value.slice(-8), 16);
+    const highBits = parseInt(value.slice(-16, -8) || '', 16);
+    return (minus ? negate : Int64.fromBits)(lowBits, highBits);
+  }
+
+  // Note to the reader:
+  // goog.math.Long suffers from a code size issue. JsCompiler almost always
+  // considers toString methods to be alive in a program. So if you are
+  // constructing a Long instance the toString method is assumed to be live.
+  // Unfortunately Long's toString method makes a large chunk of code alive
+  // of the entire class adding 1.3kB (gzip) of extra code size.
+  // Callers that are sensitive to code size and are not using Long already
+  // should avoid calling this method.
+  /**
+   * Creates an Int64 instance from a Long value.
+   * @param {!Long} value
+   * @return {!Int64}
+   */
+  static fromLong(value) {
+    return new Int64(value.getLowBits(), value.getHighBits());
+  }
+
+  /**
+   * @param {number} lowBits
+   * @param {number} highBits
+   * @private
+   */
+  constructor(lowBits, highBits) {
+    /** @const @private {number} */
+    this.lowBits_ = lowBits | 0;
+    /** @const @private {number} */
+    this.highBits_ = highBits | 0;
+  }
+
+  /**
+   * Returns the int64 value as a JavaScript number. This will lose precision
+   * if the number is outside of the safe range for JavaScript of 53 bits
+   * precision.
+   * @return {number}
+   */
+  asNumber() {
+    const result = this.highBits_ * TWO_PWR_32_DBL + this.getLowBitsUnsigned();
+    // TODO: Use our own checking system here.
+    assert(
+        Number.isSafeInteger(result), 'conversion to number loses precision.');
+    return result;
+  }
+
+  // Note to the reader:
+  // goog.math.Long suffers from a code size issue. JsCompiler almost always
+  // considers toString methods to be alive in a program. So if you are
+  // constructing a Long instance the toString method is assumed to be live.
+  // Unfortunately Long's toString method makes a large chunk of code alive
+  // of the entire class adding 1.3kB (gzip) of extra code size.
+  // Callers that are sensitive to code size and are not using Long already
+  // should avoid calling this method.
+  /** @return {!Long} */
+  asLong() {
+    return Long.fromBits(this.lowBits_, this.highBits_);
+  }
+
+  /** @return {number} Signed 32-bit integer value. */
+  getLowBits() {
+    return this.lowBits_;
+  }
+
+  /** @return {number} Signed 32-bit integer value. */
+  getHighBits() {
+    return this.highBits_;
+  }
+
+  /** @return {number} Unsigned 32-bit integer. */
+  getLowBitsUnsigned() {
+    return this.lowBits_ >>> 0;
+  }
+
+  /** @return {number} Unsigned 32-bit integer. */
+  getHighBitsUnsigned() {
+    return this.highBits_ >>> 0;
+  }
+
+  /** @return {string} */
+  toSignedDecimalString() {
+    return joinSignedDecimalString(this);
+  }
+
+  /** @return {string} */
+  toUnsignedDecimalString() {
+    return joinUnsignedDecimalString(this);
+  }
+
+  /**
+   * Returns an unsigned hexadecimal string representation of the Int64.
+   * @return {string}
+   */
+  toHexString() {
+    let nibbles = new Array(16);
+    let lowBits = this.lowBits_;
+    let highBits = this.highBits_;
+    for (let highIndex = 7, lowIndex = 15; lowIndex > 7;
+         highIndex--, lowIndex--) {
+      nibbles[highIndex] = HEX_DIGITS[highBits & 0xF];
+      nibbles[lowIndex] = HEX_DIGITS[lowBits & 0xF];
+      highBits = highBits >>> 4;
+      lowBits = lowBits >>> 4;
+    }
+    // Always leave the least significant hex digit.
+    while (nibbles.length > 1 && nibbles[0] == '0') {
+      nibbles.shift();
+    }
+    return `0x${nibbles.join('')}`;
+  }
+
+  /**
+   * @param {*} other object to compare against.
+   * @return {boolean} Whether this Int64 equals the other.
+   */
+  equals(other) {
+    if (this === other) {
+      return true;
+    }
+    if (!(other instanceof Int64)) {
+      return false;
+    }
+    // Compare low parts first as there is higher chance they are different.
+    const otherInt64 = /** @type{!Int64} */ (other);
+    return (this.lowBits_ === otherInt64.lowBits_) &&
+        (this.highBits_ === otherInt64.highBits_);
+  }
+
+  /**
+   * Returns a number (int32) that is suitable for using in hashed structures.
+   * @return {number}
+   */
+  hashCode() {
+    return (31 * this.lowBits_ + 17 * this.highBits_) | 0;
+  }
+}
+
+/**
+ * Losslessly converts a 64-bit unsigned integer in 32:32 split representation
+ * into a decimal string.
+ * @param {!Int64} int64
+ * @return {string} The binary number represented as a string.
+ */
+const joinUnsignedDecimalString = (int64) => {
+  const lowBits = int64.getLowBitsUnsigned();
+  const highBits = int64.getHighBitsUnsigned();
+  // Skip the expensive conversion if the number is small enough to use the
+  // built-in conversions.
+  // Number.MAX_SAFE_INTEGER = 0x001FFFFF FFFFFFFF, thus any number with
+  // highBits <= 0x1FFFFF can be safely expressed with a double and retain
+  // integer precision.
+  // Proven by: Number.isSafeInteger(0x1FFFFF * 2**32 + 0xFFFFFFFF) == true.
+  if (highBits <= 0x1FFFFF) {
+    return String(TWO_PWR_32_DBL * highBits + lowBits);
+  }
+
+  // What this code is doing is essentially converting the input number from
+  // base-2 to base-1e7, which allows us to represent the 64-bit range with
+  // only 3 (very large) digits. Those digits are then trivial to convert to
+  // a base-10 string.
+
+  // The magic numbers used here are -
+  // 2^24 = 16777216 = (1,6777216) in base-1e7.
+  // 2^48 = 281474976710656 = (2,8147497,6710656) in base-1e7.
+
+  // Split 32:32 representation into 16:24:24 representation so our
+  // intermediate digits don't overflow.
+  const low = lowBits & LOW_24_BITS;
+  const mid = ((lowBits >>> 24) | (highBits << 8)) & LOW_24_BITS;
+  const high = (highBits >> 16) & LOW_16_BITS;
+
+  // Assemble our three base-1e7 digits, ignoring carries. The maximum
+  // value in a digit at this step is representable as a 48-bit integer, which
+  // can be stored in a 64-bit floating point number.
+  let digitA = low + (mid * 6777216) + (high * 6710656);
+  let digitB = mid + (high * 8147497);
+  let digitC = (high * 2);
+
+  // Apply carries from A to B and from B to C.
+  const base = 10000000;
+  if (digitA >= base) {
+    digitB += Math.floor(digitA / base);
+    digitA %= base;
+  }
+
+  if (digitB >= base) {
+    digitC += Math.floor(digitB / base);
+    digitB %= base;
+  }
+
+  // If digitC is 0, then we should have returned in the trivial code path
+  // at the top for non-safe integers. Given this, we can assume both digitB
+  // and digitA need leading zeros.
+  // TODO: Use our own checking system here.
+  assert(digitC);
+  return digitC + decimalFrom1e7WithLeadingZeros(digitB) +
+      decimalFrom1e7WithLeadingZeros(digitA);
+};
+
+/**
+ * @param {number} digit1e7 Number < 1e7
+ * @return {string} Decimal representation of digit1e7 with leading zeros.
+ */
+const decimalFrom1e7WithLeadingZeros = (digit1e7) => {
+  const partial = String(digit1e7);
+  return '0000000'.slice(partial.length) + partial;
+};
+
+/**
+ * Losslessly converts a 64-bit signed integer in 32:32 split representation
+ * into a decimal string.
+ * @param {!Int64} int64
+ * @return {string} The binary number represented as a string.
+ */
+const joinSignedDecimalString = (int64) => {
+  // If we're treating the input as a signed value and the high bit is set, do
+  // a manual two's complement conversion before the decimal conversion.
+  const negative = (int64.getHighBits() & 0x80000000);
+  if (negative) {
+    int64 = negate(int64.getLowBits(), int64.getHighBits());
+  }
+
+  const result = joinUnsignedDecimalString(int64);
+  return negative ? '-' + result : result;
+};
+
+/**
+ * @param {string} dec
+ * @return {!Int64}
+ */
+const decimalStringToInt64 = (dec) => {
+  // Check for minus sign.
+  const minus = dec[0] === '-';
+  if (minus) {
+    dec = dec.slice(1);
+  }
+
+  // Work 6 decimal digits at a time, acting like we're converting base 1e6
+  // digits to binary. This is safe to do with floating point math because
+  // Number.isSafeInteger(ALL_32_BITS * 1e6) == true.
+  const base = 1e6;
+  let lowBits = 0;
+  let highBits = 0;
+  function add1e6digit(begin, end = undefined) {
+    // Note: Number('') is 0.
+    const digit1e6 = Number(dec.slice(begin, end));
+    highBits *= base;
+    lowBits = lowBits * base + digit1e6;
+    // Carry bits from lowBits to
+    if (lowBits >= TWO_PWR_32_DBL) {
+      highBits = highBits + ((lowBits / TWO_PWR_32_DBL) | 0);
+      lowBits = lowBits % TWO_PWR_32_DBL;
+    }
+  }
+  add1e6digit(-24, -18);
+  add1e6digit(-18, -12);
+  add1e6digit(-12, -6);
+  add1e6digit(-6);
+
+  return (minus ? negate : Int64.fromBits)(lowBits, highBits);
+};
+
+/**
+ * @param {number} lowBits
+ * @param {number} highBits
+ * @return {!Int64} Two's compliment negation of input.
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Signed_32-bit_integers
+ */
+const negate = (lowBits, highBits) => {
+  highBits = ~highBits;
+  if (lowBits) {
+    lowBits = ~lowBits + 1;
+  } else {
+    // If lowBits is 0, then bitwise-not is 0xFFFFFFFF,
+    // adding 1 to that, results in 0x100000000, which leaves
+    // the low bits 0x0 and simply adds one to the high bits.
+    highBits += 1;
+  }
+  return Int64.fromBits(lowBits, highBits);
+};
+
+/** @const {!Int64} */
+const ZERO = new Int64(0, 0);
+
+/** @const @private {number} */
+const LOW_16_BITS = 0xFFFF;
+
+/** @const @private {number} */
+const LOW_24_BITS = 0xFFFFFF;
+
+/** @const @private {number} */
+const LOW_31_BITS = 0x7FFFFFFF;
+
+/** @const @private {number} */
+const ALL_32_BITS = 0xFFFFFFFF;
+
+/** @const {!Int64} */
+const MAX_VALUE = Int64.fromBits(ALL_32_BITS, LOW_31_BITS);
+
+/** @const {!Int64} */
+const MIN_VALUE = Int64.fromBits(0, 0x80000000);
+
+/** @const {number} */
+const TWO_PWR_32_DBL = 0x100000000;
+
+/** @const {string} */
+const HEX_DIGITS = '0123456789abcdef';
+
+exports = Int64;

+ 213 - 0
js/experimental/runtime/int64_test.js

@@ -0,0 +1,213 @@
+/**
+ * @fileoverview Tests for Int64.
+ */
+goog.module('protobuf.Int64Test');
+goog.setTestOnly();
+
+const Int64 = goog.require('protobuf.Int64');
+const Long = goog.require('goog.math.Long');
+
+describe('Int64', () => {
+  it('can be constructed from bits', () => {
+    const int64 = Int64.fromBits(0, 1);
+    expect(int64.getLowBits()).toEqual(0);
+    expect(int64.getHighBits()).toEqual(1);
+  });
+
+  it('zero is defined', () => {
+    const int64 = Int64.getZero();
+    expect(int64.getLowBits()).toEqual(0);
+    expect(int64.getHighBits()).toEqual(0);
+  });
+
+  it('max value is defined', () => {
+    const int64 = Int64.getMaxValue();
+    expect(int64).toEqual(Int64.fromBits(0xFFFFFFFF, 0x7FFFFFFF));
+    expect(int64.asLong()).toEqual(Long.getMaxValue());
+  });
+
+  it('min value is defined', () => {
+    const int64 = Int64.getMinValue();
+    expect(int64).toEqual(Int64.fromBits(0, 0x80000000));
+    expect(int64.asLong()).toEqual(Long.getMinValue());
+  });
+
+  it('Can be converted to long', () => {
+    const int64 = Int64.fromInt(1);
+    expect(int64.asLong()).toEqual(Long.fromInt(1));
+  });
+
+  it('Negative value can be converted to long', () => {
+    const int64 = Int64.fromInt(-1);
+    expect(int64.getLowBits()).toEqual(0xFFFFFFFF | 0);
+    expect(int64.getHighBits()).toEqual(0xFFFFFFFF | 0);
+    expect(int64.asLong()).toEqual(Long.fromInt(-1));
+  });
+
+  it('Can be converted to number', () => {
+    const int64 = Int64.fromInt(1);
+    expect(int64.asNumber()).toEqual(1);
+  });
+
+  it('Can convert negative value to number', () => {
+    const int64 = Int64.fromInt(-1);
+    expect(int64.asNumber()).toEqual(-1);
+  });
+
+  it('MAX_SAFE_INTEGER can be used.', () => {
+    const int64 = Int64.fromNumber(Number.MAX_SAFE_INTEGER);
+    expect(int64.getLowBitsUnsigned()).toEqual(0xFFFFFFFF);
+    expect(int64.getHighBits()).toEqual(0x1FFFFF);
+    expect(int64.asNumber()).toEqual(Number.MAX_SAFE_INTEGER);
+  });
+
+  it('MIN_SAFE_INTEGER can be used.', () => {
+    const int64 = Int64.fromNumber(Number.MIN_SAFE_INTEGER);
+    expect(int64.asNumber()).toEqual(Number.MIN_SAFE_INTEGER);
+  });
+
+  it('constructs fromInt', () => {
+    const int64 = Int64.fromInt(1);
+    expect(int64.getLowBits()).toEqual(1);
+    expect(int64.getHighBits()).toEqual(0);
+  });
+
+  it('constructs fromLong', () => {
+    const int64 = Int64.fromLong(Long.fromInt(1));
+    expect(int64.getLowBits()).toEqual(1);
+    expect(int64.getHighBits()).toEqual(0);
+  });
+
+  // TODO: Use our own checking system here.
+  if (goog.DEBUG) {
+    it('asNumber throws for MAX_SAFE_INTEGER + 1', () => {
+      expect(() => Int64.fromNumber(Number.MAX_SAFE_INTEGER + 1).asNumber())
+          .toThrow();
+    });
+
+    it('fromInt(MAX_SAFE_INTEGER) throws', () => {
+      expect(() => Int64.fromInt(Number.MAX_SAFE_INTEGER)).toThrow();
+    });
+
+    it('fromInt(1.5) throws', () => {
+      expect(() => Int64.fromInt(1.5)).toThrow();
+    });
+  }
+
+  const decimalHexPairs = {
+    '0x0000000000000000': {signed: '0'},
+    '0x0000000000000001': {signed: '1'},
+    '0x00000000ffffffff': {signed: '4294967295'},
+    '0x0000000100000000': {signed: '4294967296'},
+    '0xffffffffffffffff': {signed: '-1', unsigned: '18446744073709551615'},
+    '0x8000000000000000':
+        {signed: '-9223372036854775808', unsigned: '9223372036854775808'},
+    '0x8000000080000000':
+        {signed: '-9223372034707292160', unsigned: '9223372039002259456'},
+    '0x01b69b4bacd05f15': {signed: '123456789123456789'},
+    '0xfe4964b4532fa0eb':
+        {signed: '-123456789123456789', unsigned: '18323287284586094827'},
+    '0xa5a5a5a5a5a5a5a5':
+        {signed: '-6510615555426900571', unsigned: '11936128518282651045'},
+    '0x5a5a5a5a5a5a5a5a': {signed: '6510615555426900570'},
+    '0xffffffff00000000':
+        {signed: '-4294967296', unsigned: '18446744069414584320'},
+  };
+
+  it('serializes to signed decimal strings', () => {
+    for (const [hex, decimals] of Object.entries(decimalHexPairs)) {
+      const int64 = hexToInt64(hex);
+      expect(int64.toSignedDecimalString()).toEqual(decimals.signed);
+    }
+  });
+
+  it('serializes to unsigned decimal strings', () => {
+    for (const [hex, decimals] of Object.entries(decimalHexPairs)) {
+      const int64 = hexToInt64(hex);
+      expect(int64.toUnsignedDecimalString())
+          .toEqual(decimals.unsigned || decimals.signed);
+    }
+  });
+
+  it('serializes to unsigned hex strings', () => {
+    for (const [hex, decimals] of Object.entries(decimalHexPairs)) {
+      const int64 = hexToInt64(hex);
+      let shortHex = hex.replace(/0x0*/, '0x');
+      if (shortHex == '0x') {
+        shortHex = '0x0';
+      }
+      expect(int64.toHexString()).toEqual(shortHex);
+    }
+  });
+
+  it('parses decimal strings', () => {
+    for (const [hex, decimals] of Object.entries(decimalHexPairs)) {
+      const signed = Int64.fromDecimalString(decimals.signed);
+      expect(int64ToHex(signed)).toEqual(hex);
+      if (decimals.unsigned) {
+        const unsigned = Int64.fromDecimalString(decimals.unsigned);
+        expect(int64ToHex(unsigned)).toEqual(hex);
+      }
+    }
+  });
+
+  it('parses hex strings', () => {
+    for (const [hex, decimals] of Object.entries(decimalHexPairs)) {
+      expect(int64ToHex(Int64.fromHexString(hex))).toEqual(hex);
+    }
+    expect(int64ToHex(Int64.fromHexString('-0x1')))
+        .toEqual('0xffffffffffffffff');
+  });
+
+  // TODO: Use our own checking system here.
+  if (goog.DEBUG) {
+    it('throws when parsing empty string', () => {
+      expect(() => Int64.fromDecimalString('')).toThrow();
+    });
+
+    it('throws when parsing float string', () => {
+      expect(() => Int64.fromDecimalString('1.5')).toThrow();
+    });
+
+    it('throws when parsing non-numeric string', () => {
+      expect(() => Int64.fromDecimalString('0xa')).toThrow();
+    });
+  }
+
+  it('checks if equal', () => {
+    const low = Int64.fromInt(1);
+    const high = Int64.getMaxValue();
+    expect(low.equals(Int64.fromInt(1))).toEqual(true);
+    expect(low.equals(high)).toEqual(false);
+    expect(high.equals(Int64.getMaxValue())).toEqual(true);
+  });
+
+  it('returns unique hashcode', () => {
+    expect(Int64.fromInt(1).hashCode()).toEqual(Int64.fromInt(1).hashCode());
+    expect(Int64.fromInt(1).hashCode())
+        .not.toEqual(Int64.fromInt(2).hashCode());
+  });
+});
+
+/**
+ * @param {string} hexString
+ * @return {!Int64}
+ */
+function hexToInt64(hexString) {
+  const high = hexString.slice(2, 10);
+  const low = hexString.slice(10);
+  return Int64.fromBits(parseInt(low, 16), parseInt(high, 16));
+}
+
+/**
+ * @param {!Int64} int64
+ * @return {string}
+ */
+function int64ToHex(int64) {
+  const ZEROS_32_BIT = '00000000';
+  const highPartialHex = int64.getHighBitsUnsigned().toString(16);
+  const lowPartialHex = int64.getLowBitsUnsigned().toString(16);
+  const highHex = ZEROS_32_BIT.slice(highPartialHex.length) + highPartialHex;
+  const lowHex = ZEROS_32_BIT.slice(lowPartialHex.length) + lowPartialHex;
+  return `0x${highHex}${lowHex}`;
+}

+ 708 - 0
js/experimental/runtime/internal/checks.js

@@ -0,0 +1,708 @@
+/**
+ * @fileoverview Proto internal runtime checks.
+ *
+ * Checks are grouped into different severity, see:
+ * http://g3doc/javascript/protobuf/README.md#configurable-check-support-in-protocol-buffers
+ *
+ * Checks are also grouped into different sections:
+ *   - CHECK_BOUNDS:
+ *       Checks that ensure that indexed access is within bounds
+ *       (e.g. an array being accessed past its size).
+ *   - CHECK_STATE
+ *       Checks related to the state of an object
+ *       (e.g. a parser hitting an invalid case).
+ *   - CHECK_TYPE:
+ *       Checks that relate to type errors (e.g. code receives a number instead
+ *       of a string).
+ */
+goog.module('protobuf.internal.checks');
+
+const ByteString = goog.require('protobuf.ByteString');
+const Int64 = goog.require('protobuf.Int64');
+const WireType = goog.require('protobuf.binary.WireType');
+
+//
+// See
+// http://g3doc/javascript/protobuf/README.md#configurable-check-support-in-protocol-buffers
+//
+/** @define{string} */
+const CHECK_LEVEL_DEFINE = goog.define('protobuf.defines.CHECK_LEVEL', '');
+
+/** @define{boolean} */
+const POLYFILL_TEXT_ENCODING =
+    goog.define('protobuf.defines.POLYFILL_TEXT_ENCODING', true);
+
+/**
+ * @const {number}
+ */
+const MAX_FIELD_NUMBER = Math.pow(2, 29) - 1;
+
+/**
+ * The largest finite float32 value.
+ * @const {number}
+ */
+const FLOAT32_MAX = 3.4028234663852886e+38;
+
+/** @enum {number} */
+const CheckLevel = {
+  DEBUG: 0,
+  CRITICAL: 1,
+  OFF: 2
+};
+
+
+/** @return {!CheckLevel} */
+function calculateCheckLevel() {
+  const definedLevel = CHECK_LEVEL_DEFINE.toUpperCase();
+  if (definedLevel === '') {
+    // user did not set a value, value now just depends on goog.DEBUG
+    return goog.DEBUG ? CheckLevel.DEBUG : CheckLevel.CRITICAL;
+  }
+
+  if (definedLevel === 'CRITICAL') {
+    return CheckLevel.CRITICAL;
+  }
+
+  if (definedLevel === 'OFF') {
+    return CheckLevel.OFF;
+  }
+
+  if (definedLevel === 'DEBUG') {
+    return CheckLevel.DEBUG;
+  }
+
+  throw new Error(`Unknown value for CHECK_LEVEL: ${CHECK_LEVEL_DEFINE}`);
+}
+
+const /** !CheckLevel */ CHECK_LEVEL = calculateCheckLevel();
+
+const /** boolean */ CHECK_STATE = CHECK_LEVEL === CheckLevel.DEBUG;
+
+const /** boolean */ CHECK_CRITICAL_STATE =
+    CHECK_LEVEL === CheckLevel.CRITICAL || CHECK_LEVEL === CheckLevel.DEBUG;
+
+const /** boolean */ CHECK_BOUNDS = CHECK_LEVEL === CheckLevel.DEBUG;
+
+const /** boolean */ CHECK_CRITICAL_BOUNDS =
+    CHECK_LEVEL === CheckLevel.CRITICAL || CHECK_LEVEL === CheckLevel.DEBUG;
+
+const /** boolean */ CHECK_TYPE = CHECK_LEVEL === CheckLevel.DEBUG;
+
+const /** boolean */ CHECK_CRITICAL_TYPE =
+    CHECK_LEVEL === CheckLevel.CRITICAL || CHECK_LEVEL === CheckLevel.DEBUG;
+
+/**
+ * Ensures the truth of an expression involving the state of the calling
+ * instance, but not involving any parameters to the calling method.
+ *
+ * For cases where failing fast is pretty important and not failing early could
+ * cause bugs that are much harder to debug.
+ * @param {boolean} state
+ * @param {string=} message
+ * @throws {!Error} If the state is false and the check state is critical.
+ */
+function checkCriticalState(state, message = '') {
+  if (!CHECK_CRITICAL_STATE) {
+    return;
+  }
+  if (!state) {
+    throw new Error(message);
+  }
+}
+
+/**
+ * Ensures the truth of an expression involving the state of the calling
+ * instance, but not involving any parameters to the calling method.
+ *
+ * @param {boolean} state
+ * @param {string=} message
+ * @throws {!Error} If the state is false and the check state is debug.
+ */
+function checkState(state, message = '') {
+  if (!CHECK_STATE) {
+    return;
+  }
+  checkCriticalState(state, message);
+}
+
+/**
+ * Ensures that `index` specifies a valid position in an indexable object of
+ * size `size`. A position index may range from zero to size, inclusive.
+ * @param {number} index
+ * @param {number} size
+ * @throws {!Error} If the index is out of range and the check state is debug.
+ */
+function checkPositionIndex(index, size) {
+  if (!CHECK_BOUNDS) {
+    return;
+  }
+  checkCriticalPositionIndex(index, size);
+}
+
+/**
+ * Ensures that `index` specifies a valid position in an indexable object of
+ * size `size`. A position index may range from zero to size, inclusive.
+ * @param {number} index
+ * @param {number} size
+ * @throws {!Error} If the index is out of range and the check state is
+ * critical.
+ */
+function checkCriticalPositionIndex(index, size) {
+  if (!CHECK_CRITICAL_BOUNDS) {
+    return;
+  }
+  if (index < 0 || index > size) {
+    throw new Error(`Index out of bounds: index: ${index} size: ${size}`);
+  }
+}
+
+/**
+ * Ensures that `index` specifies a valid element in an indexable object of
+ * size `size`. A element index may range from zero to size, exclusive.
+ * @param {number} index
+ * @param {number} size
+ * @throws {!Error} If the index is out of range and the check state is
+ * debug.
+ */
+function checkElementIndex(index, size) {
+  if (!CHECK_BOUNDS) {
+    return;
+  }
+  checkCriticalElementIndex(index, size);
+}
+
+/**
+ * Ensures that `index` specifies a valid element in an indexable object of
+ * size `size`. A element index may range from zero to size, exclusive.
+ * @param {number} index
+ * @param {number} size
+ * @throws {!Error} If the index is out of range and the check state is
+ * critical.
+ */
+function checkCriticalElementIndex(index, size) {
+  if (!CHECK_CRITICAL_BOUNDS) {
+    return;
+  }
+  if (index < 0 || index >= size) {
+    throw new Error(`Index out of bounds: index: ${index} size: ${size}`);
+  }
+}
+
+/**
+ * Ensures the range of [start, end) is with the range of [0, size).
+ * @param {number} start
+ * @param {number} end
+ * @param {number} size
+ * @throws {!Error} If start and end are out of range and the check state is
+ * debug.
+ */
+function checkRange(start, end, size) {
+  if (!CHECK_BOUNDS) {
+    return;
+  }
+  checkCriticalRange(start, end, size);
+}
+
+/**
+ * Ensures the range of [start, end) is with the range of [0, size).
+ * @param {number} start
+ * @param {number} end
+ * @param {number} size
+ * @throws {!Error} If start and end are out of range and the check state is
+ * critical.
+ */
+function checkCriticalRange(start, end, size) {
+  if (!CHECK_CRITICAL_BOUNDS) {
+    return;
+  }
+  if (start < 0 || end < 0 || start > size || end > size) {
+    throw new Error(`Range error: start: ${start} end: ${end} size: ${size}`);
+  }
+  if (start > end) {
+    throw new Error(`Start > end: ${start} > ${end}`);
+  }
+}
+
+/**
+ * Ensures that field number is an integer and within the range of
+ * [1, MAX_FIELD_NUMBER].
+ * @param {number} fieldNumber
+ * @throws {!Error} If the field number is out of range and the check state is
+ * debug.
+ */
+function checkFieldNumber(fieldNumber) {
+  if (!CHECK_TYPE) {
+    return;
+  }
+  checkCriticalFieldNumber(fieldNumber);
+}
+
+/**
+ * Ensures that the value is neither null nor undefined.
+ *
+ * @param {T} value
+ * @return {R}
+ *
+ * @template T
+ * @template R :=
+ *     mapunion(T, (V) =>
+ *         cond(eq(V, 'null'),
+ *             none(),
+ *             cond(eq(V, 'undefined'),
+ *                 none(),
+ *                 V)))
+ *  =:
+ */
+function checkDefAndNotNull(value) {
+  if (CHECK_TYPE) {
+    // Note that undefined == null.
+    if (value == null) {
+      throw new Error(`Value can't be null`);
+    }
+  }
+  return value;
+}
+
+/**
+ * Ensures that the value exists and is a function.
+ *
+ * @param {function(?): ?} func
+ */
+function checkFunctionExists(func) {
+  if (CHECK_TYPE) {
+    if (typeof func !== 'function') {
+      throw new Error(`${func} is not a function`);
+    }
+  }
+}
+
+/**
+ * Ensures that field number is an integer and within the range of
+ * [1, MAX_FIELD_NUMBER].
+ * @param {number} fieldNumber
+ * @throws {!Error} If the field number is out of range and the check state is
+ * critical.
+ */
+function checkCriticalFieldNumber(fieldNumber) {
+  if (!CHECK_CRITICAL_TYPE) {
+    return;
+  }
+  if (fieldNumber <= 0 || fieldNumber > MAX_FIELD_NUMBER) {
+    throw new Error(`Field number is out of range: ${fieldNumber}`);
+  }
+}
+
+/**
+ * Ensures that wire type is valid.
+ * @param {!WireType} wireType
+ * @throws {!Error} If the wire type is invalid and the check state is debug.
+ */
+function checkWireType(wireType) {
+  if (!CHECK_TYPE) {
+    return;
+  }
+  checkCriticalWireType(wireType);
+}
+
+/**
+ * Ensures that wire type is valid.
+ * @param {!WireType} wireType
+ * @throws {!Error} If the wire type is invalid and the check state is critical.
+ */
+function checkCriticalWireType(wireType) {
+  if (!CHECK_CRITICAL_TYPE) {
+    return;
+  }
+  if (wireType < WireType.VARINT || wireType > WireType.FIXED32) {
+    throw new Error(`Invalid wire type: ${wireType}`);
+  }
+}
+
+/**
+ * Ensures the given value has the correct type.
+ * @param {boolean} expression
+ * @param {string} errorMsg
+ * @throws {!Error} If the value has the wrong type and the check state is
+ * critical.
+ */
+function checkCriticalType(expression, errorMsg) {
+  if (!CHECK_CRITICAL_TYPE) {
+    return;
+  }
+  if (!expression) {
+    throw new Error(errorMsg);
+  }
+}
+
+/**
+ * Checks whether a given object is an array.
+ * @param {*} value
+ * @return {!Array<*>}
+ */
+function checkCriticalTypeArray(value) {
+  checkCriticalType(
+      Array.isArray(value), `Must be an array, but got: ${value}`);
+  return /** @type {!Array<*>} */ (value);
+}
+
+/**
+ * Checks whether a given object is an iterable.
+ * @param {*} value
+ * @return {!Iterable<*>}
+ */
+function checkCriticalTypeIterable(value) {
+  checkCriticalType(
+      !!value[Symbol.iterator], `Must be an iterable, but got: ${value}`);
+  return /** @type {!Iterable<*>} */ (value);
+}
+
+/**
+ * Checks whether a given object is a boolean.
+ * @param {*} value
+ */
+function checkCriticalTypeBool(value) {
+  checkCriticalType(
+      typeof value === 'boolean', `Must be a boolean, but got: ${value}`);
+}
+
+/**
+ * Checks whether a given object is an array of boolean.
+ * @param {*} values
+ */
+function checkCriticalTypeBoolArray(values) {
+  // TODO(b/134765672)
+  if (!CHECK_CRITICAL_TYPE) {
+    return;
+  }
+  const array = checkCriticalTypeArray(values);
+  for (const value of array) {
+    checkCriticalTypeBool(value);
+  }
+}
+
+/**
+ * Checks whether a given object is a ByteString.
+ * @param {*} value
+ */
+function checkCriticalTypeByteString(value) {
+  checkCriticalType(
+      value instanceof ByteString, `Must be a ByteString, but got: ${value}`);
+}
+
+/**
+ * Checks whether a given object is an array of ByteString.
+ * @param {*} values
+ */
+function checkCriticalTypeByteStringArray(values) {
+  // TODO(b/134765672)
+  if (!CHECK_CRITICAL_TYPE) {
+    return;
+  }
+  const array = checkCriticalTypeArray(values);
+  for (const value of array) {
+    checkCriticalTypeByteString(value);
+  }
+}
+
+/**
+ * Checks whether a given object is a number.
+ * @param {*} value
+ * @throws {!Error} If the value is not float and the check state is debug.
+ */
+function checkTypeDouble(value) {
+  if (!CHECK_TYPE) {
+    return;
+  }
+  checkCriticalTypeDouble(value);
+}
+
+/**
+ * Checks whether a given object is a number.
+ * @param {*} value
+ * @throws {!Error} If the value is not float and the check state is critical.
+ */
+function checkCriticalTypeDouble(value) {
+  checkCriticalType(
+      typeof value === 'number', `Must be a number, but got: ${value}`);
+}
+
+/**
+ * Checks whether a given object is an array of double.
+ * @param {*} values
+ */
+function checkCriticalTypeDoubleArray(values) {
+  // TODO(b/134765672)
+  if (!CHECK_CRITICAL_TYPE) {
+    return;
+  }
+  const array = checkCriticalTypeArray(values);
+  for (const value of array) {
+    checkCriticalTypeDouble(value);
+  }
+}
+
+/**
+ * Checks whether a given object is a number.
+ * @param {*} value
+ * @throws {!Error} If the value is not signed int32 and the check state is
+ *     debug.
+ */
+function checkTypeSignedInt32(value) {
+  if (!CHECK_TYPE) {
+    return;
+  }
+  checkCriticalTypeSignedInt32(value);
+}
+
+/**
+ * Checks whether a given object is a number.
+ * @param {*} value
+ * @throws {!Error} If the value is not signed int32 and the check state is
+ *     critical.
+ */
+function checkCriticalTypeSignedInt32(value) {
+  checkCriticalTypeDouble(value);
+  const valueAsNumber = /** @type {number} */ (value);
+  if (CHECK_CRITICAL_TYPE) {
+    if (valueAsNumber < -Math.pow(2, 31) || valueAsNumber > Math.pow(2, 31) ||
+        !Number.isInteger(valueAsNumber)) {
+      throw new Error(`Must be int32, but got: ${valueAsNumber}`);
+    }
+  }
+}
+
+/**
+ * Checks whether a given object is an array of numbers.
+ * @param {*} values
+ */
+function checkCriticalTypeSignedInt32Array(values) {
+  // TODO(b/134765672)
+  if (!CHECK_CRITICAL_TYPE) {
+    return;
+  }
+  const array = checkCriticalTypeArray(values);
+  for (const value of array) {
+    checkCriticalTypeSignedInt32(value);
+  }
+}
+
+/**
+ * Ensures that value is a long instance.
+ * @param {*} value
+ * @throws {!Error} If the value is not a long instance and check state is
+ *     debug.
+ */
+function checkTypeSignedInt64(value) {
+  if (!CHECK_TYPE) {
+    return;
+  }
+  checkCriticalTypeSignedInt64(value);
+}
+
+/**
+ * Ensures that value is a long instance.
+ * @param {*} value
+ * @throws {!Error} If the value is not a long instance and check state is
+ *     critical.
+ */
+function checkCriticalTypeSignedInt64(value) {
+  if (!CHECK_CRITICAL_TYPE) {
+    return;
+  }
+  if (!(value instanceof Int64)) {
+    throw new Error(`Must be Int64 instance, but got: ${value}`);
+  }
+}
+
+/**
+ * Checks whether a given object is an array of long instances.
+ * @param {*} values
+ * @throws {!Error} If values is not an array of long instances.
+ */
+function checkCriticalTypeSignedInt64Array(values) {
+  // TODO(b/134765672)
+  if (!CHECK_CRITICAL_TYPE) {
+    return;
+  }
+  const array = checkCriticalTypeArray(values);
+  for (const value of array) {
+    checkCriticalTypeSignedInt64(value);
+  }
+}
+
+/**
+ * Checks whether a given object is a number and within float32 precision.
+ * @param {*} value
+ * @throws {!Error} If the value is not float and the check state is debug.
+ */
+function checkTypeFloat(value) {
+  if (!CHECK_TYPE) {
+    return;
+  }
+  checkCriticalTypeFloat(value);
+}
+
+/**
+ * Checks whether a given object is a number and within float32 precision.
+ * @param {*} value
+ * @throws {!Error} If the value is not float and the check state is critical.
+ */
+function checkCriticalTypeFloat(value) {
+  checkCriticalTypeDouble(value);
+  if (CHECK_CRITICAL_TYPE) {
+    const valueAsNumber = /** @type {number} */ (value);
+    if (Number.isFinite(valueAsNumber) &&
+        (valueAsNumber > FLOAT32_MAX || valueAsNumber < -FLOAT32_MAX)) {
+      throw new Error(
+          `Given number does not fit into float precision: ${value}`);
+    }
+  }
+}
+
+/**
+ * Checks whether a given object is an iterable of floats.
+ * @param {*} values
+ */
+function checkCriticalTypeFloatIterable(values) {
+  // TODO(b/134765672)
+  if (!CHECK_CRITICAL_TYPE) {
+    return;
+  }
+  const iterable = checkCriticalTypeIterable(values);
+  for (const value of iterable) {
+    checkCriticalTypeFloat(value);
+  }
+}
+
+/**
+ * Checks whether a given object is a string.
+ * @param {*} value
+ */
+function checkCriticalTypeString(value) {
+  checkCriticalType(
+      typeof value === 'string', `Must be string, but got: ${value}`);
+}
+
+/**
+ * Checks whether a given object is an array of string.
+ * @param {*} values
+ */
+function checkCriticalTypeStringArray(values) {
+  // TODO(b/134765672)
+  if (!CHECK_CRITICAL_TYPE) {
+    return;
+  }
+  const array = checkCriticalTypeArray(values);
+  for (const value of array) {
+    checkCriticalTypeString(value);
+  }
+}
+
+/**
+ * Ensures that value is a valid unsigned int32.
+ * @param {*} value
+ * @throws {!Error} If the value is out of range and the check state is debug.
+ */
+function checkTypeUnsignedInt32(value) {
+  if (!CHECK_TYPE) {
+    return;
+  }
+  checkCriticalTypeUnsignedInt32(value);
+}
+
+/**
+ * Ensures that value is a valid unsigned int32.
+ * @param {*} value
+ * @throws {!Error} If the value is out of range and the check state
+ * is critical.
+ */
+function checkCriticalTypeUnsignedInt32(value) {
+  if (!CHECK_CRITICAL_TYPE) {
+    return;
+  }
+  checkCriticalTypeDouble(value);
+  const valueAsNumber = /** @type {number} */ (value);
+  if (valueAsNumber < 0 || valueAsNumber > Math.pow(2, 32) - 1 ||
+      !Number.isInteger(valueAsNumber)) {
+    throw new Error(`Must be uint32, but got: ${value}`);
+  }
+}
+
+/**
+ * Checks whether a given object is an array of unsigned int32.
+ * @param {*} values
+ */
+function checkCriticalTypeUnsignedInt32Array(values) {
+  // TODO(b/134765672)
+  if (!CHECK_CRITICAL_TYPE) {
+    return;
+  }
+  const array = checkCriticalTypeArray(values);
+  for (const value of array) {
+    checkCriticalTypeUnsignedInt32(value);
+  }
+}
+
+/**
+ * Checks whether a given object is an array of message.
+ * @param {*} values
+ */
+function checkCriticalTypeMessageArray(values) {
+  // TODO(b/134765672)
+  if (!CHECK_CRITICAL_TYPE) {
+    return;
+  }
+  const array = checkCriticalTypeArray(values);
+  for (const value of array) {
+    checkCriticalType(
+        value !== null, 'Given value is not a message instance: null');
+  }
+}
+
+exports = {
+  checkDefAndNotNull,
+  checkCriticalElementIndex,
+  checkCriticalFieldNumber,
+  checkCriticalPositionIndex,
+  checkCriticalRange,
+  checkCriticalState,
+  checkCriticalTypeBool,
+  checkCriticalTypeBoolArray,
+  checkCriticalTypeByteString,
+  checkCriticalTypeByteStringArray,
+  checkCriticalTypeDouble,
+  checkTypeDouble,
+  checkCriticalTypeDoubleArray,
+  checkTypeFloat,
+  checkCriticalTypeFloat,
+  checkCriticalTypeFloatIterable,
+  checkCriticalTypeMessageArray,
+  checkCriticalTypeSignedInt32,
+  checkCriticalTypeSignedInt32Array,
+  checkCriticalTypeSignedInt64,
+  checkTypeSignedInt64,
+  checkCriticalTypeSignedInt64Array,
+  checkCriticalTypeString,
+  checkCriticalTypeStringArray,
+  checkCriticalTypeUnsignedInt32,
+  checkCriticalTypeUnsignedInt32Array,
+  checkCriticalType,
+  checkCriticalWireType,
+  checkElementIndex,
+  checkFieldNumber,
+  checkFunctionExists,
+  checkPositionIndex,
+  checkRange,
+  checkState,
+  checkTypeUnsignedInt32,
+  checkTypeSignedInt32,
+  checkWireType,
+  CHECK_BOUNDS,
+  CHECK_CRITICAL_BOUNDS,
+  CHECK_STATE,
+  CHECK_CRITICAL_STATE,
+  CHECK_TYPE,
+  CHECK_CRITICAL_TYPE,
+  MAX_FIELD_NUMBER,
+  POLYFILL_TEXT_ENCODING,
+};

+ 58 - 0
js/experimental/runtime/internal/checks_test.js

@@ -0,0 +1,58 @@
+/**
+ * @fileoverview Tests for checks.js.
+ */
+goog.module('protobuf.internal.checksTest');
+
+const {CHECK_TYPE, checkDefAndNotNull, checkFunctionExists} = goog.require('protobuf.internal.checks');
+
+describe('checkDefAndNotNull', () => {
+  it('throws if undefined', () => {
+    let value;
+    if (CHECK_TYPE) {
+      expect(() => checkDefAndNotNull(value)).toThrow();
+    } else {
+      expect(checkDefAndNotNull(value)).toBeUndefined();
+    }
+  });
+
+  it('throws if null', () => {
+    const value = null;
+    if (CHECK_TYPE) {
+      expect(() => checkDefAndNotNull(value)).toThrow();
+    } else {
+      expect(checkDefAndNotNull(value)).toBeNull();
+    }
+  });
+
+  it('does not throw if empty string', () => {
+    const value = '';
+    expect(checkDefAndNotNull(value)).toEqual('');
+  });
+});
+
+describe('checkFunctionExists', () => {
+  it('throws if the function is undefined', () => {
+    let foo = /** @type {function()} */ (/** @type {*} */ (undefined));
+    if (CHECK_TYPE) {
+      expect(() => checkFunctionExists(foo)).toThrow();
+    } else {
+      checkFunctionExists(foo);
+    }
+  });
+
+  it('throws if the property is defined but not a function', () => {
+    let foo = /** @type {function()} */ (/** @type {*} */ (1));
+    if (CHECK_TYPE) {
+      expect(() => checkFunctionExists(foo)).toThrow();
+    } else {
+      checkFunctionExists(foo);
+    }
+  });
+
+  it('does not throw if the function is defined', () => {
+    function foo(x) {
+      return x;
+    }
+    checkFunctionExists(foo);
+  });
+});

+ 79 - 0
js/experimental/runtime/kernel/bool_test_pairs.js

@@ -0,0 +1,79 @@
+/**
+ * @fileoverview Test data for bool encoding and decoding.
+ */
+goog.module('protobuf.binary.boolTestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of boolean values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, boolValue: boolean, bufferDecoder:
+ *     !BufferDecoder, error: ?boolean, skip_writer: ?boolean}>}
+ */
+function getBoolPairs() {
+  const boolPairs = [
+    {
+      name: 'true',
+      boolValue: true,
+      bufferDecoder: createBufferDecoder(0x01),
+    },
+    {
+      name: 'false',
+      boolValue: false,
+      bufferDecoder: createBufferDecoder(0x00),
+    },
+    {
+      name: 'two-byte true',
+      boolValue: true,
+      bufferDecoder: createBufferDecoder(0x80, 0x01),
+      skip_writer: true,
+    },
+    {
+      name: 'two-byte false',
+      boolValue: false,
+      bufferDecoder: createBufferDecoder(0x80, 0x00),
+      skip_writer: true,
+    },
+    {
+      name: 'minus one',
+      boolValue: true,
+      bufferDecoder: createBufferDecoder(
+          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01),
+      skip_writer: true,
+    },
+    {
+      name: 'max signed int 2^63 - 1',
+      boolValue: true,
+      bufferDecoder: createBufferDecoder(
+          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F),
+      skip_writer: true,
+    },
+    {
+      name: 'min signed int -2^63',
+      boolValue: true,
+      bufferDecoder: createBufferDecoder(
+          0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01),
+      skip_writer: true,
+    },
+    {
+      name: 'overflowed but valid varint',
+      boolValue: false,
+      bufferDecoder: createBufferDecoder(
+          0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02),
+      skip_writer: true,
+    },
+    {
+      name: 'errors out for 11 bytes',
+      boolValue: true,
+      bufferDecoder: createBufferDecoder(
+          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF),
+      error: true,
+      skip_writer: true,
+    },
+  ];
+  return [...boolPairs];
+}
+
+exports = {getBoolPairs};

+ 256 - 0
js/experimental/runtime/kernel/buffer_decoder.js

@@ -0,0 +1,256 @@
+/**
+ * @fileoverview A buffer implementation that can decode data for protobufs.
+ */
+
+goog.module('protobuf.binary.BufferDecoder');
+
+const ByteString = goog.require('protobuf.ByteString');
+const functions = goog.require('goog.functions');
+const {POLYFILL_TEXT_ENCODING, checkCriticalElementIndex, checkCriticalPositionIndex, checkState} = goog.require('protobuf.internal.checks');
+const {byteStringFromUint8ArrayUnsafe} = goog.require('protobuf.byteStringInternal');
+const {concatenateByteArrays} = goog.require('protobuf.binary.uint8arrays');
+const {decode} = goog.require('protobuf.binary.textencoding');
+
+/**
+ * Returns a valid utf-8 decoder function based on TextDecoder if available or
+ * a polyfill.
+ * Some of the environments we run in do not have TextDecoder defined.
+ * TextDecoder is faster than our polyfill so we prefer it over the polyfill.
+ * @return {function(!DataView): string}
+ */
+function getStringDecoderFunction() {
+  if (goog.global['TextDecoder']) {
+    const textDecoder = new goog.global['TextDecoder']('utf-8', {fatal: true});
+    return bytes => textDecoder.decode(bytes);
+  }
+  if (POLYFILL_TEXT_ENCODING) {
+    return decode;
+  } else {
+    throw new Error(
+        'TextDecoder is missing. ' +
+        'Enable protobuf.defines.POLYFILL_TEXT_ENCODING.');
+  }
+}
+
+/** @type {function(): function(!DataView): string} */
+const stringDecoderFunction =
+    functions.cacheReturnValue(() => getStringDecoderFunction());
+
+/** @type {function(): !DataView} */
+const emptyDataView =
+    functions.cacheReturnValue(() => new DataView(new ArrayBuffer(0)));
+
+class BufferDecoder {
+  /**
+   * @param {!Array<!BufferDecoder>} bufferDecoders
+   * @return {!BufferDecoder}
+   */
+  static merge(bufferDecoders) {
+    const uint8Arrays = bufferDecoders.map(b => b.asUint8Array());
+    const bytesArray = concatenateByteArrays(uint8Arrays);
+    return BufferDecoder.fromArrayBuffer(bytesArray.buffer);
+  }
+
+  /**
+   * @param {!ArrayBuffer} arrayBuffer
+   * @return {!BufferDecoder}
+   */
+  static fromArrayBuffer(arrayBuffer) {
+    return new BufferDecoder(
+        new DataView(arrayBuffer), 0, arrayBuffer.byteLength);
+  }
+
+  /**
+   * @param {!DataView} dataView
+   * @param {number} startIndex
+   * @param {number} length
+   * @private
+   */
+  constructor(dataView, startIndex, length) {
+    /** @private @const {!DataView} */
+    this.dataView_ = dataView;
+    /** @private @const {number} */
+    this.startIndex_ = startIndex;
+    /** @private @const {number} */
+    this.endIndex_ = this.startIndex_ + length;
+  }
+
+  /**
+   * Returns the start index of the underlying buffer.
+   * @return {number}
+   */
+  startIndex() {
+    return this.startIndex_;
+  }
+
+  /**
+   * Returns the end index of the underlying buffer.
+   * @return {number}
+   */
+  endIndex() {
+    return this.endIndex_;
+  }
+
+  /**
+   * Returns the length of the underlying buffer.
+   * @return {number}
+   */
+  length() {
+    return this.endIndex_ - this.startIndex_;
+  }
+
+  /**
+   * Returns a float32 from a given index
+   * @param {number} index
+   * @return {number}
+   */
+  getFloat32(index) {
+    return this.dataView_.getFloat32(index, true);
+  }
+
+  /**
+   * Returns a float64 from a given index
+   * @param {number} index
+   * @return {number}
+   */
+  getFloat64(index) {
+    return this.dataView_.getFloat64(index, true);
+  }
+
+  /**
+   * Returns an int32 from a given index
+   * @param {number} index
+   * @return {number}
+   */
+  getInt32(index) {
+    return this.dataView_.getInt32(index, true);
+  }
+
+  /**
+   * @param {number} index
+   * @return {number}
+   */
+  getUint8(index) {
+    return this.dataView_.getUint8(index);
+  }
+
+  /**
+   * Returns a uint32 from a given index
+   * @param {number} index
+   * @return {number}
+   */
+  getUint32(index) {
+    return this.dataView_.getUint32(index, true);
+  }
+
+  /**
+   * Returns two JS numbers each representing 32 bits of a 64 bit number.
+   * @param {number} index
+   * @return {{lowBits: number, highBits: number, dataStart: number}}
+   */
+  getVarint(index) {
+    let start = index;
+    let lowBits = 0;
+    let highBits = 0;
+
+    for (let shift = 0; shift < 28; shift += 7) {
+      const b = this.dataView_.getUint8(start++);
+      lowBits |= (b & 0x7F) << shift;
+      if ((b & 0x80) === 0) {
+        return {lowBits, highBits, dataStart: start};
+      }
+    }
+
+    const middleByte = this.dataView_.getUint8(start++);
+
+    // last four bits of the first 32 bit number
+    lowBits |= (middleByte & 0x0F) << 28;
+
+    // 3 upper bits are part of the next 32 bit number
+    highBits = (middleByte & 0x70) >> 4;
+
+    if ((middleByte & 0x80) === 0) {
+      return {lowBits, highBits, dataStart: start};
+    }
+
+
+    for (let shift = 3; shift <= 31; shift += 7) {
+      const b = this.dataView_.getUint8(start++);
+      highBits |= (b & 0x7F) << shift;
+      if ((b & 0x80) === 0) {
+        return {lowBits, highBits, dataStart: start};
+      }
+    }
+
+    checkState(false, 'Data is longer than 10 bytes');
+    return {lowBits, highBits, dataStart: start};
+  }
+
+  /**
+   * Skips over a varint at a given index and returns the next position.
+   * @param {number} index Start of the data.
+   * @return {number} Position of the first byte after the varint.
+   * @package
+   */
+  skipVarint(index) {
+    let cursor = index;
+    checkCriticalElementIndex(cursor, this.endIndex());
+    while (this.dataView_.getUint8(cursor++) & 0x80) {
+      checkCriticalElementIndex(cursor, this.endIndex());
+    }
+    checkCriticalPositionIndex(cursor, index + 10);
+    return cursor;
+  }
+
+  /**
+   * @param {number} startIndex
+   * @param {number} length
+   * @return {!BufferDecoder}
+   */
+  subBufferDecoder(startIndex, length) {
+    checkState(
+        startIndex >= this.startIndex(),
+        `Current start: ${this.startIndex()}, subBufferDecoder start: ${
+            startIndex}`);
+    checkState(length >= 0, `Length: ${length}`);
+    checkState(
+        startIndex + length <= this.endIndex(),
+        `Current end: ${this.endIndex()}, subBufferDecoder start: ${
+            startIndex}, subBufferDecoder length: ${length}`);
+    return new BufferDecoder(this.dataView_, startIndex, length);
+  }
+
+  /**
+   * Returns the buffer as a string.
+   * @return {string}
+   */
+  asString() {
+    // TODO: Remove this check when we no longer need to support IE
+    const stringDataView = this.length() === 0 ?
+        emptyDataView() :
+        new DataView(this.dataView_.buffer, this.startIndex_, this.length());
+    return stringDecoderFunction()(stringDataView);
+  }
+
+  /**
+   * Returns the buffer as a ByteString.
+   * @return {!ByteString}
+   */
+  asByteString() {
+    return byteStringFromUint8ArrayUnsafe(this.asUint8Array());
+  }
+
+  /**
+   * Returns the DataView as an Uint8Array. DO NOT MODIFY or expose the
+   * underlying buffer.
+   *
+   * @package
+   * @return {!Uint8Array}
+   */
+  asUint8Array() {
+    return new Uint8Array(
+        this.dataView_.buffer, this.startIndex_, this.length());
+  }
+}
+
+exports = BufferDecoder;

+ 18 - 0
js/experimental/runtime/kernel/buffer_decoder_helper.js

@@ -0,0 +1,18 @@
+/**
+ * @fileoverview Helper methods to create BufferDecoders.
+ */
+goog.module('protobuf.binary.bufferDecoderHelper');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+
+/**
+ * @param {...number} bytes
+ * @return {!BufferDecoder}
+ */
+function createBufferDecoder(...bytes) {
+  return BufferDecoder.fromArrayBuffer(new Uint8Array(bytes).buffer);
+}
+
+exports = {
+  createBufferDecoder,
+};

+ 104 - 0
js/experimental/runtime/kernel/buffer_decoder_test.js

@@ -0,0 +1,104 @@
+/**
+ * @fileoverview Tests for BufferDecoder.
+ */
+
+goog.module('protobuf.binary.varintsTest');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const {CHECK_CRITICAL_BOUNDS, CHECK_STATE} = goog.require('protobuf.internal.checks');
+
+goog.setTestOnly();
+
+/**
+ * @param {...number} bytes
+ * @return {!ArrayBuffer}
+ */
+function createArrayBuffer(...bytes) {
+  return new Uint8Array(bytes).buffer;
+}
+
+describe('Skip varint does', () => {
+  it('skip a varint', () => {
+    const bufferDecoder =
+        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x01));
+    expect(bufferDecoder.skipVarint(0)).toBe(1);
+  });
+
+  it('fail when varint is larger than 10 bytes', () => {
+    const bufferDecoder = BufferDecoder.fromArrayBuffer(createArrayBuffer(
+        0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00));
+
+    if (CHECK_CRITICAL_BOUNDS) {
+      expect(() => bufferDecoder.skipVarint(0)).toThrow();
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(bufferDecoder.skipVarint(0)).toBe(11);
+    }
+  });
+
+  it('fail when varint is beyond end of underlying array', () => {
+    const bufferDecoder =
+        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x80, 0x80));
+    expect(() => bufferDecoder.skipVarint(0)).toThrow();
+  });
+});
+
+describe('readVarint64 does', () => {
+  it('read zero', () => {
+    const bufferDecoder =
+        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x00));
+    const {dataStart, lowBits, highBits} = bufferDecoder.getVarint(0);
+    expect(dataStart).toBe(1);
+    expect(lowBits).toBe(0);
+    expect(highBits).toBe(0);
+  });
+
+  it('read one', () => {
+    const bufferDecoder =
+        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x01));
+    const {dataStart, lowBits, highBits} = bufferDecoder.getVarint(0);
+    expect(dataStart).toBe(1);
+    expect(lowBits).toBe(1);
+    expect(highBits).toBe(0);
+  });
+
+  it('read max value', () => {
+    const bufferDecoder = BufferDecoder.fromArrayBuffer(createArrayBuffer(
+        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01));
+    const {dataStart, lowBits, highBits} = bufferDecoder.getVarint(0);
+    expect(dataStart).toBe(10);
+    expect(lowBits).toBe(-1);
+    expect(highBits).toBe(-1);
+  });
+});
+
+describe('subBufferDecoder does', () => {
+  it('can create valid sub buffers', () => {
+    const bufferDecoder =
+        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x00, 0x01, 0x02));
+
+    expect(bufferDecoder.subBufferDecoder(0, 0))
+        .toEqual(BufferDecoder.fromArrayBuffer(createArrayBuffer()));
+    expect(bufferDecoder.subBufferDecoder(0, 1))
+        .toEqual(BufferDecoder.fromArrayBuffer(createArrayBuffer(0x00)));
+    expect(bufferDecoder.subBufferDecoder(1, 0))
+        .toEqual(BufferDecoder.fromArrayBuffer(createArrayBuffer()));
+    expect(bufferDecoder.subBufferDecoder(1, 1))
+        .toEqual(BufferDecoder.fromArrayBuffer(createArrayBuffer(0x01)));
+    expect(bufferDecoder.subBufferDecoder(1, 2))
+        .toEqual(BufferDecoder.fromArrayBuffer(createArrayBuffer(0x01, 0x02)));
+  });
+
+  it('can not create invalid', () => {
+    const bufferDecoder =
+        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x00, 0x01, 0x02));
+    if (CHECK_STATE) {
+      expect(() => bufferDecoder.subBufferDecoder(-1, 1)).toThrow();
+      expect(() => bufferDecoder.subBufferDecoder(0, -4)).toThrow();
+      expect(() => bufferDecoder.subBufferDecoder(0, 4)).toThrow();
+    }
+  });
+});

+ 91 - 0
js/experimental/runtime/kernel/conformance/conformance_request.js

@@ -0,0 +1,91 @@
+/**
+ * @fileoverview Handwritten code of ConformanceRequest.
+ */
+goog.module('proto.conformance.ConformanceRequest');
+
+const LazyAccessor = goog.require('protobuf.binary.LazyAccessor');
+const WireFormat = goog.require('proto.conformance.WireFormat');
+
+/**
+ * Handwritten code of conformance.ConformanceRequest.
+ * This is used to send request from the conformance test runner to the testee.
+ * Check //third_party/protobuf/testing/protobuf/conformance/conformance.proto
+ * for more details.
+ * @final
+ */
+class ConformanceRequest {
+  /**
+   * @param {!ArrayBuffer} bytes
+   * @private
+   */
+  constructor(bytes) {
+    /** @private @const {!LazyAccessor} */
+    this.accessor_ = LazyAccessor.fromArrayBuffer(bytes);
+  }
+
+  /**
+   * Create a request instance with the given bytes data.
+   * @param {!ArrayBuffer} bytes
+   * @return {!ConformanceRequest}
+   */
+  static deserialize(bytes) {
+    return new ConformanceRequest(bytes);
+  }
+
+  /**
+   * Gets the protobuf_payload.
+   * @return {!ArrayBuffer}
+   */
+  getProtobufPayload() {
+    return this.accessor_.getBytesWithDefault(1).toArrayBuffer();
+  }
+
+  /**
+   * Gets the requested_output_format.
+   * @return {!WireFormat}
+   */
+  getRequestedOutputFormat() {
+    return /** @type {!WireFormat} */ (this.accessor_.getInt32WithDefault(3));
+  }
+
+  /**
+   * Gets the message_type.
+   * @return {string}
+   */
+  getMessageType() {
+    return this.accessor_.getStringWithDefault(4);
+  }
+
+  /**
+   * Gets the oneof case for payload field.
+   * This implementation assumes only one field in a oneof group is set.
+   * @return {!ConformanceRequest.PayloadCase}
+   */
+  getPayloadCase() {
+    if (this.accessor_.hasFieldNumber(1)) {
+      return /** @type {!ConformanceRequest.PayloadCase} */ (
+          ConformanceRequest.PayloadCase.PROTOBUF_PAYLOAD);
+    } else if (this.accessor_.hasFieldNumber(2)) {
+      return /** @type {!ConformanceRequest.PayloadCase} */ (
+          ConformanceRequest.PayloadCase.JSON_PAYLOAD);
+    } else if (this.accessor_.hasFieldNumber(8)) {
+      return /** @type {!ConformanceRequest.PayloadCase} */ (
+          ConformanceRequest.PayloadCase.TEXT_PAYLOAD);
+    } else {
+      return /** @type {!ConformanceRequest.PayloadCase} */ (
+          ConformanceRequest.PayloadCase.PAYLOAD_NOT_SET);
+    }
+  }
+}
+
+/**
+ * @enum {number}
+ */
+ConformanceRequest.PayloadCase = {
+  PAYLOAD_NOT_SET: 0,
+  PROTOBUF_PAYLOAD: 1,
+  JSON_PAYLOAD: 2,
+  TEXT_PAYLOAD: 8,
+};
+
+exports = ConformanceRequest;

+ 76 - 0
js/experimental/runtime/kernel/conformance/conformance_response.js

@@ -0,0 +1,76 @@
+/**
+ * @fileoverview Handwritten code of ConformanceResponse.
+ */
+goog.module('proto.conformance.ConformanceResponse');
+
+const ByteString = goog.require('protobuf.ByteString');
+const LazyAccessor = goog.require('protobuf.binary.LazyAccessor');
+
+/**
+ * Handwritten code of conformance.ConformanceResponse.
+ * This is used to send response from the conformance testee to the test runner.
+ * Check //third_party/protobuf/testing/protobuf/conformance/conformance.proto
+ * for more details.
+ * @final
+ */
+class ConformanceResponse {
+  /**
+   * @param {!ArrayBuffer} bytes
+   * @private
+   */
+  constructor(bytes) {
+    /** @private @const {!LazyAccessor} */
+    this.accessor_ = LazyAccessor.fromArrayBuffer(bytes);
+  }
+
+  /**
+   * Create an empty response instance.
+   * @return {!ConformanceResponse}
+   */
+  static createEmpty() {
+    return new ConformanceResponse(new ArrayBuffer(0));
+  }
+
+  /**
+   * Sets parse_error field.
+   * @param {string} value
+   */
+  setParseError(value) {
+    this.accessor_.setString(1, value);
+  }
+
+  /**
+   * Sets runtime_error field.
+   * @param {string} value
+   */
+  setRuntimeError(value) {
+    this.accessor_.setString(2, value);
+  }
+
+  /**
+   * Sets protobuf_payload field.
+   * @param {!ArrayBuffer} value
+   */
+  setProtobufPayload(value) {
+    const bytesString = ByteString.fromArrayBuffer(value);
+    this.accessor_.setBytes(3, bytesString);
+  }
+
+  /**
+   * Sets skipped field.
+   * @param {string} value
+   */
+  setSkipped(value) {
+    this.accessor_.setString(5, value);
+  }
+
+  /**
+   * Serializes into binary data.
+   * @return {!ArrayBuffer}
+   */
+  serialize() {
+    return this.accessor_.serialize();
+  }
+}
+
+exports = ConformanceResponse;

+ 103 - 0
js/experimental/runtime/kernel/conformance/conformance_testee.js

@@ -0,0 +1,103 @@
+goog.module('javascript.protobuf.conformance');
+
+const ConformanceRequest = goog.require('proto.conformance.ConformanceRequest');
+const ConformanceResponse = goog.require('proto.conformance.ConformanceResponse');
+const TestAllTypesProto2 = goog.require('proto.conformance.TestAllTypesProto2');
+const TestAllTypesProto3 = goog.require('proto.conformance.TestAllTypesProto3');
+const WireFormat = goog.require('proto.conformance.WireFormat');
+const base64 = goog.require('goog.crypt.base64');
+
+/**
+ * Creates a `proto.conformance.ConformanceResponse` response according to the
+ * `proto.conformance.ConformanceRequest` request.
+ * @param {!ConformanceRequest} request
+ * @return {!ConformanceResponse} response
+ */
+function doTest(request) {
+  const response = ConformanceResponse.createEmpty();
+
+  if(request.getPayloadCase() === ConformanceRequest.PayloadCase.JSON_PAYLOAD) {
+    response.setSkipped('Json is not supported as input format.');
+    return response;
+  }
+
+  if(request.getPayloadCase() === ConformanceRequest.PayloadCase.TEXT_PAYLOAD) {
+    response.setSkipped('Text format is not supported as input format.');
+    return response;
+  }
+
+  if(request.getPayloadCase() === ConformanceRequest.PayloadCase.PAYLOAD_NOT_SET) {
+    response.setRuntimeError('Request didn\'t have payload.');
+    return response;
+  }
+
+  if(request.getPayloadCase() !== ConformanceRequest.PayloadCase.PROTOBUF_PAYLOAD) {
+     throw new Error('Request didn\'t have accepted input format.');
+  }
+
+  if (request.getRequestedOutputFormat() === WireFormat.JSON) {
+    response.setSkipped('Json is not supported as output format.');
+    return response;
+  }
+
+  if (request.getRequestedOutputFormat() === WireFormat.TEXT_FORMAT) {
+    response.setSkipped('Text format is not supported as output format.');
+    return response;
+  }
+
+  if (request.getRequestedOutputFormat() === WireFormat.TEXT_FORMAT) {
+    response.setRuntimeError('Unspecified output format');
+    return response;
+  }
+
+  if (request.getRequestedOutputFormat() !== WireFormat.PROTOBUF) {
+    throw new Error('Request didn\'t have accepted output format.');
+  }
+
+  if (request.getMessageType() === 'conformance.FailureSet') {
+    response.setProtobufPayload(new ArrayBuffer(0));
+  } else if (
+      request.getMessageType() ===
+      'protobuf_test_messages.proto2.TestAllTypesProto2') {
+    try {
+      const testMessage =
+          TestAllTypesProto2.deserialize(request.getProtobufPayload());
+      response.setProtobufPayload(testMessage.serialize());
+    } catch (err) {
+      response.setParseError(err.toString());
+    }
+  } else if (
+      request.getMessageType() ===
+      'protobuf_test_messages.proto3.TestAllTypesProto3') {
+    try {
+      const testMessage =
+          TestAllTypesProto3.deserialize(request.getProtobufPayload());
+      response.setProtobufPayload(testMessage.serialize());
+    } catch (err) {
+      response.setParseError(err.toString());
+    }
+  } else {
+    throw new Error(
+        `Payload message not supported: ${request.getMessageType()}.`);
+  }
+
+  return response;
+}
+
+/**
+ * Same as doTest, but both request and response are in base64.
+ * @param {string} base64Request
+ * @return {string} response
+ */
+function runConformanceTest(base64Request) {
+  const request =
+      ConformanceRequest.deserialize(
+          base64.decodeStringToUint8Array(base64Request).buffer);
+  const response = doTest(request);
+  return base64.encodeByteArray(new Uint8Array(response.serialize()));
+}
+
+// Needed for node test
+exports.doTest = doTest;
+// Needed for browser test
+goog.exportSymbol('runConformanceTest', runConformanceTest);

+ 62 - 0
js/experimental/runtime/kernel/conformance/conformance_testee_runner_node.js

@@ -0,0 +1,62 @@
+const ConformanceRequest = goog.require('proto.conformance.ConformanceRequest');
+const {doTest} = goog.require('javascript.protobuf.conformance');
+const fs = require('fs');
+
+
+/**
+ * Reads a buffer of N bytes.
+ * @param {number} bytes Number of bytes to read.
+ * @return {!Buffer} Buffer which contains data.
+ */
+function readBuffer(bytes) {
+  // Linux cannot use process.stdin.fd (which isn't set up as sync)
+  const buf = new Buffer.alloc(bytes);
+  const fd = fs.openSync('/dev/stdin', 'r');
+  fs.readSync(fd, buf, 0, bytes);
+  fs.closeSync(fd);
+  return buf;
+}
+
+/**
+ * Writes all data in buffer.
+ * @param {!Buffer} buffer Buffer which contains data.
+ */
+function writeBuffer(buffer) {
+  // Under linux, process.stdout.fd is async. Needs to open stdout in a synced
+  // way for sync write.
+  const fd = fs.openSync('/dev/stdout', 'w');
+  fs.writeSync(fd, buffer, 0, buffer.length);
+  fs.closeSync(fd);
+}
+
+/**
+ * Returns true if the test ran successfully, false on legitimate EOF.
+ * @return {boolean} Whether to continue test.
+ */
+function runConformanceTest() {
+  const requestLengthBuf = readBuffer(4);
+  const requestLength = requestLengthBuf.readInt32LE(0);
+  if (!requestLength) {
+    return false;
+  }
+
+  const serializedRequest = readBuffer(requestLength);
+  const array = new Uint8Array(serializedRequest);
+  const request = ConformanceRequest.deserialize(array.buffer);
+  const response = doTest(request);
+
+  const serializedResponse = response.serialize();
+
+  const responseLengthBuf = new Buffer.alloc(4);
+  responseLengthBuf.writeInt32LE(serializedResponse.byteLength, 0);
+  writeBuffer(responseLengthBuf);
+  writeBuffer(new Buffer.from(serializedResponse));
+
+  return true;
+}
+
+while (true) {
+  if (!runConformanceTest()) {
+    break;
+  }
+}

+ 309 - 0
js/experimental/runtime/kernel/conformance/test_all_types_proto2.js

@@ -0,0 +1,309 @@
+/**
+ * @fileoverview Handwritten code of TestAllTypesProto2.
+ */
+goog.module('proto.conformance.TestAllTypesProto2');
+
+const InternalMessage = goog.require('protobuf.binary.InternalMessage');
+const LazyAccessor = goog.require('protobuf.binary.LazyAccessor');
+
+/**
+ * Handwritten code of conformance.TestAllTypesProto2.
+ * Check google/protobuf/test_messages_proto3.proto for more details.
+ * @implements {InternalMessage}
+ * @final
+ */
+class TestAllTypesProto2 {
+  /**
+   * @param {!LazyAccessor=} accessor
+   * @private
+   */
+  constructor(accessor = LazyAccessor.createEmpty()) {
+    /** @private @const {!LazyAccessor} */
+    this.accessor_ = accessor;
+  }
+
+  /**
+   * @override
+   * @package
+   * @return {!LazyAccessor}
+   */
+  internalGetKernel() {
+    return this.accessor_;
+  }
+
+  /**
+   * Create a request instance with the given bytes data.
+   * If we directly use the accessor created by the binary decoding, the
+   * LazyAccessor instance will only copy the same data over for encoding.  By
+   * explicitly fetching data from the previous accessor and setting all fields
+   * into a new accessor, we will actually test encoding/decoding for the binary
+   * format.
+   * @param {!ArrayBuffer} bytes
+   * @return {!TestAllTypesProto2}
+   */
+  static deserialize(bytes) {
+    const msg = new TestAllTypesProto2();
+    const requestAccessor = LazyAccessor.fromArrayBuffer(bytes);
+
+    if (requestAccessor.hasFieldNumber(1)) {
+      const value = requestAccessor.getInt32WithDefault(1);
+      msg.accessor_.setInt32(1, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(2)) {
+      const value = requestAccessor.getInt64WithDefault(2);
+      msg.accessor_.setInt64(2, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(3)) {
+      const value = requestAccessor.getUint32WithDefault(3);
+      msg.accessor_.setUint32(3, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(4)) {
+      const value = requestAccessor.getUint64WithDefault(4);
+      msg.accessor_.setUint64(4, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(5)) {
+      const value = requestAccessor.getSint32WithDefault(5);
+      msg.accessor_.setSint32(5, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(6)) {
+      const value = requestAccessor.getSint64WithDefault(6);
+      msg.accessor_.setSint64(6, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(7)) {
+      const value = requestAccessor.getFixed32WithDefault(7);
+      msg.accessor_.setFixed32(7, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(8)) {
+      const value = requestAccessor.getFixed64WithDefault(8);
+      msg.accessor_.setFixed64(8, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(9)) {
+      const value = requestAccessor.getSfixed32WithDefault(9);
+      msg.accessor_.setSfixed32(9, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(10)) {
+      const value = requestAccessor.getSfixed64WithDefault(10);
+      msg.accessor_.setSfixed64(10, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(11)) {
+      const value = requestAccessor.getFloatWithDefault(11);
+      msg.accessor_.setFloat(11, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(12)) {
+      const value = requestAccessor.getDoubleWithDefault(12);
+      msg.accessor_.setDouble(12, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(13)) {
+      const value = requestAccessor.getBoolWithDefault(13);
+      msg.accessor_.setBool(13, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(14)) {
+      const value = requestAccessor.getStringWithDefault(14);
+      msg.accessor_.setString(14, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(15)) {
+      const value = requestAccessor.getBytesWithDefault(15);
+      msg.accessor_.setBytes(15, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(18)) {
+      const value = requestAccessor.getMessage(
+          18, (accessor) => new TestAllTypesProto2(accessor));
+      msg.accessor_.setMessage(18, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(21)) {
+      // Unknown enum is not checked here, because even if an enum is unknown,
+      // it should be kept during encoding. For the purpose of wire format test,
+      // we can simplify the implementation by treating it as an int32 field,
+      // which has the same semantic except for the unknown value checking.
+      const value = requestAccessor.getInt32WithDefault(21);
+      msg.accessor_.setInt32(21, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(31)) {
+      const value = requestAccessor.getRepeatedInt32Iterable(31);
+      msg.accessor_.setUnpackedInt32Iterable(31, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(32)) {
+      const value = requestAccessor.getRepeatedInt64Iterable(32);
+      msg.accessor_.setUnpackedInt64Iterable(32, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(33)) {
+      const value = requestAccessor.getRepeatedUint32Iterable(33);
+      msg.accessor_.setUnpackedUint32Iterable(33, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(34)) {
+      const value = requestAccessor.getRepeatedUint64Iterable(34);
+      msg.accessor_.setUnpackedUint64Iterable(34, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(35)) {
+      const value = requestAccessor.getRepeatedSint32Iterable(35);
+      msg.accessor_.setUnpackedSint32Iterable(35, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(36)) {
+      const value = requestAccessor.getRepeatedSint64Iterable(36);
+      msg.accessor_.setUnpackedSint64Iterable(36, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(37)) {
+      const value = requestAccessor.getRepeatedFixed32Iterable(37);
+      msg.accessor_.setUnpackedFixed32Iterable(37, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(38)) {
+      const value = requestAccessor.getRepeatedFixed64Iterable(38);
+      msg.accessor_.setUnpackedFixed64Iterable(38, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(39)) {
+      const value = requestAccessor.getRepeatedSfixed32Iterable(39);
+      msg.accessor_.setUnpackedSfixed32Iterable(39, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(40)) {
+      const value = requestAccessor.getRepeatedSfixed64Iterable(40);
+      msg.accessor_.setUnpackedSfixed64Iterable(40, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(41)) {
+      const value = requestAccessor.getRepeatedFloatIterable(41);
+      msg.accessor_.setUnpackedFloatIterable(41, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(42)) {
+      const value = requestAccessor.getRepeatedDoubleIterable(42);
+      msg.accessor_.setUnpackedDoubleIterable(42, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(43)) {
+      const value = requestAccessor.getRepeatedBoolIterable(43);
+      msg.accessor_.setUnpackedBoolIterable(43, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(44)) {
+      const value = requestAccessor.getRepeatedStringIterable(44);
+      msg.accessor_.setRepeatedStringIterable(44, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(45)) {
+      const value = requestAccessor.getRepeatedBytesIterable(45);
+      msg.accessor_.setRepeatedBytesIterable(45, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(48)) {
+      const value = requestAccessor.getRepeatedMessageIterable(
+          48, (accessor) => new TestAllTypesProto2(accessor));
+      msg.accessor_.setRepeatedMessageIterable(48, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(51)) {
+      // Unknown enum is not checked here, because even if an enum is unknown,
+      // it should be kept during encoding. For the purpose of wire format test,
+      // we can simplify the implementation by treating it as an int32 field,
+      // which has the same semantic except for the unknown value checking.
+      const value = requestAccessor.getRepeatedInt32Iterable(51);
+      msg.accessor_.setUnpackedInt32Iterable(51, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(75)) {
+      const value = requestAccessor.getRepeatedInt32Iterable(75);
+      msg.accessor_.setPackedInt32Iterable(75, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(76)) {
+      const value = requestAccessor.getRepeatedInt64Iterable(76);
+      msg.accessor_.setPackedInt64Iterable(76, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(77)) {
+      const value = requestAccessor.getRepeatedUint32Iterable(77);
+      msg.accessor_.setPackedUint32Iterable(77, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(78)) {
+      const value = requestAccessor.getRepeatedUint64Iterable(78);
+      msg.accessor_.setPackedUint64Iterable(78, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(79)) {
+      const value = requestAccessor.getRepeatedSint32Iterable(79);
+      msg.accessor_.setPackedSint32Iterable(79, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(80)) {
+      const value = requestAccessor.getRepeatedSint64Iterable(80);
+      msg.accessor_.setPackedSint64Iterable(80, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(81)) {
+      const value = requestAccessor.getRepeatedFixed32Iterable(81);
+      msg.accessor_.setPackedFixed32Iterable(81, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(82)) {
+      const value = requestAccessor.getRepeatedFixed64Iterable(82);
+      msg.accessor_.setPackedFixed64Iterable(82, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(83)) {
+      const value = requestAccessor.getRepeatedSfixed32Iterable(83);
+      msg.accessor_.setPackedSfixed32Iterable(83, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(84)) {
+      const value = requestAccessor.getRepeatedSfixed64Iterable(84);
+      msg.accessor_.setPackedSfixed64Iterable(84, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(85)) {
+      const value = requestAccessor.getRepeatedFloatIterable(85);
+      msg.accessor_.setPackedFloatIterable(85, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(86)) {
+      const value = requestAccessor.getRepeatedDoubleIterable(86);
+      msg.accessor_.setPackedDoubleIterable(86, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(87)) {
+      const value = requestAccessor.getRepeatedBoolIterable(87);
+      msg.accessor_.setPackedBoolIterable(87, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(88)) {
+      const value = requestAccessor.getRepeatedInt32Iterable(88);
+      msg.accessor_.setPackedInt32Iterable(88, value);
+    }
+    return msg;
+  }
+
+  /**
+   * Serializes into binary data.
+   * @return {!ArrayBuffer}
+   */
+  serialize() {
+    return this.accessor_.serialize();
+  }
+}
+
+exports = TestAllTypesProto2;

+ 310 - 0
js/experimental/runtime/kernel/conformance/test_all_types_proto3.js

@@ -0,0 +1,310 @@
+/**
+ * @fileoverview Handwritten code of TestAllTypesProto3.
+ */
+goog.module('proto.conformance.TestAllTypesProto3');
+
+const InternalMessage = goog.require('protobuf.binary.InternalMessage');
+const LazyAccessor = goog.require('protobuf.binary.LazyAccessor');
+
+/**
+ * Handwritten code of conformance.TestAllTypesProto3.
+ * Check google/protobuf/test_messages_proto3.proto for more details.
+ * @implements {InternalMessage}
+ * @final
+ */
+class TestAllTypesProto3 {
+  /**
+   * @param {!LazyAccessor=} accessor
+   * @private
+   */
+  constructor(accessor = LazyAccessor.createEmpty()) {
+    /** @private @const {!LazyAccessor} */
+    this.accessor_ = accessor;
+  }
+
+  /**
+   * @override
+   * @package
+   * @return {!LazyAccessor}
+   */
+  internalGetKernel() {
+    return this.accessor_;
+  }
+
+  /**
+   * Create a request instance with the given bytes data.
+   * If we directly use the accessor created by the binary decoding, the
+   * LazyAccessor instance will only copy the same data over for encoding.  By
+   * explicitly fetching data from the previous accessor and setting all fields
+   * into a new accessor, we will actually test encoding/decoding for the binary
+   * format.
+   * @param {!ArrayBuffer} bytes
+   * @return {!TestAllTypesProto3}
+   */
+  static deserialize(bytes) {
+    const msg = new TestAllTypesProto3();
+    const requestAccessor = LazyAccessor.fromArrayBuffer(bytes);
+
+    if (requestAccessor.hasFieldNumber(1)) {
+      const value = requestAccessor.getInt32WithDefault(1);
+      msg.accessor_.setInt32(1, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(2)) {
+      const value = requestAccessor.getInt64WithDefault(2);
+      msg.accessor_.setInt64(2, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(3)) {
+      const value = requestAccessor.getUint32WithDefault(3);
+      msg.accessor_.setUint32(3, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(4)) {
+      const value = requestAccessor.getUint64WithDefault(4);
+      msg.accessor_.setUint64(4, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(5)) {
+      const value = requestAccessor.getSint32WithDefault(5);
+      msg.accessor_.setSint32(5, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(6)) {
+      const value = requestAccessor.getSint64WithDefault(6);
+      msg.accessor_.setSint64(6, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(7)) {
+      const value = requestAccessor.getFixed32WithDefault(7);
+      msg.accessor_.setFixed32(7, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(8)) {
+      const value = requestAccessor.getFixed64WithDefault(8);
+      msg.accessor_.setFixed64(8, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(9)) {
+      const value = requestAccessor.getSfixed32WithDefault(9);
+      msg.accessor_.setSfixed32(9, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(10)) {
+      const value = requestAccessor.getSfixed64WithDefault(10);
+      msg.accessor_.setSfixed64(10, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(11)) {
+      const value = requestAccessor.getFloatWithDefault(11);
+      msg.accessor_.setFloat(11, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(12)) {
+      const value = requestAccessor.getDoubleWithDefault(12);
+      msg.accessor_.setDouble(12, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(13)) {
+      const value = requestAccessor.getBoolWithDefault(13);
+      msg.accessor_.setBool(13, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(14)) {
+      const value = requestAccessor.getStringWithDefault(14);
+      msg.accessor_.setString(14, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(15)) {
+      const value = requestAccessor.getBytesWithDefault(15);
+      msg.accessor_.setBytes(15, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(18)) {
+      const value = requestAccessor.getMessage(
+          18, (accessor) => new TestAllTypesProto3(accessor));
+      msg.accessor_.setMessage(18, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(21)) {
+      // Unknown enum is not checked here, because even if an enum is unknown,
+      // it should be kept during encoding. For the purpose of wire format test,
+      // we can simplify the implementation by treating it as an int32 field,
+      // which has the same semantic except for the unknown value checking.
+      const value = requestAccessor.getInt32WithDefault(21);
+      msg.accessor_.setInt32(21, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(31)) {
+      const value = requestAccessor.getRepeatedInt32Iterable(31);
+      msg.accessor_.setPackedInt32Iterable(31, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(32)) {
+      const value = requestAccessor.getRepeatedInt64Iterable(32);
+      msg.accessor_.setPackedInt64Iterable(32, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(33)) {
+      const value = requestAccessor.getRepeatedUint32Iterable(33);
+      msg.accessor_.setPackedUint32Iterable(33, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(34)) {
+      const value = requestAccessor.getRepeatedUint64Iterable(34);
+      msg.accessor_.setPackedUint64Iterable(34, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(35)) {
+      const value = requestAccessor.getRepeatedSint32Iterable(35);
+      msg.accessor_.setPackedSint32Iterable(35, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(36)) {
+      const value = requestAccessor.getRepeatedSint64Iterable(36);
+      msg.accessor_.setPackedSint64Iterable(36, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(37)) {
+      const value = requestAccessor.getRepeatedFixed32Iterable(37);
+      msg.accessor_.setPackedFixed32Iterable(37, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(38)) {
+      const value = requestAccessor.getRepeatedFixed64Iterable(38);
+      msg.accessor_.setPackedFixed64Iterable(38, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(39)) {
+      const value = requestAccessor.getRepeatedSfixed32Iterable(39);
+      msg.accessor_.setPackedSfixed32Iterable(39, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(40)) {
+      const value = requestAccessor.getRepeatedSfixed64Iterable(40);
+      msg.accessor_.setPackedSfixed64Iterable(40, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(41)) {
+      const value = requestAccessor.getRepeatedFloatIterable(41);
+      msg.accessor_.setPackedFloatIterable(41, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(42)) {
+      const value = requestAccessor.getRepeatedDoubleIterable(42);
+      msg.accessor_.setPackedDoubleIterable(42, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(43)) {
+      const value = requestAccessor.getRepeatedBoolIterable(43);
+      msg.accessor_.setPackedBoolIterable(43, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(44)) {
+      const value = requestAccessor.getRepeatedStringIterable(44);
+      msg.accessor_.setRepeatedStringIterable(44, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(45)) {
+      const value = requestAccessor.getRepeatedBytesIterable(45);
+      msg.accessor_.setRepeatedBytesIterable(45, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(48)) {
+      const value = requestAccessor.getRepeatedMessageIterable(
+          48, (accessor) => new TestAllTypesProto3(accessor));
+      msg.accessor_.setRepeatedMessageIterable(48, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(51)) {
+      // Unknown enum is not checked here, because even if an enum is unknown,
+      // it should be kept during encoding. For the purpose of wire format test,
+      // we can simplify the implementation by treating it as an int32 field,
+      // which has the same semantic except for the unknown value checking.
+      const value = requestAccessor.getRepeatedInt32Iterable(51);
+      msg.accessor_.setPackedInt32Iterable(51, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(89)) {
+      const value = requestAccessor.getRepeatedInt32Iterable(89);
+      msg.accessor_.setUnpackedInt32Iterable(89, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(90)) {
+      const value = requestAccessor.getRepeatedInt64Iterable(90);
+      msg.accessor_.setUnpackedInt64Iterable(90, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(91)) {
+      const value = requestAccessor.getRepeatedUint32Iterable(91);
+      msg.accessor_.setUnpackedUint32Iterable(91, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(92)) {
+      const value = requestAccessor.getRepeatedUint64Iterable(92);
+      msg.accessor_.setUnpackedUint64Iterable(92, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(93)) {
+      const value = requestAccessor.getRepeatedSint32Iterable(93);
+      msg.accessor_.setUnpackedSint32Iterable(93, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(94)) {
+      const value = requestAccessor.getRepeatedSint64Iterable(94);
+      msg.accessor_.setUnpackedSint64Iterable(94, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(95)) {
+      const value = requestAccessor.getRepeatedFixed32Iterable(95);
+      msg.accessor_.setUnpackedFixed32Iterable(95, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(96)) {
+      const value = requestAccessor.getRepeatedFixed64Iterable(96);
+      msg.accessor_.setUnpackedFixed64Iterable(96, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(97)) {
+      const value = requestAccessor.getRepeatedSfixed32Iterable(97);
+      msg.accessor_.setUnpackedSfixed32Iterable(97, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(98)) {
+      const value = requestAccessor.getRepeatedSfixed64Iterable(98);
+      msg.accessor_.setUnpackedSfixed64Iterable(98, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(99)) {
+      const value = requestAccessor.getRepeatedFloatIterable(99);
+      msg.accessor_.setUnpackedFloatIterable(99, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(100)) {
+      const value = requestAccessor.getRepeatedDoubleIterable(100);
+      msg.accessor_.setUnpackedDoubleIterable(100, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(101)) {
+      const value = requestAccessor.getRepeatedBoolIterable(101);
+      msg.accessor_.setUnpackedBoolIterable(101, value);
+    }
+
+    if (requestAccessor.hasFieldNumber(102)) {
+      const value = requestAccessor.getRepeatedInt32Iterable(102);
+      msg.accessor_.setUnpackedInt32Iterable(102, value);
+    }
+
+    return msg;
+  }
+
+  /**
+   * Serializes into binary data.
+   * @return {!ArrayBuffer}
+   */
+  serialize() {
+    return this.accessor_.serialize();
+  }
+}
+
+exports = TestAllTypesProto3;

+ 16 - 0
js/experimental/runtime/kernel/conformance/wire_format.js

@@ -0,0 +1,16 @@
+/**
+ * @fileoverview Handwritten code of WireFormat.
+ */
+goog.module('proto.conformance.WireFormat');
+
+/**
+ * @enum {number}
+ */
+const WireFormat = {
+  UNSPECIFIED: 0,
+  PROTOBUF: 1,
+  JSON: 2,
+  TEXT_FORMAT: 4,
+};
+
+exports = WireFormat;

+ 89 - 0
js/experimental/runtime/kernel/double_test_pairs.js

@@ -0,0 +1,89 @@
+/**
+ * @fileoverview Test data for double encoding and decoding.
+ */
+goog.module('protobuf.binary.doubleTestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of double values and their bit representation.
+ * This is used to test encoding and decoding from the protobuf wire format.
+ * @return {!Array<{name: string, doubleValue:number, bufferDecoder:
+ *     !BufferDecoder}>}
+ */
+function getDoublePairs() {
+  const doublePairs = [
+    {
+      name: 'zero',
+      doubleValue: 0,
+      bufferDecoder:
+          createBufferDecoder(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
+    },
+    {
+      name: 'minus zero',
+      doubleValue: -0,
+      bufferDecoder:
+          createBufferDecoder(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80)
+    },
+    {
+      name: 'one',
+      doubleValue: 1,
+      bufferDecoder:
+          createBufferDecoder(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F)
+    },
+    {
+      name: 'minus one',
+      doubleValue: -1,
+      bufferDecoder:
+          createBufferDecoder(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF)
+    },
+
+    {
+      name: 'PI',
+      doubleValue: Math.PI,
+      bufferDecoder:
+          createBufferDecoder(0x18, 0x2D, 0x44, 0x54, 0xFB, 0x21, 0x09, 0x40)
+
+    },
+    {
+      name: 'max value',
+      doubleValue: Number.MAX_VALUE,
+      bufferDecoder:
+          createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x7F)
+    },
+    {
+      name: 'min value',
+      doubleValue: Number.MIN_VALUE,
+      bufferDecoder:
+          createBufferDecoder(0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
+    },
+    {
+      name: 'Infinity',
+      doubleValue: Infinity,
+      bufferDecoder:
+          createBufferDecoder(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x7F)
+    },
+    {
+      name: 'minus Infinity',
+      doubleValue: -Infinity,
+      bufferDecoder:
+          createBufferDecoder(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF)
+    },
+    {
+      name: 'Number.MAX_SAFE_INTEGER',
+      doubleValue: Number.MAX_SAFE_INTEGER,
+      bufferDecoder:
+          createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x43)
+    },
+    {
+      name: 'Number.MIN_SAFE_INTEGER',
+      doubleValue: Number.MIN_SAFE_INTEGER,
+      bufferDecoder:
+          createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0xC3)
+    },
+  ];
+  return [...doublePairs];
+}
+
+exports = {getDoublePairs};

+ 196 - 0
js/experimental/runtime/kernel/field.js

@@ -0,0 +1,196 @@
+/**
+ * @fileoverview Contains classes that hold data for a protobuf field.
+ */
+
+goog.module('protobuf.binary.field');
+
+const WireType = goog.requireType('protobuf.binary.WireType');
+const Writer = goog.requireType('protobuf.binary.Writer');
+const {checkDefAndNotNull, checkState} = goog.require('protobuf.internal.checks');
+
+/**
+ * Number of bits taken to represent a wire type.
+ * @const {number}
+ */
+const WIRE_TYPE_LENGTH_BITS = 3;
+
+/** @const {number} */
+const WIRE_TYPE_EXTRACTOR = (1 << WIRE_TYPE_LENGTH_BITS) - 1;
+
+/**
+ * An IndexEntry consists of the wire type and the position of a field in the
+ * binary data. The wire type and the position are encoded into a single number
+ * to save memory, which can be decoded using Field.getWireType() and
+ * Field.getStartIndex() methods.
+ * @typedef {number}
+ */
+let IndexEntry;
+
+/**
+ * An entry containing the index into the binary data and/or the corresponding
+ * cached JS object(s) for a field.
+ * @template T
+ * @final
+ * @package
+ */
+class Field {
+  /**
+   * Creates a field and inserts the wireType and position of the first
+   * occurrence of a field.
+   * @param {!WireType} wireType
+   * @param {number} startIndex
+   * @return {!Field}
+   */
+  static fromFirstIndexEntry(wireType, startIndex) {
+    return new Field([Field.encodeIndexEntry(wireType, startIndex)]);
+  }
+
+  /**
+   * @param {T} decodedValue The cached JS object decoded from the binary data.
+   * @param {function(!Writer, number, T):void|undefined} encoder Write function
+   *     to encode the cache into binary bytes.
+   * @return {!Field}
+   * @template T
+   */
+  static fromDecodedValue(decodedValue, encoder) {
+    return new Field(null, decodedValue, encoder);
+  }
+
+  /**
+   * @param {!WireType} wireType
+   * @param {number} startIndex
+   * @return {!IndexEntry}
+   */
+  static encodeIndexEntry(wireType, startIndex) {
+    return startIndex << WIRE_TYPE_LENGTH_BITS | wireType;
+  }
+
+  /**
+   * @param {!IndexEntry} indexEntry
+   * @return {!WireType}
+   */
+  static getWireType(indexEntry) {
+    return /** @type {!WireType} */ (indexEntry & WIRE_TYPE_EXTRACTOR);
+  }
+
+  /**
+   * @param {!IndexEntry} indexEntry
+   * @return {number}
+   */
+  static getStartIndex(indexEntry) {
+    return indexEntry >> WIRE_TYPE_LENGTH_BITS;
+  }
+
+  /**
+   * @param {?Array<!IndexEntry>} indexArray
+   * @param {T=} decodedValue
+   * @param {function(!Writer, number, T):void=} encoder
+   * @private
+   */
+  constructor(indexArray, decodedValue = undefined, encoder = undefined) {
+    checkState(
+        !!indexArray || decodedValue !== undefined,
+        'At least one of indexArray and decodedValue must be set');
+
+    /** @private {?Array<!IndexEntry>} */
+    this.indexArray_ = indexArray;
+    /** @private {T|undefined} */
+    this.decodedValue_ = decodedValue;
+    // TODO: Consider storing an enum to represent encoder
+    /** @private {function(!Writer, number, T)|undefined} */
+    this.encoder_ = encoder;
+  }
+
+  /**
+   * Adds a new IndexEntry.
+   * @param {!WireType} wireType
+   * @param {number} startIndex
+   */
+  addIndexEntry(wireType, startIndex) {
+    checkDefAndNotNull(this.indexArray_)
+        .push(Field.encodeIndexEntry(wireType, startIndex));
+  }
+
+  /**
+   * Returns the array of IndexEntry.
+   * @return {?Array<!IndexEntry>}
+   */
+  getIndexArray() {
+    return this.indexArray_;
+  }
+
+  /**
+   * Caches the decoded value and sets the write function to encode cache into
+   * binary bytes.
+   * @param {T} decodedValue
+   * @param {function(!Writer, number, T):void|undefined} encoder
+   */
+  setCache(decodedValue, encoder) {
+    this.decodedValue_ = decodedValue;
+    this.encoder_ = encoder;
+    this.maybeRemoveIndexArray_();
+  }
+
+  /**
+   * If the decoded value has been set.
+   * @return {boolean}
+   */
+  hasDecodedValue() {
+    return this.decodedValue_ !== undefined;
+  }
+
+  /**
+   * Returns the cached decoded value. The value needs to be set when this
+   * method is called.
+   * @return {T}
+   */
+  getDecodedValue() {
+    // Makes sure that the decoded value in the cache has already been set. This
+    // prevents callers from doing `if (field.getDecodedValue()) {...}` to check
+    // if a value exist in the cache, because the check might return false even
+    // if the cache has a valid value set (e.g. 0 or empty string).
+    checkState(this.decodedValue_ !== undefined);
+    return this.decodedValue_;
+  }
+
+  /**
+   * Returns the write function to encode cache into binary bytes.
+   * @return {function(!Writer, number, T)|undefined}
+   */
+  getEncoder() {
+    return this.encoder_;
+  }
+
+  /**
+   * Returns a copy of the field, containing the original index entries and a
+   * shallow copy of the cache.
+   * @return {!Field}
+   */
+  shallowCopy() {
+    // Repeated fields are arrays in the cache.
+    // We have to copy the array to make sure that modifications to a repeated
+    // field (e.g. add) are not seen on a cloned accessor.
+    const copiedCache = this.hasDecodedValue() ?
+        (Array.isArray(this.getDecodedValue()) ? [...this.getDecodedValue()] :
+                                                 this.getDecodedValue()) :
+        undefined;
+    return new Field(this.getIndexArray(), copiedCache, this.getEncoder());
+  }
+
+  /**
+   * @private
+   */
+  maybeRemoveIndexArray_() {
+    checkState(
+        this.encoder_ === undefined || this.decodedValue_ !== undefined,
+        'Encoder exists but decoded value doesn\'t');
+    if (this.encoder_ !== undefined) {
+      this.indexArray_ = null;
+    }
+  }
+}
+
+exports = {
+  IndexEntry,
+  Field,
+};

+ 36 - 0
js/experimental/runtime/kernel/fixed32_test_pairs.js

@@ -0,0 +1,36 @@
+/**
+ * @fileoverview Test data for float encoding and decoding.
+ */
+goog.module('protobuf.binary.fixed32TestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of float values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, intValue: number, bufferDecoder:
+ *     !BufferDecoder}>}
+ */
+function getFixed32Pairs() {
+  const fixed32Pairs = [
+    {
+      name: 'zero',
+      intValue: 0,
+      bufferDecoder: createBufferDecoder(0x00, 0x00, 0x00, 0x00),
+    },
+    {
+      name: 'one ',
+      intValue: 1,
+      bufferDecoder: createBufferDecoder(0x01, 0x00, 0x00, 0x00)
+    },
+    {
+      name: 'max int 2^32 -1',
+      intValue: Math.pow(2, 32) - 1,
+      bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF)
+    },
+  ];
+  return [...fixed32Pairs];
+}
+
+exports = {getFixed32Pairs};

+ 78 - 0
js/experimental/runtime/kernel/float_test_pairs.js

@@ -0,0 +1,78 @@
+/**
+ * @fileoverview Test data for float encoding and decoding.
+ */
+goog.module('protobuf.binary.floatTestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of float values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, floatValue:number, bufferDecoder:
+ *     !BufferDecoder}>}
+ */
+function getFloatPairs() {
+  const floatPairs = [
+    {
+      name: 'zero',
+      floatValue: 0,
+      bufferDecoder: createBufferDecoder(0x00, 0x00, 0x00, 0x00),
+    },
+    {
+      name: 'minus zero',
+      floatValue: -0,
+      bufferDecoder: createBufferDecoder(0x00, 0x00, 0x00, 0x80)
+    },
+    {
+      name: 'one ',
+      floatValue: 1,
+      bufferDecoder: createBufferDecoder(0x00, 0x00, 0x80, 0x3F)
+    },
+    {
+      name: 'minus one',
+      floatValue: -1,
+      bufferDecoder: createBufferDecoder(0x00, 0x00, 0x80, 0xBF)
+    },
+    {
+      name: 'two',
+      floatValue: 2,
+      bufferDecoder: createBufferDecoder(0x00, 0x00, 0x00, 0x40)
+    },
+    {
+      name: 'max float32',
+      floatValue: Math.pow(2, 127) * (2 - 1 / Math.pow(2, 23)),
+      bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0x7F, 0x7F)
+    },
+
+    {
+      name: 'min float32',
+      floatValue: 1 / Math.pow(2, 127 - 1),
+      bufferDecoder: createBufferDecoder(0x00, 0x00, 0x80, 0x00)
+    },
+
+    {
+      name: 'Infinity',
+      floatValue: Infinity,
+      bufferDecoder: createBufferDecoder(0x00, 0x00, 0x80, 0x7F)
+    },
+    {
+      name: 'minus Infinity',
+      floatValue: -Infinity,
+      bufferDecoder: createBufferDecoder(0x00, 0x00, 0x80, 0xFF)
+    },
+    {
+      name: '1.5',
+      floatValue: 1.5,
+      bufferDecoder: createBufferDecoder(0x00, 0x00, 0xC0, 0x3F)
+    },
+    {
+      name: '1.6',
+      floatValue: 1.6,
+      bufferDecoder: createBufferDecoder(0xCD, 0xCC, 0xCC, 0x3F)
+    },
+  ];
+  return [...floatPairs];
+}
+
+exports = {getFloatPairs};

+ 192 - 0
js/experimental/runtime/kernel/indexer.js

@@ -0,0 +1,192 @@
+/**
+ * @fileoverview Utilities to index a binary proto by fieldnumbers without
+ * relying on strutural proto information.
+ */
+goog.module('protobuf.binary.indexer');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const Storage = goog.require('protobuf.binary.Storage');
+const WireType = goog.require('protobuf.binary.WireType');
+const {Field} = goog.require('protobuf.binary.field');
+const {checkCriticalPositionIndex, checkCriticalState} = goog.require('protobuf.internal.checks');
+
+/**
+ * Appends a new entry in the index array for the given field number.
+ * @param {!Storage<!Field>} storage
+ * @param {number} fieldNumber
+ * @param {!WireType} wireType
+ * @param {number} startIndex
+ */
+function addIndexEntry(storage, fieldNumber, wireType, startIndex) {
+  const field = storage.get(fieldNumber);
+  if (field !== undefined) {
+    field.addIndexEntry(wireType, startIndex);
+  } else {
+    storage.set(fieldNumber, Field.fromFirstIndexEntry(wireType, startIndex));
+  }
+}
+
+/**
+ * Returns wire type stored in a tag.
+ * Protos store the wire type as the first 3 bit of a tag.
+ * @param {number} tag
+ * @return {!WireType}
+ */
+function tagToWireType(tag) {
+  return /** @type {!WireType} */ (tag & 0x07);
+}
+
+/**
+ * Returns the field number stored in a tag.
+ * Protos store the field number in the upper 29 bits of a 32 bit number.
+ * @param {number} tag
+ * @return {number}
+ */
+function tagToFieldNumber(tag) {
+  return tag >>> 3;
+}
+
+/**
+ * An Indexer that indexes a given binary protobuf by fieldnumber.
+ */
+class Indexer {
+  /**
+   * @param {!BufferDecoder} bufferDecoder
+   * @private
+   */
+  constructor(bufferDecoder) {
+    /** @private @const {!BufferDecoder} */
+    this.bufferDecoder_ = bufferDecoder;
+    /** @private {number} */
+    this.cursor_ = bufferDecoder.startIndex();
+  }
+
+  /**
+   * @param {number|undefined} pivot
+   * @return {!Storage<!Field>}
+   */
+  index(pivot) {
+    const storage = new Storage(pivot);
+    while (this.hasNextByte_()) {
+      const tag = this.readVarInt32_();
+      const wireType = tagToWireType(tag);
+      const fieldNumber = tagToFieldNumber(tag);
+      checkCriticalState(
+          fieldNumber > 0, `Invalid field number ${fieldNumber}`);
+
+      addIndexEntry(storage, fieldNumber, wireType, this.cursor_);
+
+      checkCriticalState(
+          !this.skipField_(wireType, fieldNumber),
+          'Found unmatched stop group.');
+    }
+    return storage;
+  }
+
+  /**
+   * Skips over fields until the next field of the message.
+   * @param {!WireType} wireType
+   * @param {number} fieldNumber
+   * @return {boolean} Whether the field we skipped over was a stop group.
+   * @private
+   */
+  skipField_(wireType, fieldNumber) {
+    switch (wireType) {
+      case WireType.VARINT:
+        this.cursor_ = this.bufferDecoder_.skipVarint(this.cursor_);
+        return false;
+      case WireType.FIXED64:
+        this.skip_(8);
+        return false;
+      case WireType.DELIMITED:
+        const length = this.readVarInt32_();
+        this.skip_(length);
+        return false;
+      case WireType.START_GROUP:
+        checkCriticalState(this.skipGroup_(fieldNumber), 'No end group found.');
+        return false;
+      case WireType.END_GROUP:
+        // Signal that we found a stop group to the caller
+        return true;
+      case WireType.FIXED32:
+        this.skip_(4);
+        return false;
+      default:
+        throw new Error(`Invalid wire type: ${wireType}`);
+    }
+  }
+
+  /**
+   * Seeks forward by the given amount.
+   * @param {number} skipAmount
+   * @private
+   */
+  skip_(skipAmount) {
+    this.cursor_ += skipAmount;
+    checkCriticalPositionIndex(this.cursor_, this.bufferDecoder_.endIndex());
+  }
+
+  /**
+   * Skips over fields until it finds the end of a given group.
+   * @param {number} groupFieldNumber
+   * @return {boolean} Returns true if an end was found.
+   * @private
+   */
+  skipGroup_(groupFieldNumber) {
+    // On a start group we need to keep skipping fields until we find a
+    // corresponding stop group
+    // Note: Since we are calling skipField from here nested groups will be
+    // handled by recursion of this method and thus we will not see a nested
+    // STOP GROUP here unless there is something wrong with the input data.
+    while (this.hasNextByte_()) {
+      const tag = this.readVarInt32_();
+      const wireType = tagToWireType(tag);
+      const fieldNumber = tagToFieldNumber(tag);
+
+      if (this.skipField_(wireType, fieldNumber)) {
+        checkCriticalState(
+            groupFieldNumber === fieldNumber,
+            `Expected stop group for fieldnumber ${
+                groupFieldNumber} not found.`);
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns a JS number for a 32 bit var int.
+   * @return {number}
+   * @private
+   */
+  readVarInt32_() {
+    const {lowBits, dataStart} = this.bufferDecoder_.getVarint(this.cursor_);
+    this.cursor_ = dataStart;
+    return lowBits;
+  }
+
+  /**
+   * Returns true if there are more bytes to read in the array.
+   * @return {boolean}
+   * @private
+   */
+  hasNextByte_() {
+    return this.cursor_ < this.bufferDecoder_.endIndex();
+  }
+}
+
+/**
+ * Creates an index of field locations in a given binary protobuf.
+ * @param {!BufferDecoder} bufferDecoder
+ * @param {number|undefined} pivot
+ * @return {!Storage<!Field>}
+ * @package
+ */
+function buildIndex(bufferDecoder, pivot) {
+  return new Indexer(bufferDecoder).index(pivot);
+}
+
+
+exports = {
+  buildIndex,
+};

+ 359 - 0
js/experimental/runtime/kernel/indexer_test.js

@@ -0,0 +1,359 @@
+/**
+ * @fileoverview Tests for indexer.js.
+ */
+goog.module('protobuf.binary.IndexerTest');
+
+goog.setTestOnly();
+
+// Note to the reader:
+// Since the index behavior changes with the checking level some of the tests
+// in this file have to know which checking level is enabled to make correct
+// assertions.
+// Test are run in all checking levels.
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const Storage = goog.require('protobuf.binary.Storage');
+const WireType = goog.require('protobuf.binary.WireType');
+const {CHECK_CRITICAL_STATE} = goog.require('protobuf.internal.checks');
+const {Field, IndexEntry} = goog.require('protobuf.binary.field');
+const {buildIndex} = goog.require('protobuf.binary.indexer');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * Returns the number of fields stored.
+ *
+ * @param {!Storage} storage
+ * @return {number}
+ */
+function getStorageSize(storage) {
+  let size = 0;
+  storage.forEach(() => void size++);
+  return size;
+}
+
+/**
+ * @type {number}
+ */
+const PIVOT = 1;
+
+/**
+ * Asserts a single IndexEntry at a given field number.
+ * @param {!Storage} storage
+ * @param {number} fieldNumber
+ * @param {...!IndexEntry} expectedEntries
+ */
+function assertStorageEntries(storage, fieldNumber, ...expectedEntries) {
+  expect(getStorageSize(storage)).toBe(1);
+
+  const entryArray = storage.get(fieldNumber).getIndexArray();
+  expect(entryArray).not.toBeUndefined();
+  expect(entryArray.length).toBe(expectedEntries.length);
+
+  for (let i = 0; i < entryArray.length; i++) {
+    const storageEntry = entryArray[i];
+    const expectedEntry = expectedEntries[i];
+
+    expect(storageEntry).toBe(expectedEntry);
+  }
+}
+
+describe('Indexer does', () => {
+  it('return empty storage for empty array', () => {
+    const storage = buildIndex(createBufferDecoder(), PIVOT);
+    expect(storage).not.toBeNull();
+    expect(getStorageSize(storage)).toBe(0);
+  });
+
+  it('throw for null array', () => {
+    expect(
+        () => buildIndex(
+            /** @type {!BufferDecoder} */ (/** @type {*} */ (null)), PIVOT))
+        .toThrow();
+  });
+
+  it('fail for invalid wire type (6)', () => {
+    expect(() => buildIndex(createBufferDecoder(0x0E, 0x01), PIVOT))
+        .toThrowError('Invalid wire type: 6');
+  });
+
+  it('fail for invalid wire type (7)', () => {
+    expect(() => buildIndex(createBufferDecoder(0x0F, 0x01), PIVOT))
+        .toThrowError('Invalid wire type: 7');
+  });
+
+  it('index varint', () => {
+    const data = createBufferDecoder(0x08, 0x01, 0x08, 0x01);
+    const storage = buildIndex(data, PIVOT);
+    assertStorageEntries(
+        storage, /* fieldNumber= */ 1,
+        Field.encodeIndexEntry(WireType.VARINT, /* startIndex= */ 1),
+        Field.encodeIndexEntry(WireType.VARINT, /* startIndex= */ 3));
+  });
+
+  it('index varint with two bytes field number', () => {
+    const data = createBufferDecoder(0xF8, 0x01, 0x01);
+    const storage = buildIndex(data, PIVOT);
+    assertStorageEntries(
+        storage, /* fieldNumber= */ 31,
+        Field.encodeIndexEntry(WireType.VARINT, /* startIndex= */ 2));
+  });
+
+  it('fail for varints that are longer than 10 bytes', () => {
+    const data = createBufferDecoder(
+        0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00);
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => buildIndex(data, PIVOT))
+          .toThrowError('Index out of bounds: index: 12 size: 11');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      const storage = buildIndex(data, PIVOT);
+      assertStorageEntries(
+          storage, /* fieldNumber= */ 1,
+          Field.encodeIndexEntry(WireType.VARINT, /* startIndex= */ 1));
+    }
+  });
+
+  it('fail for varints with no data', () => {
+    const data = createBufferDecoder(0x08);
+    expect(() => buildIndex(data, PIVOT)).toThrow();
+  });
+
+  it('index fixed64', () => {
+    const data = createBufferDecoder(
+        /* first= */ 0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+        /* second= */ 0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08);
+    const storage = buildIndex(data, PIVOT);
+    assertStorageEntries(
+        storage, /* fieldNumber= */ 1,
+        Field.encodeIndexEntry(WireType.FIXED64, /* startIndex= */ 1),
+        Field.encodeIndexEntry(WireType.FIXED64, /* startIndex= */ 10));
+  });
+
+  it('fail for fixed64 data missing in input', () => {
+    const data =
+        createBufferDecoder(0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07);
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => buildIndex(data, PIVOT))
+          .toThrowError('Index out of bounds: index: 9 size: 8');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      const storage = buildIndex(data, PIVOT);
+      assertStorageEntries(
+          storage, /* fieldNumber= */ 1,
+          Field.encodeIndexEntry(WireType.FIXED64, /* startIndex= */ 1));
+    }
+  });
+
+  it('fail for fixed64 tag that has no data after it', () => {
+    if (CHECK_CRITICAL_STATE) {
+      const data = createBufferDecoder(0x09);
+      expect(() => buildIndex(data, PIVOT))
+          .toThrowError('Index out of bounds: index: 9 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      const data = createBufferDecoder(0x09);
+      const storage = buildIndex(data, PIVOT);
+      assertStorageEntries(
+          storage, /* fieldNumber= */ 1,
+          Field.encodeIndexEntry(WireType.FIXED64, /* startIndex= */ 1));
+    }
+  });
+
+  it('index delimited', () => {
+    const data = createBufferDecoder(
+        /* first= */ 0x0A, 0x02, 0x00, 0x01, /* second= */ 0x0A, 0x02, 0x00,
+        0x01);
+    const storage = buildIndex(data, PIVOT);
+    assertStorageEntries(
+        storage, /* fieldNumber= */ 1,
+        Field.encodeIndexEntry(WireType.DELIMITED, /* startIndex= */ 1),
+        Field.encodeIndexEntry(WireType.DELIMITED, /* startIndex= */ 5));
+  });
+
+  it('fail for length deliimted field data missing in input', () => {
+    const data = createBufferDecoder(0x0A, 0x04, 0x00, 0x01);
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => buildIndex(data, PIVOT))
+          .toThrowError('Index out of bounds: index: 6 size: 4');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      const storage = buildIndex(data, PIVOT);
+      assertStorageEntries(
+          storage, /* fieldNumber= */ 1,
+          Field.encodeIndexEntry(WireType.DELIMITED, /* startIndex= */ 1));
+    }
+  });
+
+  it('fail for delimited tag that has no data after it', () => {
+    const data = createBufferDecoder(0x0A);
+    expect(() => buildIndex(data, PIVOT)).toThrow();
+  });
+
+  it('index fixed32', () => {
+    const data = createBufferDecoder(
+        /* first= */ 0x0D, 0x01, 0x02, 0x03, 0x04, /* second= */ 0x0D, 0x01,
+        0x02, 0x03, 0x04);
+    const storage = buildIndex(data, PIVOT);
+    assertStorageEntries(
+        storage, /* fieldNumber= */ 1,
+        Field.encodeIndexEntry(WireType.FIXED32, /* startIndex= */ 1),
+        Field.encodeIndexEntry(WireType.FIXED32, /* startIndex= */ 6));
+  });
+
+  it('fail for fixed32 data missing in input', () => {
+    const data = createBufferDecoder(0x0D, 0x01, 0x02, 0x03);
+
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => buildIndex(data, PIVOT))
+          .toThrowError('Index out of bounds: index: 5 size: 4');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      const storage = buildIndex(data, PIVOT);
+      assertStorageEntries(
+          storage, /* fieldNumber= */ 1,
+          Field.encodeIndexEntry(WireType.FIXED32, /* startIndex= */ 1));
+    }
+  });
+
+  it('fail for fixed32 tag that has no data after it', () => {
+    if (CHECK_CRITICAL_STATE) {
+      const data = createBufferDecoder(0x0D);
+      expect(() => buildIndex(data, PIVOT))
+          .toThrowError('Index out of bounds: index: 5 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      const data = createBufferDecoder(0x0D);
+      const storage = buildIndex(data, PIVOT);
+      assertStorageEntries(
+          storage, /* fieldNumber= */ 1,
+          Field.encodeIndexEntry(WireType.FIXED32, /* startIndex= */ 1));
+    }
+  });
+
+  it('index group', () => {
+    const data = createBufferDecoder(
+        /* first= */ 0x0B, 0x08, 0x01, 0x0C, /* second= */ 0x0B, 0x08, 0x01,
+        0x0C);
+    const storage = buildIndex(data, PIVOT);
+    assertStorageEntries(
+        storage, /* fieldNumber= */ 1,
+        Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 1),
+        Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 5));
+  });
+
+  it('index group and skips inner group', () => {
+    const data =
+        createBufferDecoder(0x0B, 0x0B, 0x08, 0x01, 0x0C, 0x08, 0x01, 0x0C);
+    const storage = buildIndex(data, PIVOT);
+    assertStorageEntries(
+        storage, /* fieldNumber= */ 1,
+        Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 1));
+  });
+
+  it('fail on unmatched stop group', () => {
+    const data = createBufferDecoder(0x0C, 0x01);
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => buildIndex(data, PIVOT))
+          .toThrowError('Found unmatched stop group.');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      const storage = buildIndex(data, PIVOT);
+
+      expect(getStorageSize(storage)).toBe(1);
+      const entryArray = storage.get(1).getIndexArray();
+      expect(entryArray).not.toBeUndefined();
+      expect(entryArray.length).toBe(1);
+      const entry = entryArray[0];
+
+      expect(Field.getWireType(entry)).toBe(WireType.END_GROUP);
+      expect(Field.getStartIndex(entry)).toBe(1);
+
+      const entryArray2 = storage.get(0).getIndexArray();
+      expect(entryArray2).not.toBeUndefined();
+      expect(entryArray2.length).toBe(1);
+      const entry2 = entryArray2[0];
+
+      expect(Field.getWireType(entry2)).toBe(WireType.FIXED64);
+      expect(Field.getStartIndex(entry2)).toBe(2);
+    }
+  });
+
+  it('fail for groups without matching stop group', () => {
+    const data = createBufferDecoder(0x0B, 0x08, 0x01, 0x1C);
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => buildIndex(data, PIVOT))
+          .toThrowError('Expected stop group for fieldnumber 1 not found.');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      const storage = buildIndex(data, PIVOT);
+      assertStorageEntries(
+          storage, /* fieldNumber= */ 1,
+          Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 1));
+    }
+  });
+
+  it('fail for groups without stop group', () => {
+    const data = createBufferDecoder(0x0B, 0x08, 0x01);
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => buildIndex(data, PIVOT)).toThrowError('No end group found.');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      const storage = buildIndex(data, PIVOT);
+      assertStorageEntries(
+          storage, /* fieldNumber= */ 1,
+          Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 1));
+    }
+  });
+
+  it('fail for group tag that has no data after it', () => {
+    const data = createBufferDecoder(0x0B);
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => buildIndex(data, PIVOT)).toThrowError('No end group found.');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      const storage = buildIndex(data, PIVOT);
+      assertStorageEntries(
+          storage, /* fieldNumber= */ 1,
+          Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 1));
+    }
+  });
+
+  it('index too large tag', () => {
+    const data = createBufferDecoder(0xF8, 0xFF, 0xFF, 0xFF, 0xFF);
+    expect(() => buildIndex(data, PIVOT)).toThrow();
+  });
+
+  it('fail for varint tag that has no data after it', () => {
+    const data = createBufferDecoder(0x08);
+    expect(() => buildIndex(data, PIVOT)).toThrow();
+  });
+});

+ 71 - 0
js/experimental/runtime/kernel/int32_test_pairs.js

@@ -0,0 +1,71 @@
+/**
+ * @fileoverview Test data for int32 encoding and decoding.
+ */
+goog.module('protobuf.binary.int32TestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of float values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, intValue:number, bufferDecoder:
+ *     !BufferDecoder, error: ?boolean, skip_writer: ?boolean}>}
+ */
+function getInt32Pairs() {
+  const int32Pairs = [
+    {
+      name: 'zero',
+      intValue: 0,
+      bufferDecoder: createBufferDecoder(0x00),
+    },
+    {
+      name: 'one ',
+      intValue: 1,
+      bufferDecoder: createBufferDecoder(0x01),
+    },
+    {
+      name: 'minus one',
+      intValue: -1,
+      bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0x0F),
+      // The writer will encode this with 64 bits, see below
+      skip_writer: true,
+    },
+    {
+      name: 'minus one (64bits)',
+      intValue: -1,
+      bufferDecoder: createBufferDecoder(
+          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01),
+    },
+    {
+      name: 'max signed int 2^31 - 1',
+      intValue: Math.pow(2, 31) - 1,
+      bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0x07),
+
+    },
+    {
+      name: 'min signed int -2^31',
+      intValue: -Math.pow(2, 31),
+      bufferDecoder: createBufferDecoder(0x80, 0x80, 0x80, 0x80, 0x08),
+      // The writer will encode this with 64 bits, see below
+      skip_writer: true,
+    },
+    {
+      name: 'value min signed int -2^31 (64 bit)',
+      intValue: -Math.pow(2, 31),
+      bufferDecoder: createBufferDecoder(
+          0x80, 0x80, 0x80, 0x80, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x01),
+    },
+    {
+      name: 'errors out for 11 bytes',
+      intValue: -1,
+      bufferDecoder: createBufferDecoder(
+          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF),
+      error: true,
+      skip_writer: true,
+    },
+  ];
+  return [...int32Pairs];
+}
+
+exports = {getInt32Pairs};

+ 59 - 0
js/experimental/runtime/kernel/int64_test_pairs.js

@@ -0,0 +1,59 @@
+/**
+ * @fileoverview Test data for int64 encoding and decoding.
+ */
+goog.module('protobuf.binary.int64TestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const Int64 = goog.require('protobuf.Int64');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of float values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, longValue: !Int64, bufferDecoder:
+ *     !BufferDecoder, error: ?boolean, skip_writer: ?boolean}>}
+ */
+function getInt64Pairs() {
+  const int64Pairs = [
+    {
+      name: 'zero',
+      longValue: Int64.fromInt(0),
+      bufferDecoder: createBufferDecoder(0x00),
+    },
+    {
+      name: 'one ',
+      longValue: Int64.fromInt(1),
+      bufferDecoder: createBufferDecoder(0x01),
+    },
+    {
+      name: 'minus one',
+      longValue: Int64.fromInt(-1),
+      bufferDecoder: createBufferDecoder(
+          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01),
+    },
+    {
+      name: 'max signed int 2^63 - 1',
+      longValue: Int64.fromBits(0xFFFFFFFF, 0x7FFFFFFF),
+      bufferDecoder: createBufferDecoder(
+          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F),
+
+    },
+    {
+      name: 'value min signed int -2^63 (64 bit)',
+      longValue: Int64.fromBits(0xFFFFFFFF, 0xFFFFFFFF),
+      bufferDecoder: createBufferDecoder(
+          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01),
+    },
+    {
+      name: 'errors out for 11 bytes',
+      longValue: Int64.fromInt(-1),
+      bufferDecoder: createBufferDecoder(
+          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF),
+      error: true,
+      skip_writer: true,
+    },
+  ];
+  return [...int64Pairs];
+}
+
+exports = {getInt64Pairs};

+ 24 - 0
js/experimental/runtime/kernel/internal_message.js

@@ -0,0 +1,24 @@
+/**
+ * @fileoverview Internal interface for messages implemented with the binary
+ * kernel.
+ */
+goog.module('protobuf.binary.InternalMessage');
+
+const LazyAccessor = goog.requireType('protobuf.binary.LazyAccessor');
+
+/**
+ * Interface that needs to be implemented by messages implemented with the
+ * binary kernel. This is an internal only interface and should be used only by
+ * the classes in binary kernel.
+ *
+ * @interface
+ */
+class InternalMessage {
+  /**
+   * @package
+   * @return {!LazyAccessor}
+   */
+  internalGetKernel() {}
+}
+
+exports = InternalMessage;

+ 3767 - 0
js/experimental/runtime/kernel/lazy_accessor.js

@@ -0,0 +1,3767 @@
+/**
+ * @fileoverview LazyAccessor is a class to provide type-checked accessing
+ * (read/write bool/int32/string/...) on binary data.
+ *
+ * When creating the LazyAccessor with the binary data, there is no deep
+ * decoding done (which requires full type information). The deep decoding is
+ * deferred until the first time accessing (when accessors can provide
+ * full type information).
+ *
+ * Because accessors can be statically analyzed and stripped, unlike eager
+ * binary decoding (which requires the full type information of all defined
+ * fields), LazyAccessor will only need the full type information of used
+ * fields.
+ */
+goog.module('protobuf.binary.LazyAccessor');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const ByteString = goog.require('protobuf.ByteString');
+const Int64 = goog.require('protobuf.Int64');
+const InternalMessage = goog.require('protobuf.binary.InternalMessage');
+const Storage = goog.require('protobuf.binary.Storage');
+const WireType = goog.require('protobuf.binary.WireType');
+const Writer = goog.require('protobuf.binary.Writer');
+const reader = goog.require('protobuf.binary.reader');
+const {CHECK_TYPE, checkCriticalElementIndex, checkCriticalState, checkCriticalType, checkCriticalTypeBool, checkCriticalTypeBoolArray, checkCriticalTypeByteString, checkCriticalTypeByteStringArray, checkCriticalTypeDouble, checkCriticalTypeDoubleArray, checkCriticalTypeFloat, checkCriticalTypeFloatIterable, checkCriticalTypeMessageArray, checkCriticalTypeSignedInt32, checkCriticalTypeSignedInt32Array, checkCriticalTypeSignedInt64, checkCriticalTypeSignedInt64Array, checkCriticalTypeString, checkCriticalTypeStringArray, checkCriticalTypeUnsignedInt32, checkCriticalTypeUnsignedInt32Array, checkDefAndNotNull, checkElementIndex, checkFieldNumber, checkFunctionExists, checkState, checkTypeDouble, checkTypeFloat, checkTypeSignedInt32, checkTypeSignedInt64, checkTypeUnsignedInt32} = goog.require('protobuf.internal.checks');
+const {Field, IndexEntry} = goog.require('protobuf.binary.field');
+const {buildIndex} = goog.require('protobuf.binary.indexer');
+
+
+/**
+ * Validates the index entry has the correct wire type.
+ * @param {!IndexEntry} indexEntry
+ * @param {!WireType} expected
+ */
+function validateWireType(indexEntry, expected) {
+  const wireType = Field.getWireType(indexEntry);
+  checkCriticalState(
+      wireType === expected,
+      `Expected wire type: ${expected} but found: ${wireType}`);
+}
+
+/**
+ * Checks if the object implements InternalMessage interface.
+ * @param {?} obj
+ * @return {!InternalMessage}
+ */
+function checkIsInternalMessage(obj) {
+  const message = /** @type {!InternalMessage} */ (obj);
+  checkFunctionExists(message.internalGetKernel);
+  return message;
+}
+
+/**
+ * Checks if the instanceCreator returns an instance that implements the
+ * InternalMessage interface.
+ * @param {function(!LazyAccessor):T} instanceCreator
+ * @template T
+ */
+function checkInstanceCreator(instanceCreator) {
+  if (CHECK_TYPE) {
+    const emptyMessage = instanceCreator(LazyAccessor.createEmpty());
+    checkFunctionExists(emptyMessage.internalGetKernel);
+  }
+}
+
+/**
+ * Reads the last entry of the index array using the given read function.
+ * This is used to implement parsing singular primitive fields.
+ * @param {!Array<!IndexEntry>} indexArray
+ * @param {!BufferDecoder} bufferDecoder
+ * @param {function(!BufferDecoder, number):T} readFunc
+ * @param {!WireType} wireType
+ * @return {T}
+ * @template T
+ */
+function readOptional(indexArray, bufferDecoder, readFunc, wireType) {
+  const index = indexArray.length - 1;
+  checkElementIndex(index, indexArray.length);
+  const indexEntry = indexArray[index];
+  validateWireType(indexEntry, wireType);
+  return readFunc(bufferDecoder, Field.getStartIndex(indexEntry));
+}
+
+/**
+ * Converts all entries of the index array to the template type using given read
+ * methods and return an Iterable containing those converted values.
+ * Primitive repeated fields may be encoded either packed or unpacked. Thus, two
+ * read methods are needed for those two cases.
+ * This is used to implement parsing repeated primitive fields.
+ * @param {!Array<!IndexEntry>} indexArray
+ * @param {!BufferDecoder} bufferDecoder
+ * @param {function(!BufferDecoder, number):T} singularReadFunc
+ * @param {function(!BufferDecoder, number):!Array<T>} packedReadFunc
+ * @param {!WireType} expectedWireType
+ * @return {!Array<T>}
+ * @template T
+ */
+function readRepeatedPrimitive(
+    indexArray, bufferDecoder, singularReadFunc, packedReadFunc,
+    expectedWireType) {
+  // Fast path when there is a single packed entry.
+  if (indexArray.length === 1 &&
+      Field.getWireType(indexArray[0]) === WireType.DELIMITED) {
+    return packedReadFunc(bufferDecoder, Field.getStartIndex(indexArray[0]));
+  }
+
+  let /** !Array<T> */ result = [];
+  for (const indexEntry of indexArray) {
+    const wireType = Field.getWireType(indexEntry);
+    const startIndex = Field.getStartIndex(indexEntry);
+    if (wireType === WireType.DELIMITED) {
+      result = result.concat(packedReadFunc(bufferDecoder, startIndex));
+    } else {
+      validateWireType(indexEntry, expectedWireType);
+      result.push(singularReadFunc(bufferDecoder, startIndex));
+    }
+  }
+  return result;
+}
+
+/**
+ * Converts all entries of the index array to the template type using the given
+ * read function and return an Array containing those converted values. This is
+ * used to implement parsing repeated non-primitive fields.
+ * @param {!Array<!IndexEntry>} indexArray
+ * @param {!BufferDecoder} bufferDecoder
+ * @param {function(!BufferDecoder, number):T} singularReadFunc
+ * @return {!Array<T>}
+ * @template T
+ */
+function readRepeatedNonPrimitive(indexArray, bufferDecoder, singularReadFunc) {
+  const result = new Array(indexArray.length);
+  for (let i = 0; i < indexArray.length; i++) {
+    validateWireType(indexArray[i], WireType.DELIMITED);
+    result[i] =
+        singularReadFunc(bufferDecoder, Field.getStartIndex(indexArray[i]));
+  }
+  return result;
+}
+
+/**
+ * Creates a new bytes array to contain all data of a submessage.
+ * When there are multiple entries, merge them together.
+ * @param {!Array<!IndexEntry>} indexArray
+ * @param {!BufferDecoder} bufferDecoder
+ * @return {!BufferDecoder}
+ */
+function mergeMessageArrays(indexArray, bufferDecoder) {
+  const dataArrays = indexArray.map(
+      indexEntry =>
+          reader.readDelimited(bufferDecoder, Field.getStartIndex(indexEntry)));
+  return BufferDecoder.merge(dataArrays);
+}
+
+/**
+ * @param {!Array<!IndexEntry>} indexArray
+ * @param {!BufferDecoder} bufferDecoder
+ * @param {number=} pivot
+ * @return {!LazyAccessor}
+ */
+function readAccessor(indexArray, bufferDecoder, pivot = undefined) {
+  checkState(indexArray.length > 0);
+  let accessorBuffer;
+  // Faster access for one member.
+  if (indexArray.length === 1) {
+    const indexEntry = indexArray[0];
+    validateWireType(indexEntry, WireType.DELIMITED);
+    accessorBuffer =
+        reader.readDelimited(bufferDecoder, Field.getStartIndex(indexEntry));
+  } else {
+    indexArray.forEach(indexEntry => {
+      validateWireType(indexEntry, WireType.DELIMITED);
+    });
+    accessorBuffer = mergeMessageArrays(indexArray, bufferDecoder);
+  }
+  return LazyAccessor.fromBufferDecoder_(accessorBuffer, pivot);
+}
+
+/**
+ * Merges all index entries of the index array using the given read function.
+ * This is used to implement parsing singular message fields.
+ * @param {!Array<!IndexEntry>} indexArray
+ * @param {!BufferDecoder} bufferDecoder
+ * @param {function(!LazyAccessor):T} instanceCreator
+ * @param {number=} pivot
+ * @return {T}
+ * @template T
+ */
+function readMessage(indexArray, bufferDecoder, instanceCreator, pivot) {
+  checkInstanceCreator(instanceCreator);
+  const accessor = readAccessor(indexArray, bufferDecoder, pivot);
+  return instanceCreator(accessor);
+}
+
+/**
+ * @param {!Writer} writer
+ * @param {number} fieldNumber
+ * @param {?InternalMessage} value
+ */
+function writeMessage(writer, fieldNumber, value) {
+  writer.writeDelimited(
+      fieldNumber, checkDefAndNotNull(value).internalGetKernel().serialize());
+}
+
+/**
+ * Writes the array of Messages into the writer for the given field number.
+ * @param {!Writer} writer
+ * @param {number} fieldNumber
+ * @param {!Iterable<!InternalMessage>} values
+ */
+function writeRepeatedMessage(writer, fieldNumber, values) {
+  for (const value of values) {
+    writeMessage(writer, fieldNumber, value);
+  }
+}
+
+/**
+ * Array.from has a weird type definition in google3/javascript/externs/es6.js
+ * and wants the mapping function accept strings.
+ * @const {function((string|number)): number}
+ */
+const fround = /** @type {function((string|number)): number} */ (Math.fround);
+
+/**
+ * Wraps an array and exposes it as an Iterable. This class is used to provide
+ * immutable access of the array to the caller.
+ * @implements {Iterable<T>}
+ * @template T
+ */
+class ArrayIterable {
+  /**
+   * @param {!Array<T>} array
+   */
+  constructor(array) {
+    /** @private @const {!Array<T>} */
+    this.array_ = array;
+  }
+
+  /** @return {!Iterator<T>} */
+  [Symbol.iterator]() {
+    return this.array_[Symbol.iterator]();
+  }
+}
+
+/**
+ * Accesses protobuf fields on binary format data. Binary data is decoded lazily
+ * at the first access.
+ * @final
+ */
+class LazyAccessor {
+  /**
+   * Create a LazyAccessor for the given binary bytes.
+   * The bytes array is kept by the LazyAccessor. DON'T MODIFY IT.
+   * @param {!ArrayBuffer} arrayBuffer Binary bytes.
+   * @param {number=} pivot Fields with a field number no greater than the pivot
+   *     value will be stored in an array for fast access. Other fields will be
+   *     stored in a map. A higher pivot value can improve runtime performance
+   *     at the expense of requiring more memory. It's recommended to set the
+   *     value to the max field number of the message unless the field numbers
+   *     are too sparse. If the value is not set, a default value specified in
+   *     storage.js will be used.
+   * @return {!LazyAccessor}
+   */
+  static fromArrayBuffer(arrayBuffer, pivot = undefined) {
+    const bufferDecoder = BufferDecoder.fromArrayBuffer(arrayBuffer);
+    return LazyAccessor.fromBufferDecoder_(bufferDecoder, pivot);
+  }
+
+  /**
+   * Creates an empty LazyAccessor.
+   * @param {number=} pivot Fields with a field number no greater than the pivot
+   *     value will be stored in an array for fast access. Other fields will be
+   *     stored in a map. A higher pivot value can improve runtime performance
+   *     at the expense of requiring more memory. It's recommended to set the
+   *     value to the max field number of the message unless the field numbers
+   *     are too sparse. If the value is not set, a default value specified in
+   *     storage.js will be used.
+   * @return {!LazyAccessor}
+   */
+  static createEmpty(pivot = undefined) {
+    return new LazyAccessor(/* bufferDecoder= */ null, new Storage(pivot));
+  }
+
+  /**
+   * Create a LazyAccessor for the given binary bytes.
+   * The bytes array is kept by the LazyAccessor. DON'T MODIFY IT.
+   * @param {!BufferDecoder} bufferDecoder Binary bytes.
+   * @param {number|undefined} pivot
+   * @return {!LazyAccessor}
+   * @private
+   */
+  static fromBufferDecoder_(bufferDecoder, pivot) {
+    return new LazyAccessor(bufferDecoder, buildIndex(bufferDecoder, pivot));
+  }
+
+  /**
+   * @param {?BufferDecoder} bufferDecoder Binary bytes. Accessor treats the
+   *     bytes as immutable and will never attempt to write to it.
+   * @param {!Storage<!Field>} fields A map of field number to Field. The
+   *     IndexEntry in each Field needs to be populated with the location of the
+   *     field in the binary data.
+   * @private
+   */
+  constructor(bufferDecoder, fields) {
+    /** @private @const {?BufferDecoder} */
+    this.bufferDecoder_ = bufferDecoder;
+    /** @private @const {!Storage<!Field>} */
+    this.fields_ = fields;
+  }
+
+  /**
+   * Creates a shallow copy of the accessor.
+   * @return {!LazyAccessor}
+   */
+  shallowCopy() {
+    return new LazyAccessor(this.bufferDecoder_, this.fields_.shallowCopy());
+  }
+
+  /**
+   * See definition of the pivot parameter on the fromArrayBuffer() method.
+   * @return {number}
+   */
+  getPivot() {
+    return this.fields_.getPivot();
+  }
+
+  /**
+   * Clears the field for the given field number.
+   * @param {number} fieldNumber
+   */
+  clearField(fieldNumber) {
+    this.fields_.delete(fieldNumber);
+  }
+
+  /**
+   * Returns data for a field specified by the given field number. Also cache
+   * the data if it doesn't already exist in the cache. When no data is
+   * available, return the given default value.
+   * @param {number} fieldNumber
+   * @param {?T} defaultValue
+   * @param {function(!Array<!IndexEntry>, !BufferDecoder):T} readFunc
+   * @param {function(!Writer, number, T)=} encoder
+   * @return {T}
+   * @template T
+   * @private
+   */
+  getFieldWithDefault_(
+      fieldNumber, defaultValue, readFunc, encoder = undefined) {
+    checkFieldNumber(fieldNumber);
+
+    const field = this.fields_.get(fieldNumber);
+    if (field === undefined) {
+      return defaultValue;
+    }
+
+    if (field.hasDecodedValue()) {
+      checkState(!encoder || !!field.getEncoder());
+      return field.getDecodedValue();
+    }
+
+    const parsed = readFunc(
+        checkDefAndNotNull(field.getIndexArray()),
+        checkDefAndNotNull(this.bufferDecoder_));
+    field.setCache(parsed, encoder);
+    return parsed;
+  }
+
+  /**
+   * Sets data for a singular field specified by the given field number.
+   * @param {number} fieldNumber
+   * @param {T} value
+   * @param {function(!Writer, number, T)} encoder
+   * @return {T}
+   * @template T
+   * @private
+   */
+  setField_(fieldNumber, value, encoder) {
+    checkFieldNumber(fieldNumber);
+    this.fields_.set(fieldNumber, Field.fromDecodedValue(value, encoder));
+  }
+
+  /**
+   * Serializes internal contents to binary format bytes array to the
+   * given writer.
+   * @param {!Writer} writer
+   * @package
+   */
+  serializeToWriter(writer) {
+    // If we use for...of here, jscompiler returns an array of both types for
+    // fieldNumber and field without specifying which type is for
+    // field, which prevents us to use fieldNumber. Thus, we use
+    // forEach here.
+    this.fields_.forEach((field, fieldNumber) => {
+      // If encoder doesn't exist, there is no need to encode the value
+      // because the data in the index is still valid.
+      if (field.getEncoder() !== undefined) {
+        const encoder = checkDefAndNotNull(field.getEncoder());
+        encoder(writer, fieldNumber, field.getDecodedValue());
+        return;
+      }
+
+      const indexArr = field.getIndexArray();
+      if (indexArr) {
+        for (const indexEntry of indexArr) {
+          writer.writeTag(fieldNumber, Field.getWireType(indexEntry));
+          writer.writeBufferDecoder(
+              checkDefAndNotNull(this.bufferDecoder_),
+              Field.getStartIndex(indexEntry), Field.getWireType(indexEntry));
+        }
+      }
+    });
+  }
+
+  /**
+   * Serializes internal contents to binary format bytes array.
+   * @return {!ArrayBuffer}
+   */
+  serialize() {
+    const writer = new Writer();
+    this.serializeToWriter(writer);
+    return writer.getAndResetResultBuffer();
+  }
+
+  /**
+   * Returns whether data exists at the given field number.
+   * @param {number} fieldNumber
+   * @return {boolean}
+   */
+  hasFieldNumber(fieldNumber) {
+    checkFieldNumber(fieldNumber);
+    const field = this.fields_.get(fieldNumber);
+
+    if (field === undefined) {
+      return false;
+    }
+
+    if (field.getIndexArray() !== null) {
+      return true;
+    }
+
+    if (Array.isArray(field.getDecodedValue())) {
+      // For repeated fields, existence is decided by number of elements.
+      return (/** !Array<?> */ (field.getDecodedValue())).length > 0;
+    }
+    return true;
+  }
+
+  /***************************************************************************
+   *                        OPTIONAL GETTER METHODS
+   ***************************************************************************/
+
+  /**
+   * Returns data as boolean for the given field number.
+   * If no default is given, use false as the default.
+   * @param {number} fieldNumber
+   * @param {boolean=} defaultValue
+   * @return {boolean}
+   */
+  getBoolWithDefault(fieldNumber, defaultValue = false) {
+    return this.getFieldWithDefault_(
+        fieldNumber, defaultValue,
+        (indexArray, bytes) =>
+            readOptional(indexArray, bytes, reader.readBool, WireType.VARINT));
+  }
+
+  /**
+   * Returns data as a ByteString for the given field number.
+   * If no default is given, use false as the default.
+   * @param {number} fieldNumber
+   * @param {!ByteString=} defaultValue
+   * @return {!ByteString}
+   */
+  getBytesWithDefault(fieldNumber, defaultValue = ByteString.EMPTY) {
+    return this.getFieldWithDefault_(
+        fieldNumber, defaultValue,
+        (indexArray, bytes) => readOptional(
+            indexArray, bytes, reader.readBytes, WireType.DELIMITED));
+  }
+
+  /**
+   * Returns a double for the given field number.
+   * If no default is given uses zero as the default.
+   * @param {number} fieldNumber
+   * @param {number=} defaultValue
+   * @return {number}
+   */
+  getDoubleWithDefault(fieldNumber, defaultValue = 0) {
+    checkTypeDouble(defaultValue);
+    return this.getFieldWithDefault_(
+        fieldNumber, defaultValue,
+        (indexArray, bytes) => readOptional(
+            indexArray, bytes, reader.readDouble, WireType.FIXED64));
+  }
+
+  /**
+   * Returns a fixed32 for the given field number.
+   * If no default is given zero as the default.
+   * @param {number} fieldNumber
+   * @param {number=} defaultValue
+   * @return {number}
+   */
+  getFixed32WithDefault(fieldNumber, defaultValue = 0) {
+    checkTypeUnsignedInt32(defaultValue);
+    return this.getFieldWithDefault_(
+        fieldNumber, defaultValue,
+        (indexArray, bytes) => readOptional(
+            indexArray, bytes, reader.readFixed32, WireType.FIXED32));
+  }
+
+  /**
+   * Returns a fixed64 for the given field number.
+   * Note: Since g.m.Long does not support unsigned int64 values we are going
+   * the Java route here for now and simply output the number as a signed int64.
+   * Users can get to individual bits by themselves.
+   * @param {number} fieldNumber
+   * @param {!Int64=} defaultValue
+   * @return {!Int64}
+   */
+  getFixed64WithDefault(fieldNumber, defaultValue = Int64.getZero()) {
+    return this.getSfixed64WithDefault(fieldNumber, defaultValue);
+  }
+
+  /**
+   * Returns a float for the given field number.
+   * If no default is given zero as the default.
+   * @param {number} fieldNumber
+   * @param {number=} defaultValue
+   * @return {number}
+   */
+  getFloatWithDefault(fieldNumber, defaultValue = 0) {
+    checkTypeFloat(defaultValue);
+    return this.getFieldWithDefault_(
+        fieldNumber, defaultValue,
+        (indexArray, bytes) => readOptional(
+            indexArray, bytes, reader.readFloat, WireType.FIXED32));
+  }
+
+  /**
+   * Returns a int32 for the given field number.
+   * If no default is given zero as the default.
+   * @param {number} fieldNumber
+   * @param {number=} defaultValue
+   * @return {number}
+   */
+  getInt32WithDefault(fieldNumber, defaultValue = 0) {
+    checkTypeSignedInt32(defaultValue);
+    return this.getFieldWithDefault_(
+        fieldNumber, defaultValue,
+        (indexArray, bytes) =>
+            readOptional(indexArray, bytes, reader.readInt32, WireType.VARINT));
+  }
+
+  /**
+   * Returns a int64 for the given field number.
+   * If no default is given zero as the default.
+   * @param {number} fieldNumber
+   * @param {!Int64=} defaultValue
+   * @return {!Int64}
+   */
+  getInt64WithDefault(fieldNumber, defaultValue = Int64.getZero()) {
+    checkTypeSignedInt64(defaultValue);
+    return this.getFieldWithDefault_(
+        fieldNumber, defaultValue,
+        (indexArray, bytes) =>
+            readOptional(indexArray, bytes, reader.readInt64, WireType.VARINT));
+  }
+
+  /**
+   * Returns a sfixed32 for the given field number.
+   * If no default is given zero as the default.
+   * @param {number} fieldNumber
+   * @param {number=} defaultValue
+   * @return {number}
+   */
+  getSfixed32WithDefault(fieldNumber, defaultValue = 0) {
+    checkTypeSignedInt32(defaultValue);
+    return this.getFieldWithDefault_(
+        fieldNumber, defaultValue,
+        (indexArray, bytes) => readOptional(
+            indexArray, bytes, reader.readSfixed32, WireType.FIXED32));
+  }
+
+  /**
+   * Returns a sfixed64 for the given field number.
+   * If no default is given zero as the default.
+   * @param {number} fieldNumber
+   * @param {!Int64=} defaultValue
+   * @return {!Int64}
+   */
+  getSfixed64WithDefault(fieldNumber, defaultValue = Int64.getZero()) {
+    checkTypeSignedInt64(defaultValue);
+    return this.getFieldWithDefault_(
+        fieldNumber, defaultValue,
+        (indexArray, bytes) => readOptional(
+            indexArray, bytes, reader.readSfixed64, WireType.FIXED64));
+  }
+
+  /**
+   * Returns a sint32 for the given field number.
+   * If no default is given zero as the default.
+   * @param {number} fieldNumber
+   * @param {number=} defaultValue
+   * @return {number}
+   */
+  getSint32WithDefault(fieldNumber, defaultValue = 0) {
+    checkTypeSignedInt32(defaultValue);
+    return this.getFieldWithDefault_(
+        fieldNumber, defaultValue,
+        (indexArray, bytes) => readOptional(
+            indexArray, bytes, reader.readSint32, WireType.VARINT));
+  }
+
+  /**
+   * Returns a sint64 for the given field number.
+   * If no default is given zero as the default.
+   * @param {number} fieldNumber
+   * @param {!Int64=} defaultValue
+   * @return {!Int64}
+   */
+  getSint64WithDefault(fieldNumber, defaultValue = Int64.getZero()) {
+    checkTypeSignedInt64(defaultValue);
+    return this.getFieldWithDefault_(
+        fieldNumber, defaultValue,
+        (indexArray, bytes) => readOptional(
+            indexArray, bytes, reader.readSint64, WireType.VARINT));
+  }
+
+  /**
+   * Returns a string for the given field number.
+   * If no default is given uses empty string as the default.
+   * @param {number} fieldNumber
+   * @param {string=} defaultValue
+   * @return {string}
+   */
+  getStringWithDefault(fieldNumber, defaultValue = '') {
+    return this.getFieldWithDefault_(
+        fieldNumber, defaultValue,
+        (indexArray, bytes) => readOptional(
+            indexArray, bytes, reader.readString, WireType.DELIMITED));
+  }
+
+  /**
+   * Returns a uint32 for the given field number.
+   * If no default is given zero as the default.
+   * @param {number} fieldNumber
+   * @param {number=} defaultValue
+   * @return {number}
+   */
+  getUint32WithDefault(fieldNumber, defaultValue = 0) {
+    checkTypeUnsignedInt32(defaultValue);
+    return this.getFieldWithDefault_(
+        fieldNumber, defaultValue,
+        (indexArray, bytes) => readOptional(
+            indexArray, bytes, reader.readUint32, WireType.VARINT));
+  }
+
+  /**
+   * Returns a uint64 for the given field number.
+   * Note: Since g.m.Long does not support unsigned int64 values we are going
+   * the Java route here for now and simply output the number as a signed int64.
+   * Users can get to individual bits by themselves.
+   * @param {number} fieldNumber
+   * @param {!Int64=} defaultValue
+   * @return {!Int64}
+   */
+  getUint64WithDefault(fieldNumber, defaultValue = Int64.getZero()) {
+    return this.getInt64WithDefault(fieldNumber, defaultValue);
+  }
+
+  /**
+   * Returns data as a mutable proto Message for the given field number.
+   * If no value has been set, return null.
+   * If hasFieldNumber(fieldNumber) == false before calling, it remains false.
+   *
+   * This method should not be used along with getMessage, since calling
+   * getMessageOrNull after getMessage will not register the encoder.
+   *
+   * @param {number} fieldNumber
+   * @param {function(!LazyAccessor):T} instanceCreator
+   * @param {number=} pivot
+   * @return {?T}
+   * @template T
+   */
+  getMessageOrNull(fieldNumber, instanceCreator, pivot = undefined) {
+    return this.getFieldWithDefault_(
+        fieldNumber, null,
+        (indexArray, bytes) =>
+            readMessage(indexArray, bytes, instanceCreator, pivot),
+        writeMessage);
+  }
+
+  /**
+   * Returns data as a mutable proto Message for the given field number.
+   * If no value has been set previously, creates and attaches an instance.
+   * Postcondition: hasFieldNumber(fieldNumber) == true.
+   *
+   * This method should not be used along with getMessage, since calling
+   * getMessageAttach after getMessage will not register the encoder.
+   *
+   * @param {number} fieldNumber
+   * @param {function(!LazyAccessor):T} instanceCreator
+   * @param {number=} pivot
+   * @return {T}
+   * @template T
+   */
+  getMessageAttach(fieldNumber, instanceCreator, pivot = undefined) {
+    checkInstanceCreator(instanceCreator);
+    let instance = this.getMessageOrNull(fieldNumber, instanceCreator, pivot);
+    if (!instance) {
+      instance = instanceCreator(LazyAccessor.createEmpty());
+      this.setField_(fieldNumber, instance, writeMessage);
+    }
+    return instance;
+  }
+
+  /**
+   * Returns data as a proto Message for the given field number.
+   * If no value has been set, return a default instance.
+   * This default instance is guaranteed to be the same instance, unless this
+   * field is cleared.
+   * Does not register the encoder, so changes made to the returned
+   * sub-message will not be included when serializing the parent message.
+   * Use getMessageAttach() if the resulting sub-message should be mutable.
+   *
+   * This method should not be used along with getMessageOrNull or
+   * getMessageAttach, since these methods register the encoder.
+   *
+   * @param {number} fieldNumber
+   * @param {function(!LazyAccessor):T} instanceCreator
+   * @param {number=} pivot
+   * @return {T}
+   * @template T
+   */
+  getMessage(fieldNumber, instanceCreator, pivot = undefined) {
+    checkInstanceCreator(instanceCreator);
+    const message = this.getFieldWithDefault_(
+        fieldNumber, null,
+        (indexArray, bytes) =>
+            readMessage(indexArray, bytes, instanceCreator, pivot));
+    // Returns an empty message as the default value if the field doesn't exist.
+    // We don't pass the default value to getFieldWithDefault_ to reduce object
+    // allocation.
+    return message === null ? instanceCreator(LazyAccessor.createEmpty()) :
+                              message;
+  }
+
+  /**
+   * Returns the accessor for the given singular message, or returns null if
+   * it hasn't been set.
+   * @param {number} fieldNumber
+   * @param {number=} pivot
+   * @return {?LazyAccessor}
+   */
+  getMessageAccessorOrNull(fieldNumber, pivot = undefined) {
+    checkFieldNumber(fieldNumber);
+    const field = this.fields_.get(fieldNumber);
+    if (field === undefined) {
+      return null;
+    }
+
+    if (field.hasDecodedValue()) {
+      return checkIsInternalMessage(field.getDecodedValue())
+          .internalGetKernel();
+    } else {
+      return readAccessor(
+          checkDefAndNotNull(field.getIndexArray()),
+          checkDefAndNotNull(this.bufferDecoder_), pivot);
+    }
+  }
+
+  /***************************************************************************
+   *                        REPEATED GETTER METHODS
+   ***************************************************************************/
+
+  /* Bool */
+
+  /**
+   * Returns an Array instance containing boolean values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Array<boolean>}
+   * @private
+   */
+  getRepeatedBoolArray_(fieldNumber) {
+    return this.getFieldWithDefault_(
+        fieldNumber, /* defaultValue= */[],
+        (indexArray, bytes) => readRepeatedPrimitive(
+            indexArray, bytes, reader.readBool, reader.readPackedBool,
+            WireType.VARINT));
+  }
+
+  /**
+   * Returns the element at index for the given field number.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {boolean}
+   */
+  getRepeatedBoolElement(fieldNumber, index) {
+    const array = this.getRepeatedBoolArray_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    return array[index];
+  }
+
+  /**
+   * Returns an Iterable instance containing boolean values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Iterable<boolean>}
+   */
+  getRepeatedBoolIterable(fieldNumber) {
+    const array = this.getRepeatedBoolArray_(fieldNumber);
+    return new ArrayIterable(array);
+  }
+
+  /**
+   * Returns the size of the repeated field.
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedBoolSize(fieldNumber) {
+    return this.getRepeatedBoolArray_(fieldNumber).length;
+  }
+
+  /* Double */
+
+  /**
+   * Returns an Array instance containing double values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Array<number>}
+   * @private
+   */
+  getRepeatedDoubleArray_(fieldNumber) {
+    return this.getFieldWithDefault_(
+        fieldNumber, /* defaultValue= */[],
+        (indexArray, bytes) => readRepeatedPrimitive(
+            indexArray, bytes, reader.readDouble, reader.readPackedDouble,
+            WireType.FIXED64));
+  }
+
+  /**
+   * Returns the element at index for the given field number.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {number}
+   */
+  getRepeatedDoubleElement(fieldNumber, index) {
+    const array = this.getRepeatedDoubleArray_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    return array[index];
+  }
+
+  /**
+   * Returns an Iterable instance containing double values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Iterable<number>}
+   */
+  getRepeatedDoubleIterable(fieldNumber) {
+    const array = this.getRepeatedDoubleArray_(fieldNumber);
+    return new ArrayIterable(array);
+  }
+
+  /**
+   * Returns the size of the repeated field.
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedDoubleSize(fieldNumber) {
+    return this.getRepeatedDoubleArray_(fieldNumber).length;
+  }
+
+  /* Fixed32 */
+
+  /**
+   * Returns an Array instance containing fixed32 values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Array<number>}
+   * @private
+   */
+  getRepeatedFixed32Array_(fieldNumber) {
+    return this.getFieldWithDefault_(
+        fieldNumber, /* defaultValue= */[],
+        (indexArray, bytes) => readRepeatedPrimitive(
+            indexArray, bytes, reader.readFixed32, reader.readPackedFixed32,
+            WireType.FIXED32));
+  }
+
+  /**
+   * Returns the element at index for the given field number.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {number}
+   */
+  getRepeatedFixed32Element(fieldNumber, index) {
+    const array = this.getRepeatedFixed32Array_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    return array[index];
+  }
+
+  /**
+   * Returns an Iterable instance containing fixed32 values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Iterable<number>}
+   */
+  getRepeatedFixed32Iterable(fieldNumber) {
+    const array = this.getRepeatedFixed32Array_(fieldNumber);
+    return new ArrayIterable(array);
+  }
+
+  /**
+   * Returns the size of the repeated field.
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedFixed32Size(fieldNumber) {
+    return this.getRepeatedFixed32Array_(fieldNumber).length;
+  }
+
+  /* Fixed64 */
+
+  /**
+   * Returns the element at index for the given field number.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {!Int64}
+   */
+  getRepeatedFixed64Element(fieldNumber, index) {
+    return this.getRepeatedSfixed64Element(fieldNumber, index);
+  }
+
+  /**
+   * Returns an Iterable instance containing fixed64 values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Iterable<!Int64>}
+   */
+  getRepeatedFixed64Iterable(fieldNumber) {
+    return this.getRepeatedSfixed64Iterable(fieldNumber);
+  }
+
+  /**
+   * Returns the size of the repeated field.
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedFixed64Size(fieldNumber) {
+    return this.getRepeatedSfixed64Size(fieldNumber);
+  }
+
+  /* Float */
+
+  /**
+   * Returns an Array instance containing float values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Array<number>}
+   * @private
+   */
+  getRepeatedFloatArray_(fieldNumber) {
+    return this.getFieldWithDefault_(
+        fieldNumber, /* defaultValue= */[],
+        (indexArray, bytes) => readRepeatedPrimitive(
+            indexArray, bytes, reader.readFloat, reader.readPackedFloat,
+            WireType.FIXED32));
+  }
+
+  /**
+   * Returns the element at index for the given field number.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {number}
+   */
+  getRepeatedFloatElement(fieldNumber, index) {
+    const array = this.getRepeatedFloatArray_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    return array[index];
+  }
+
+  /**
+   * Returns an Iterable instance containing float values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Iterable<number>}
+   */
+  getRepeatedFloatIterable(fieldNumber) {
+    const array = this.getRepeatedFloatArray_(fieldNumber);
+    return new ArrayIterable(array);
+  }
+
+  /**
+   * Returns the size of the repeated field.
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedFloatSize(fieldNumber) {
+    return this.getRepeatedFloatArray_(fieldNumber).length;
+  }
+
+  /* Int32 */
+
+  /**
+   * Returns an Array instance containing int32 values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Array<number>}
+   * @private
+   */
+  getRepeatedInt32Array_(fieldNumber) {
+    return this.getFieldWithDefault_(
+        fieldNumber, /* defaultValue= */[],
+        (indexArray, bytes) => readRepeatedPrimitive(
+            indexArray, bytes, reader.readInt32, reader.readPackedInt32,
+            WireType.VARINT));
+  }
+
+  /**
+   * Returns the element at index for the given field number.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {number}
+   */
+  getRepeatedInt32Element(fieldNumber, index) {
+    const array = this.getRepeatedInt32Array_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    return array[index];
+  }
+
+  /**
+   * Returns an Iterable instance containing int32 values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Iterable<number>}
+   */
+  getRepeatedInt32Iterable(fieldNumber) {
+    const array = this.getRepeatedInt32Array_(fieldNumber);
+    return new ArrayIterable(array);
+  }
+
+  /**
+   * Returns the size of the repeated field.
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedInt32Size(fieldNumber) {
+    return this.getRepeatedInt32Array_(fieldNumber).length;
+  }
+
+  /* Int64 */
+
+  /**
+   * Returns an Array instance containing int64 values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Array<!Int64>}
+   * @private
+   */
+  getRepeatedInt64Array_(fieldNumber) {
+    return this.getFieldWithDefault_(
+        fieldNumber, /* defaultValue= */[],
+        (indexArray, bytes) => readRepeatedPrimitive(
+            indexArray, bytes, reader.readInt64, reader.readPackedInt64,
+            WireType.VARINT));
+  }
+
+  /**
+   * Returns the element at index for the given field number.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {!Int64}
+   */
+  getRepeatedInt64Element(fieldNumber, index) {
+    const array = this.getRepeatedInt64Array_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    return array[index];
+  }
+
+  /**
+   * Returns an Iterable instance containing int64 values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Iterable<!Int64>}
+   */
+  getRepeatedInt64Iterable(fieldNumber) {
+    const array = this.getRepeatedInt64Array_(fieldNumber);
+    return new ArrayIterable(array);
+  }
+
+  /**
+   * Returns the size of the repeated field.
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedInt64Size(fieldNumber) {
+    return this.getRepeatedInt64Array_(fieldNumber).length;
+  }
+
+  /* Sfixed32 */
+
+  /**
+   * Returns an Array instance containing sfixed32 values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Array<number>}
+   * @private
+   */
+  getRepeatedSfixed32Array_(fieldNumber) {
+    return this.getFieldWithDefault_(
+        fieldNumber, /* defaultValue= */[],
+        (indexArray, bytes) => readRepeatedPrimitive(
+            indexArray, bytes, reader.readSfixed32, reader.readPackedSfixed32,
+            WireType.FIXED32));
+  }
+
+  /**
+   * Returns the element at index for the given field number.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {number}
+   */
+  getRepeatedSfixed32Element(fieldNumber, index) {
+    const array = this.getRepeatedSfixed32Array_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    return array[index];
+  }
+
+  /**
+   * Returns an Iterable instance containing sfixed32 values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Iterable<number>}
+   */
+  getRepeatedSfixed32Iterable(fieldNumber) {
+    const array = this.getRepeatedSfixed32Array_(fieldNumber);
+    return new ArrayIterable(array);
+  }
+
+  /**
+   * Returns the size of the repeated field.
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedSfixed32Size(fieldNumber) {
+    return this.getRepeatedSfixed32Array_(fieldNumber).length;
+  }
+
+  /* Sfixed64 */
+
+  /**
+   * Returns an Array instance containing sfixed64 values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Array<!Int64>}
+   * @private
+   */
+  getRepeatedSfixed64Array_(fieldNumber) {
+    return this.getFieldWithDefault_(
+        fieldNumber, /* defaultValue= */[],
+        (indexArray, bytes) => readRepeatedPrimitive(
+            indexArray, bytes, reader.readSfixed64, reader.readPackedSfixed64,
+            WireType.FIXED64));
+  }
+
+  /**
+   * Returns the element at index for the given field number.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {!Int64}
+   */
+  getRepeatedSfixed64Element(fieldNumber, index) {
+    const array = this.getRepeatedSfixed64Array_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    return array[index];
+  }
+
+  /**
+   * Returns an Iterable instance containing sfixed64 values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Iterable<!Int64>}
+   */
+  getRepeatedSfixed64Iterable(fieldNumber) {
+    const array = this.getRepeatedSfixed64Array_(fieldNumber);
+    return new ArrayIterable(array);
+  }
+
+  /**
+   * Returns the size of the repeated field.
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedSfixed64Size(fieldNumber) {
+    return this.getRepeatedSfixed64Array_(fieldNumber).length;
+  }
+
+  /* Sint32 */
+
+  /**
+   * Returns an Array instance containing sint32 values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Array<number>}
+   * @private
+   */
+  getRepeatedSint32Array_(fieldNumber) {
+    return this.getFieldWithDefault_(
+        fieldNumber, /* defaultValue= */[],
+        (indexArray, bytes) => readRepeatedPrimitive(
+            indexArray, bytes, reader.readSint32, reader.readPackedSint32,
+            WireType.VARINT));
+  }
+
+  /**
+   * Returns the element at index for the given field number.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {number}
+   */
+  getRepeatedSint32Element(fieldNumber, index) {
+    const array = this.getRepeatedSint32Array_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    return array[index];
+  }
+
+  /**
+   * Returns an Iterable instance containing sint32 values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Iterable<number>}
+   */
+  getRepeatedSint32Iterable(fieldNumber) {
+    const array = this.getRepeatedSint32Array_(fieldNumber);
+    return new ArrayIterable(array);
+  }
+
+  /**
+   * Returns the size of the repeated field.
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedSint32Size(fieldNumber) {
+    return this.getRepeatedSint32Array_(fieldNumber).length;
+  }
+
+  /* Sint64 */
+
+  /**
+   * Returns an Array instance containing sint64 values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Array<!Int64>}
+   * @private
+   */
+  getRepeatedSint64Array_(fieldNumber) {
+    return this.getFieldWithDefault_(
+        fieldNumber, /* defaultValue= */[],
+        (indexArray, bytes) => readRepeatedPrimitive(
+            indexArray, bytes, reader.readSint64, reader.readPackedSint64,
+            WireType.VARINT));
+  }
+
+  /**
+   * Returns the element at index for the given field number.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {!Int64}
+   */
+  getRepeatedSint64Element(fieldNumber, index) {
+    const array = this.getRepeatedSint64Array_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    return array[index];
+  }
+
+  /**
+   * Returns an Iterable instance containing sint64 values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Iterable<!Int64>}
+   */
+  getRepeatedSint64Iterable(fieldNumber) {
+    const array = this.getRepeatedSint64Array_(fieldNumber);
+    return new ArrayIterable(array);
+  }
+
+  /**
+   * Returns the size of the repeated field.
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedSint64Size(fieldNumber) {
+    return this.getRepeatedSint64Array_(fieldNumber).length;
+  }
+
+  /* Uint32 */
+
+  /**
+   * Returns an Array instance containing uint32 values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Array<number>}
+   * @private
+   */
+  getRepeatedUint32Array_(fieldNumber) {
+    return this.getFieldWithDefault_(
+        fieldNumber, /* defaultValue= */[],
+        (indexArray, bytes) => readRepeatedPrimitive(
+            indexArray, bytes, reader.readUint32, reader.readPackedUint32,
+            WireType.VARINT));
+  }
+
+  /**
+   * Returns the element at index for the given field number.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {number}
+   */
+  getRepeatedUint32Element(fieldNumber, index) {
+    const array = this.getRepeatedUint32Array_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    return array[index];
+  }
+
+  /**
+   * Returns an Iterable instance containing uint32 values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Iterable<number>}
+   */
+  getRepeatedUint32Iterable(fieldNumber) {
+    const array = this.getRepeatedUint32Array_(fieldNumber);
+    return new ArrayIterable(array);
+  }
+
+  /**
+   * Returns the size of the repeated field.
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedUint32Size(fieldNumber) {
+    return this.getRepeatedUint32Array_(fieldNumber).length;
+  }
+
+  /* Uint64 */
+
+  /**
+   * Returns the element at index for the given field number.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {!Int64}
+   */
+  getRepeatedUint64Element(fieldNumber, index) {
+    return this.getRepeatedInt64Element(fieldNumber, index);
+  }
+
+  /**
+   * Returns an Iterable instance containing uint64 values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Iterable<!Int64>}
+   */
+  getRepeatedUint64Iterable(fieldNumber) {
+    return this.getRepeatedInt64Iterable(fieldNumber);
+  }
+
+  /**
+   * Returns the size of the repeated field.
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedUint64Size(fieldNumber) {
+    return this.getRepeatedInt64Size(fieldNumber);
+  }
+
+  /* Bytes */
+
+  /**
+   * Returns an array instance containing bytes values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Array<!ByteString>}
+   * @private
+   */
+  getRepeatedBytesArray_(fieldNumber) {
+    return this.getFieldWithDefault_(
+        fieldNumber, /* defaultValue= */[],
+        (indexArray, bytes) =>
+            readRepeatedNonPrimitive(indexArray, bytes, reader.readBytes));
+  }
+
+  /**
+   * Returns the element at index for the given field number as a bytes.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {!ByteString}
+   */
+  getRepeatedBytesElement(fieldNumber, index) {
+    const array = this.getRepeatedBytesArray_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    return array[index];
+  }
+
+  /**
+   * Returns an Iterable instance containing bytes values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Iterable<!ByteString>}
+   */
+  getRepeatedBytesIterable(fieldNumber) {
+    const array = this.getRepeatedBytesArray_(fieldNumber);
+    return new ArrayIterable(array);
+  }
+
+  /**
+   * Returns the size of the repeated field.
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedBytesSize(fieldNumber) {
+    return this.getRepeatedBytesArray_(fieldNumber).length;
+  }
+
+  /* String */
+
+  /**
+   * Returns an array instance containing string values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Array<string>}
+   * @private
+   */
+  getRepeatedStringArray_(fieldNumber) {
+    return this.getFieldWithDefault_(
+        fieldNumber, /* defaultValue= */[],
+        (indexArray, bufferDecoder) => readRepeatedNonPrimitive(
+            indexArray, bufferDecoder, reader.readString));
+  }
+
+  /**
+   * Returns the element at index for the given field number as a string.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {string}
+   */
+  getRepeatedStringElement(fieldNumber, index) {
+    const array = this.getRepeatedStringArray_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    return array[index];
+  }
+
+  /**
+   * Returns an Iterable instance containing string values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @return {!Iterable<string>}
+   */
+  getRepeatedStringIterable(fieldNumber) {
+    const array = this.getRepeatedStringArray_(fieldNumber);
+    return new ArrayIterable(array);
+  }
+
+  /**
+   * Returns the size of the repeated field.
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedStringSize(fieldNumber) {
+    return this.getRepeatedStringArray_(fieldNumber).length;
+  }
+
+  /* Message */
+
+  /**
+   * Returns an Array instance containing boolean values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @param {function(!LazyAccessor):T} instanceCreator
+   * @param {number|undefined} pivot
+   * @return {!Array<T>}
+   * @template T
+   * @private
+   */
+  getRepeatedMessageArray_(fieldNumber, instanceCreator, pivot) {
+    checkInstanceCreator(instanceCreator);
+    const bytesInstanceCreator = (bufferDecoder) =>
+        instanceCreator(LazyAccessor.fromBufferDecoder_(bufferDecoder, pivot));
+    const readMessageFunc = (bufferDecoder, start) =>
+        bytesInstanceCreator(reader.readDelimited(bufferDecoder, start));
+
+    const readRepeatedMessageFunc = (indexArray, bufferDecoder) =>
+        readRepeatedNonPrimitive(indexArray, bufferDecoder, readMessageFunc);
+    const encoder = (writer, fieldNumber, values) =>
+        writeRepeatedMessage(writer, fieldNumber, values);
+    return this.getFieldWithDefault_(
+        fieldNumber, /* defaultValue= */[], readRepeatedMessageFunc, encoder);
+  }
+
+  /**
+   * Returns the element at index for the given field number as a message.
+   * @param {number} fieldNumber
+   * @param {function(!LazyAccessor):T} instanceCreator
+   * @param {number} index
+   * @param {number=} pivot
+   * @return {T}
+   * @template T
+   */
+  getRepeatedMessageElement(
+      fieldNumber, instanceCreator, index, pivot = undefined) {
+    const array =
+        this.getRepeatedMessageArray_(fieldNumber, instanceCreator, pivot);
+    checkCriticalElementIndex(index, array.length);
+    return array[index];
+  }
+
+  /**
+   * Returns an Iterable instance containing message values for the given field
+   * number.
+   * @param {number} fieldNumber
+   * @param {function(!LazyAccessor):T} instanceCreator
+   * @param {number=} pivot
+   * @return {!Iterable<T>}
+   * @template T
+   */
+  getRepeatedMessageIterable(fieldNumber, instanceCreator, pivot = undefined) {
+    const array =
+        this.getRepeatedMessageArray_(fieldNumber, instanceCreator, pivot);
+    return new ArrayIterable(array);
+  }
+
+  /**
+   * Returns an Iterable instance containing message accessors for the given
+   * field number.
+   * @param {number} fieldNumber
+   * @param {number=} pivot
+   * @return {!Iterable<!LazyAccessor>}
+   */
+  getRepeatedMessageAccessorIterable(fieldNumber, pivot = undefined) {
+    checkFieldNumber(fieldNumber);
+
+    const field = this.fields_.get(fieldNumber);
+    if (!field) {
+      return [];
+    }
+
+    if (field.hasDecodedValue()) {
+      return new ArrayIterable(field.getDecodedValue().map(
+          value => checkIsInternalMessage(value).internalGetKernel()));
+    }
+
+    const readMessageFunc = (bufferDecoder, start) =>
+        LazyAccessor.fromBufferDecoder_(
+            reader.readDelimited(bufferDecoder, start), pivot);
+    const array = readRepeatedNonPrimitive(
+        checkDefAndNotNull(field.getIndexArray()),
+        checkDefAndNotNull(this.bufferDecoder_), readMessageFunc);
+    return new ArrayIterable(array);
+  }
+
+  /**
+   * Returns the size of the repeated field.
+   * @param {number} fieldNumber
+   * @param {function(!LazyAccessor):T} instanceCreator
+   * @return {number}
+   * @param {number=} pivot
+   * @template T
+   */
+  getRepeatedMessageSize(fieldNumber, instanceCreator, pivot = undefined) {
+    return this.getRepeatedMessageArray_(fieldNumber, instanceCreator, pivot)
+        .length;
+  }
+
+  /***************************************************************************
+   *                        OPTIONAL SETTER METHODS
+   ***************************************************************************/
+
+  /**
+   * Sets a boolean value to the field with the given field number.
+   * @param {number} fieldNumber
+   * @param {boolean} value
+   */
+  setBool(fieldNumber, value) {
+    checkCriticalTypeBool(value);
+    this.setField_(fieldNumber, value, (writer, fieldNumber, value) => {
+      writer.writeBool(fieldNumber, value);
+    });
+  }
+
+  /**
+   * Sets a boolean value to the field with the given field number.
+   * @param {number} fieldNumber
+   * @param {!ByteString} value
+   */
+  setBytes(fieldNumber, value) {
+    checkCriticalTypeByteString(value);
+    this.setField_(fieldNumber, value, (writer, fieldNumber, value) => {
+      writer.writeBytes(fieldNumber, value);
+    });
+  }
+
+  /**
+   * Sets a double value to the field with the given field number.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  setDouble(fieldNumber, value) {
+    checkCriticalTypeDouble(value);
+    this.setField_(fieldNumber, value, (writer, fieldNumber, value) => {
+      writer.writeDouble(fieldNumber, value);
+    });
+  }
+
+  /**
+   * Sets a fixed32 value to the field with the given field number.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  setFixed32(fieldNumber, value) {
+    checkCriticalTypeUnsignedInt32(value);
+    this.setField_(fieldNumber, value, (writer, fieldNumber, value) => {
+      writer.writeFixed32(fieldNumber, value);
+    });
+  }
+
+  /**
+   * Sets a uint64 value to the field with the given field number.\
+   * Note: Since g.m.Long does not support unsigned int64 values we are going
+   * the Java route here for now and simply output the number as a signed int64.
+   * Users can get to individual bits by themselves.
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  setFixed64(fieldNumber, value) {
+    this.setSfixed64(fieldNumber, value);
+  }
+
+  /**
+   * Sets a float value to the field with the given field number.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  setFloat(fieldNumber, value) {
+    checkCriticalTypeFloat(value);
+    // Eagerly round to 32-bit precision so that reading back after set will
+    // yield the same value a reader will receive after serialization.
+    const floatValue = Math.fround(value);
+    this.setField_(fieldNumber, floatValue, (writer, fieldNumber, value) => {
+      writer.writeFloat(fieldNumber, value);
+    });
+  }
+
+  /**
+   * Sets a int32 value to the field with the given field number.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  setInt32(fieldNumber, value) {
+    checkCriticalTypeSignedInt32(value);
+    this.setField_(fieldNumber, value, (writer, fieldNumber, value) => {
+      writer.writeInt32(fieldNumber, value);
+    });
+  }
+
+  /**
+   * Sets a int64 value to the field with the given field number.
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  setInt64(fieldNumber, value) {
+    checkCriticalTypeSignedInt64(value);
+    this.setField_(fieldNumber, value, (writer, fieldNumber, value) => {
+      writer.writeInt64(fieldNumber, value);
+    });
+  }
+
+  /**
+   * Sets a sfixed32 value to the field with the given field number.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  setSfixed32(fieldNumber, value) {
+    checkCriticalTypeSignedInt32(value);
+    this.setField_(fieldNumber, value, (writer, fieldNumber, value) => {
+      writer.writeSfixed32(fieldNumber, value);
+    });
+  }
+
+  /**
+   * Sets a sfixed64 value to the field with the given field number.
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  setSfixed64(fieldNumber, value) {
+    checkCriticalTypeSignedInt64(value);
+    this.setField_(fieldNumber, value, (writer, fieldNumber, value) => {
+      writer.writeSfixed64(fieldNumber, value);
+    });
+  }
+
+  /**
+   * Sets a sint32 value to the field with the given field number.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  setSint32(fieldNumber, value) {
+    checkCriticalTypeSignedInt32(value);
+    this.setField_(fieldNumber, value, (writer, fieldNumber, value) => {
+      writer.writeSint32(fieldNumber, value);
+    });
+  }
+
+  /**
+   * Sets a sint64 value to the field with the given field number.
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  setSint64(fieldNumber, value) {
+    checkCriticalTypeSignedInt64(value);
+    this.setField_(fieldNumber, value, (writer, fieldNumber, value) => {
+      writer.writeSint64(fieldNumber, value);
+    });
+  }
+
+  /**
+   * Sets a boolean value to the field with the given field number.
+   * @param {number} fieldNumber
+   * @param {string} value
+   */
+  setString(fieldNumber, value) {
+    checkCriticalTypeString(value);
+    this.setField_(fieldNumber, value, (writer, fieldNumber, value) => {
+      writer.writeString(fieldNumber, value);
+    });
+  }
+
+  /**
+   * Sets a uint32 value to the field with the given field number.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  setUint32(fieldNumber, value) {
+    checkCriticalTypeUnsignedInt32(value);
+    this.setField_(fieldNumber, value, (writer, fieldNumber, value) => {
+      writer.writeUint32(fieldNumber, value);
+    });
+  }
+
+  /**
+   * Sets a uint64 value to the field with the given field number.\
+   * Note: Since g.m.Long does not support unsigned int64 values we are going
+   * the Java route here for now and simply output the number as a signed int64.
+   * Users can get to individual bits by themselves.
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  setUint64(fieldNumber, value) {
+    this.setInt64(fieldNumber, value);
+  }
+
+  /**
+   * Sets a proto Message to the field with the given field number.
+   * Instead of working with the LazyAccessor inside of the message directly, we
+   * need the message instance to keep its reference equality for subsequent
+   * gettings.
+   * @param {number} fieldNumber
+   * @param {!InternalMessage} value
+   */
+  setMessage(fieldNumber, value) {
+    checkCriticalType(
+        value !== null, 'Given value is not a message instance: null');
+    this.setField_(fieldNumber, value, writeMessage);
+  }
+
+  /***************************************************************************
+   *                        REPEATED SETTER METHODS
+   ***************************************************************************/
+
+  /* Bool */
+
+  /**
+   * Adds all boolean values into the field for the given field number.
+   * How these values are encoded depends on the given write function.
+   * @param {number} fieldNumber
+   * @param {!Iterable<boolean>} values
+   * @param {function(!Writer, number, !Array<boolean>): undefined} encoder
+   * @private
+   */
+  addRepeatedBoolIterable_(fieldNumber, values, encoder) {
+    const array = [...this.getRepeatedBoolArray_(fieldNumber), ...values];
+    checkCriticalTypeBoolArray(array);
+    // Needs to set it back because the default empty array was not cached.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Adds a single boolean value into the field for the given field number.
+   * All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {boolean} value
+   */
+  addPackedBoolElement(fieldNumber, value) {
+    this.addRepeatedBoolIterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writePackedBool(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all boolean values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<boolean>} values
+   */
+  addPackedBoolIterable(fieldNumber, values) {
+    this.addRepeatedBoolIterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writePackedBool(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds a single boolean value into the field for the given field number.
+   * All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {boolean} value
+   */
+  addUnpackedBoolElement(fieldNumber, value) {
+    this.addRepeatedBoolIterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writeRepeatedBool(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all boolean values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<boolean>} values
+   */
+  addUnpackedBoolIterable(fieldNumber, values) {
+    this.addRepeatedBoolIterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writeRepeatedBool(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets a single boolean value into the field for the given field number at
+   * the given index. How these values are encoded depends on the given write
+   * function.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {boolean} value
+   * @param {function(!Writer, number, !Array<boolean>): undefined} encoder
+   * @throws {!Error} if index is out of range when check mode is critical
+   * @private
+   */
+  setRepeatedBoolElement_(fieldNumber, index, value, encoder) {
+    checkCriticalTypeBool(value);
+    const array = this.getRepeatedBoolArray_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    array[index] = value;
+    // Needs to set it back to set encoder.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Sets a single boolean value into the field for the given field number at
+   * the given index. All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {boolean} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedBoolElement(fieldNumber, index, value) {
+    this.setRepeatedBoolElement_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writePackedBool(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all boolean values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<boolean>} values
+   */
+  setPackedBoolIterable(fieldNumber, values) {
+    const /** !Array<boolean> */ array = Array.from(values);
+    checkCriticalTypeBoolArray(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writePackedBool(fieldNumber, values);
+    });
+  }
+
+  /**
+   * Sets a single boolean value into the field for the given field number at
+   * the given index. All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {boolean} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedBoolElement(fieldNumber, index, value) {
+    this.setRepeatedBoolElement_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writeRepeatedBool(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all boolean values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<boolean>} values
+   */
+  setUnpackedBoolIterable(fieldNumber, values) {
+    const /** !Array<boolean> */ array = Array.from(values);
+    checkCriticalTypeBoolArray(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writeRepeatedBool(fieldNumber, values);
+    });
+  }
+
+  /* Double */
+
+  /**
+   * Adds all double values into the field for the given field number.
+   * How these values are encoded depends on the given write function.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   * @param {function(!Writer, number, !Array<number>): undefined} encoder
+   * @private
+   */
+  addRepeatedDoubleIterable_(fieldNumber, values, encoder) {
+    const array = [...this.getRepeatedDoubleArray_(fieldNumber), ...values];
+    checkCriticalTypeDoubleArray(array);
+    // Needs to set it back because the default empty array was not cached.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Adds a single double value into the field for the given field number.
+   * All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addPackedDoubleElement(fieldNumber, value) {
+    this.addRepeatedDoubleIterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writePackedDouble(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all double values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addPackedDoubleIterable(fieldNumber, values) {
+    this.addRepeatedDoubleIterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writePackedDouble(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds a single double value into the field for the given field number.
+   * All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addUnpackedDoubleElement(fieldNumber, value) {
+    this.addRepeatedDoubleIterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writeRepeatedDouble(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all double values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addUnpackedDoubleIterable(fieldNumber, values) {
+    this.addRepeatedDoubleIterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writeRepeatedDouble(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets a single double value into the field for the given field number at the
+   * given index.
+   * How these values are encoded depends on the given write function.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @param {function(!Writer, number, !Array<number>): undefined} encoder
+   * @throws {!Error} if index is out of range when check mode is critical
+   * @private
+   */
+  setRepeatedDoubleElement_(fieldNumber, index, value, encoder) {
+    checkCriticalTypeDouble(value);
+    const array = this.getRepeatedDoubleArray_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    array[index] = value;
+    // Needs to set it back to set encoder.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Sets a single double value into the field for the given field number at the
+   * given index.
+   * All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedDoubleElement(fieldNumber, index, value) {
+    this.setRepeatedDoubleElement_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writePackedDouble(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all double values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setPackedDoubleIterable(fieldNumber, values) {
+    const array = Array.from(values);
+    checkCriticalTypeDoubleArray(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writePackedDouble(fieldNumber, values);
+    });
+  }
+
+  /**
+   * Sets a single double value into the field for the given field number at the
+   * given index.
+   * All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedDoubleElement(fieldNumber, index, value) {
+    this.setRepeatedDoubleElement_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writeRepeatedDouble(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all double values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setUnpackedDoubleIterable(fieldNumber, values) {
+    const array = Array.from(values);
+    checkCriticalTypeDoubleArray(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writeRepeatedDouble(fieldNumber, values);
+    });
+  }
+
+  /* Fixed32 */
+
+  /**
+   * Adds all fixed32 values into the field for the given field number.
+   * How these values are encoded depends on the given write function.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   * @param {function(!Writer, number, !Array<number>): undefined} encoder
+   * @private
+   */
+  addRepeatedFixed32Iterable_(fieldNumber, values, encoder) {
+    const array = [...this.getRepeatedFixed32Array_(fieldNumber), ...values];
+    checkCriticalTypeUnsignedInt32Array(array);
+    // Needs to set it back because the default empty array was not cached.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Adds a single fixed32 value into the field for the given field number.
+   * All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addPackedFixed32Element(fieldNumber, value) {
+    this.addRepeatedFixed32Iterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writePackedFixed32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all fixed32 values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addPackedFixed32Iterable(fieldNumber, values) {
+    this.addRepeatedFixed32Iterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writePackedFixed32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds a single fixed32 value into the field for the given field number.
+   * All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addUnpackedFixed32Element(fieldNumber, value) {
+    this.addRepeatedFixed32Iterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writeRepeatedFixed32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all fixed32 values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addUnpackedFixed32Iterable(fieldNumber, values) {
+    this.addRepeatedFixed32Iterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writeRepeatedFixed32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets a single fixed32 value into the field for the given field number at
+   * the given index. How these values are encoded depends on the given write
+   * function.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @param {function(!Writer, number, !Array<number>): undefined} encoder
+   * @throws {!Error} if index is out of range when check mode is critical
+   * @private
+   */
+  setRepeatedFixed32Element_(fieldNumber, index, value, encoder) {
+    checkCriticalTypeUnsignedInt32(value);
+    const array = this.getRepeatedFixed32Array_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    array[index] = value;
+    // Needs to set it back to set encoder.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Sets a single fixed32 value into the field for the given field number at
+   * the given index. All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedFixed32Element(fieldNumber, index, value) {
+    this.setRepeatedFixed32Element_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writePackedFixed32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all fixed32 values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setPackedFixed32Iterable(fieldNumber, values) {
+    const array = Array.from(values);
+    checkCriticalTypeUnsignedInt32Array(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writePackedFixed32(fieldNumber, values);
+    });
+  }
+
+  /**
+   * Sets a single fixed32 value into the field for the given field number at
+   * the given index. All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedFixed32Element(fieldNumber, index, value) {
+    this.setRepeatedFixed32Element_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writeRepeatedFixed32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all fixed32 values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setUnpackedFixed32Iterable(fieldNumber, values) {
+    const array = Array.from(values);
+    checkCriticalTypeUnsignedInt32Array(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writeRepeatedFixed32(fieldNumber, values);
+    });
+  }
+
+  /* Fixed64 */
+
+  /**
+   * Adds a single fixed64 value into the field for the given field number.
+   * All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  addPackedFixed64Element(fieldNumber, value) {
+    this.addPackedSfixed64Element(fieldNumber, value);
+  }
+
+  /**
+   * Adds all fixed64 values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  addPackedFixed64Iterable(fieldNumber, values) {
+    this.addPackedSfixed64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * Adds a single fixed64 value into the field for the given field number.
+   * All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  addUnpackedFixed64Element(fieldNumber, value) {
+    this.addUnpackedSfixed64Element(fieldNumber, value);
+  }
+
+  /**
+   * Adds all fixed64 values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  addUnpackedFixed64Iterable(fieldNumber, values) {
+    this.addUnpackedSfixed64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * Sets a single fixed64 value into the field for the given field number at
+   * the given index. All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedFixed64Element(fieldNumber, index, value) {
+    this.setPackedSfixed64Element(fieldNumber, index, value);
+  }
+
+  /**
+   * Sets all fixed64 values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  setPackedFixed64Iterable(fieldNumber, values) {
+    this.setPackedSfixed64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * Sets a single fixed64 value into the field for the given field number at
+   * the given index. All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedFixed64Element(fieldNumber, index, value) {
+    this.setUnpackedSfixed64Element(fieldNumber, index, value);
+  }
+
+  /**
+   * Sets all fixed64 values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  setUnpackedFixed64Iterable(fieldNumber, values) {
+    this.setUnpackedSfixed64Iterable(fieldNumber, values);
+  }
+
+  /* Float */
+
+  /**
+   * Adds all float values into the field for the given field number.
+   * How these values are encoded depends on the given write function.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   * @param {function(!Writer, number, !Array<number>): undefined} encoder
+   * @private
+   */
+  addRepeatedFloatIterable_(fieldNumber, values, encoder) {
+    checkCriticalTypeFloatIterable(values);
+    // Eagerly round to 32-bit precision so that reading back after set will
+    // yield the same value a reader will receive after serialization.
+    const floatValues = Array.from(values, fround);
+    const array = [...this.getRepeatedFloatArray_(fieldNumber), ...floatValues];
+    checkCriticalTypeFloatIterable(array);
+    // Needs to set it back because the default empty array was not cached.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Adds a single float value into the field for the given field number.
+   * All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addPackedFloatElement(fieldNumber, value) {
+    this.addRepeatedFloatIterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writePackedFloat(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all float values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addPackedFloatIterable(fieldNumber, values) {
+    this.addRepeatedFloatIterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writePackedFloat(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds a single float value into the field for the given field number.
+   * All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addUnpackedFloatElement(fieldNumber, value) {
+    this.addRepeatedFloatIterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writeRepeatedFloat(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all float values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addUnpackedFloatIterable(fieldNumber, values) {
+    this.addRepeatedFloatIterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writeRepeatedFloat(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets a single float value into the field for the given field number at the
+   * given index.
+   * How these values are encoded depends on the given write function.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @param {function(!Writer, number, !Array<number>): undefined} encoder
+   * @throws {!Error} if index is out of range when check mode is critical
+   * @private
+   */
+  setRepeatedFloatElement_(fieldNumber, index, value, encoder) {
+    checkCriticalTypeFloat(value);
+    // Eagerly round to 32-bit precision so that reading back after set will
+    // yield the same value a reader will receive after serialization.
+    const floatValue = Math.fround(value);
+    const array = this.getRepeatedFloatArray_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    array[index] = floatValue;
+    // Needs to set it back to set encoder.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Sets a single float value into the field for the given field number at the
+   * given index.
+   * All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedFloatElement(fieldNumber, index, value) {
+    this.setRepeatedFloatElement_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writePackedFloat(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all float values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setPackedFloatIterable(fieldNumber, values) {
+    checkCriticalTypeFloatIterable(values);
+    // Eagerly round to 32-bit precision so that reading back after set will
+    // yield the same value a reader will receive after serialization.
+    const array = Array.from(values, fround);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writePackedFloat(fieldNumber, values);
+    });
+  }
+
+  /**
+   * Sets a single float value into the field for the given field number at the
+   * given index.
+   * All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedFloatElement(fieldNumber, index, value) {
+    this.setRepeatedFloatElement_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writeRepeatedFloat(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all float values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setUnpackedFloatIterable(fieldNumber, values) {
+    checkCriticalTypeFloatIterable(values);
+    // Eagerly round to 32-bit precision so that reading back after set will
+    // yield the same value a reader will receive after serialization.
+    const array = Array.from(values, fround);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writeRepeatedFloat(fieldNumber, values);
+    });
+  }
+
+  /* Int32 */
+
+  /**
+   * Adds all int32 values into the field for the given field number.
+   * How these values are encoded depends on the given write function.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   * @param {function(!Writer, number, !Array<number>): undefined} encoder
+   * @private
+   */
+  addRepeatedInt32Iterable_(fieldNumber, values, encoder) {
+    const array = [...this.getRepeatedInt32Array_(fieldNumber), ...values];
+    checkCriticalTypeSignedInt32Array(array);
+    // Needs to set it back because the default empty array was not cached.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Adds a single int32 value into the field for the given field number.
+   * All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addPackedInt32Element(fieldNumber, value) {
+    this.addRepeatedInt32Iterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writePackedInt32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all int32 values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addPackedInt32Iterable(fieldNumber, values) {
+    this.addRepeatedInt32Iterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writePackedInt32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds a single int32 value into the field for the given field number.
+   * All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addUnpackedInt32Element(fieldNumber, value) {
+    this.addRepeatedInt32Iterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writeRepeatedInt32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all int32 values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addUnpackedInt32Iterable(fieldNumber, values) {
+    this.addRepeatedInt32Iterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writeRepeatedInt32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets a single int32 value into the field for the given field number at
+   * the given index. How these values are encoded depends on the given write
+   * function.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @param {function(!Writer, number, !Array<number>): undefined} encoder
+   * @throws {!Error} if index is out of range when check mode is critical
+   * @private
+   */
+  setRepeatedInt32Element_(fieldNumber, index, value, encoder) {
+    checkCriticalTypeSignedInt32(value);
+    const array = this.getRepeatedInt32Array_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    array[index] = value;
+    // Needs to set it back to set encoder.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Sets a single int32 value into the field for the given field number at
+   * the given index. All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedInt32Element(fieldNumber, index, value) {
+    this.setRepeatedInt32Element_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writePackedInt32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all int32 values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setPackedInt32Iterable(fieldNumber, values) {
+    const array = Array.from(values);
+    checkCriticalTypeSignedInt32Array(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writePackedInt32(fieldNumber, values);
+    });
+  }
+
+  /**
+   * Sets a single int32 value into the field for the given field number at
+   * the given index. All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedInt32Element(fieldNumber, index, value) {
+    this.setRepeatedInt32Element_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writeRepeatedInt32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all int32 values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setUnpackedInt32Iterable(fieldNumber, values) {
+    const array = Array.from(values);
+    checkCriticalTypeSignedInt32Array(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writeRepeatedInt32(fieldNumber, values);
+    });
+  }
+
+  /* Int64 */
+
+  /**
+   * Adds all int64 values into the field for the given field number.
+   * How these values are encoded depends on the given write function.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   * @param {function(!Writer, number, !Array<!Int64>): undefined} encoder
+   * @private
+   */
+  addRepeatedInt64Iterable_(fieldNumber, values, encoder) {
+    const array = [...this.getRepeatedInt64Array_(fieldNumber), ...values];
+    checkCriticalTypeSignedInt64Array(array);
+    // Needs to set it back because the default empty array was not cached.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Adds a single int64 value into the field for the given field number.
+   * All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  addPackedInt64Element(fieldNumber, value) {
+    this.addRepeatedInt64Iterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writePackedInt64(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all int64 values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  addPackedInt64Iterable(fieldNumber, values) {
+    this.addRepeatedInt64Iterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writePackedInt64(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds a single int64 value into the field for the given field number.
+   * All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  addUnpackedInt64Element(fieldNumber, value) {
+    this.addRepeatedInt64Iterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writeRepeatedInt64(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all int64 values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  addUnpackedInt64Iterable(fieldNumber, values) {
+    this.addRepeatedInt64Iterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writeRepeatedInt64(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets a single int64 value into the field for the given field number at
+   * the given index. How these values are encoded depends on the given write
+   * function.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @param {function(!Writer, number, !Array<!Int64>): undefined} encoder
+   * @throws {!Error} if index is out of range when check mode is critical
+   * @private
+   */
+  setRepeatedInt64Element_(fieldNumber, index, value, encoder) {
+    checkCriticalTypeSignedInt64(value);
+    const array = this.getRepeatedInt64Array_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    array[index] = value;
+    // Needs to set it back to set encoder.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Sets a single int64 value into the field for the given field number at
+   * the given index. All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedInt64Element(fieldNumber, index, value) {
+    this.setRepeatedInt64Element_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writePackedInt64(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all int64 values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  setPackedInt64Iterable(fieldNumber, values) {
+    const array = Array.from(values);
+    checkCriticalTypeSignedInt64Array(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writePackedInt64(fieldNumber, values);
+    });
+  }
+
+  /**
+   * Sets a single int64 value into the field for the given field number at
+   * the given index. All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedInt64Element(fieldNumber, index, value) {
+    this.setRepeatedInt64Element_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writeRepeatedInt64(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all int64 values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  setUnpackedInt64Iterable(fieldNumber, values) {
+    const array = Array.from(values);
+    checkCriticalTypeSignedInt64Array(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writeRepeatedInt64(fieldNumber, values);
+    });
+  }
+
+  /* Sfixed32 */
+
+  /**
+   * Adds all sfixed32 values into the field for the given field number.
+   * How these values are encoded depends on the given write function.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   * @param {function(!Writer, number, !Array<number>): undefined} encoder
+   * @private
+   */
+  addRepeatedSfixed32Iterable_(fieldNumber, values, encoder) {
+    const array = [...this.getRepeatedSfixed32Array_(fieldNumber), ...values];
+    checkCriticalTypeSignedInt32Array(array);
+    // Needs to set it back because the default empty array was not cached.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Adds a single sfixed32 value into the field for the given field number.
+   * All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addPackedSfixed32Element(fieldNumber, value) {
+    this.addRepeatedSfixed32Iterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writePackedSfixed32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all sfixed32 values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addPackedSfixed32Iterable(fieldNumber, values) {
+    this.addRepeatedSfixed32Iterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writePackedSfixed32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds a single sfixed32 value into the field for the given field number.
+   * All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addUnpackedSfixed32Element(fieldNumber, value) {
+    this.addRepeatedSfixed32Iterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writeRepeatedSfixed32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all sfixed32 values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addUnpackedSfixed32Iterable(fieldNumber, values) {
+    this.addRepeatedSfixed32Iterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writeRepeatedSfixed32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets a single sfixed32 value into the field for the given field number at
+   * the given index. How these values are encoded depends on the given write
+   * function.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @param {function(!Writer, number, !Array<number>): undefined} encoder
+   * @throws {!Error} if index is out of range when check mode is critical
+   * @private
+   */
+  setRepeatedSfixed32Element_(fieldNumber, index, value, encoder) {
+    checkCriticalTypeSignedInt32(value);
+    const array = this.getRepeatedSfixed32Array_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    array[index] = value;
+    // Needs to set it back to set encoder.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Sets a single sfixed32 value into the field for the given field number at
+   * the given index. All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedSfixed32Element(fieldNumber, index, value) {
+    this.setRepeatedSfixed32Element_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writePackedSfixed32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all sfixed32 values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setPackedSfixed32Iterable(fieldNumber, values) {
+    const array = Array.from(values);
+    checkCriticalTypeSignedInt32Array(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writePackedSfixed32(fieldNumber, values);
+    });
+  }
+
+  /**
+   * Sets a single sfixed32 value into the field for the given field number at
+   * the given index. All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedSfixed32Element(fieldNumber, index, value) {
+    this.setRepeatedSfixed32Element_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writeRepeatedSfixed32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all sfixed32 values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setUnpackedSfixed32Iterable(fieldNumber, values) {
+    const array = Array.from(values);
+    checkCriticalTypeSignedInt32Array(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writeRepeatedSfixed32(fieldNumber, values);
+    });
+  }
+
+  /* Sfixed64 */
+
+  /**
+   * Adds all sfixed64 values into the field for the given field number.
+   * How these values are encoded depends on the given write function.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   * @param {function(!Writer, number, !Array<!Int64>): undefined} encoder
+   * @private
+   */
+  addRepeatedSfixed64Iterable_(fieldNumber, values, encoder) {
+    const array = [...this.getRepeatedSfixed64Array_(fieldNumber), ...values];
+    checkCriticalTypeSignedInt64Array(array);
+    // Needs to set it back because the default empty array was not cached.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Adds a single sfixed64 value into the field for the given field number.
+   * All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  addPackedSfixed64Element(fieldNumber, value) {
+    this.addRepeatedSfixed64Iterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writePackedSfixed64(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all sfixed64 values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  addPackedSfixed64Iterable(fieldNumber, values) {
+    this.addRepeatedSfixed64Iterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writePackedSfixed64(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds a single sfixed64 value into the field for the given field number.
+   * All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  addUnpackedSfixed64Element(fieldNumber, value) {
+    this.addRepeatedSfixed64Iterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writeRepeatedSfixed64(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all sfixed64 values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  addUnpackedSfixed64Iterable(fieldNumber, values) {
+    this.addRepeatedSfixed64Iterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writeRepeatedSfixed64(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets a single sfixed64 value into the field for the given field number at
+   * the given index. How these values are encoded depends on the given write
+   * function.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @param {function(!Writer, number, !Array<!Int64>): undefined} encoder
+   * @throws {!Error} if index is out of range when check mode is critical
+   * @private
+   */
+  setRepeatedSfixed64Element_(fieldNumber, index, value, encoder) {
+    checkCriticalTypeSignedInt64(value);
+    const array = this.getRepeatedSfixed64Array_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    array[index] = value;
+    // Needs to set it back to set encoder.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Sets a single sfixed64 value into the field for the given field number at
+   * the given index. All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedSfixed64Element(fieldNumber, index, value) {
+    this.setRepeatedSfixed64Element_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writePackedSfixed64(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all sfixed64 values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  setPackedSfixed64Iterable(fieldNumber, values) {
+    const array = Array.from(values);
+    checkCriticalTypeSignedInt64Array(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writePackedSfixed64(fieldNumber, values);
+    });
+  }
+
+  /**
+   * Sets a single sfixed64 value into the field for the given field number at
+   * the given index. All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedSfixed64Element(fieldNumber, index, value) {
+    this.setRepeatedSfixed64Element_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writeRepeatedSfixed64(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all sfixed64 values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  setUnpackedSfixed64Iterable(fieldNumber, values) {
+    const array = Array.from(values);
+    checkCriticalTypeSignedInt64Array(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writeRepeatedSfixed64(fieldNumber, values);
+    });
+  }
+
+  /* Sint32 */
+
+  /**
+   * Adds all sint32 values into the field for the given field number.
+   * How these values are encoded depends on the given write function.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   * @param {function(!Writer, number, !Array<number>): undefined} encoder
+   * @private
+   */
+  addRepeatedSint32Iterable_(fieldNumber, values, encoder) {
+    const array = [...this.getRepeatedSint32Array_(fieldNumber), ...values];
+    checkCriticalTypeSignedInt32Array(array);
+    // Needs to set it back because the default empty array was not cached.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Adds a single sint32 value into the field for the given field number.
+   * All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addPackedSint32Element(fieldNumber, value) {
+    this.addRepeatedSint32Iterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writePackedSint32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all sint32 values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addPackedSint32Iterable(fieldNumber, values) {
+    this.addRepeatedSint32Iterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writePackedSint32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds a single sint32 value into the field for the given field number.
+   * All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addUnpackedSint32Element(fieldNumber, value) {
+    this.addRepeatedSint32Iterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writeRepeatedSint32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all sint32 values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addUnpackedSint32Iterable(fieldNumber, values) {
+    this.addRepeatedSint32Iterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writeRepeatedSint32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets a single sint32 value into the field for the given field number at
+   * the given index. How these values are encoded depends on the given write
+   * function.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @param {function(!Writer, number, !Array<number>): undefined} encoder
+   * @throws {!Error} if index is out of range when check mode is critical
+   * @private
+   */
+  setRepeatedSint32Element_(fieldNumber, index, value, encoder) {
+    checkCriticalTypeSignedInt32(value);
+    const array = this.getRepeatedSint32Array_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    array[index] = value;
+    // Needs to set it back to set encoder.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Sets a single sint32 value into the field for the given field number at
+   * the given index. All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedSint32Element(fieldNumber, index, value) {
+    this.setRepeatedSint32Element_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writePackedSint32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all sint32 values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setPackedSint32Iterable(fieldNumber, values) {
+    const array = Array.from(values);
+    checkCriticalTypeSignedInt32Array(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writePackedSint32(fieldNumber, values);
+    });
+  }
+
+  /**
+   * Sets a single sint32 value into the field for the given field number at
+   * the given index. All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedSint32Element(fieldNumber, index, value) {
+    this.setRepeatedSint32Element_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writeRepeatedSint32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all sint32 values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setUnpackedSint32Iterable(fieldNumber, values) {
+    const array = Array.from(values);
+    checkCriticalTypeSignedInt32Array(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writeRepeatedSint32(fieldNumber, values);
+    });
+  }
+
+  /* Sint64 */
+
+  /**
+   * Adds all sint64 values into the field for the given field number.
+   * How these values are encoded depends on the given write function.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   * @param {function(!Writer, number, !Array<!Int64>): undefined} encoder
+   * @private
+   */
+  addRepeatedSint64Iterable_(fieldNumber, values, encoder) {
+    const array = [...this.getRepeatedSint64Array_(fieldNumber), ...values];
+    checkCriticalTypeSignedInt64Array(array);
+    // Needs to set it back because the default empty array was not cached.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Adds a single sint64 value into the field for the given field number.
+   * All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  addPackedSint64Element(fieldNumber, value) {
+    this.addRepeatedSint64Iterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writePackedSint64(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all sint64 values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  addPackedSint64Iterable(fieldNumber, values) {
+    this.addRepeatedSint64Iterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writePackedSint64(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds a single sint64 value into the field for the given field number.
+   * All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  addUnpackedSint64Element(fieldNumber, value) {
+    this.addRepeatedSint64Iterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writeRepeatedSint64(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all sint64 values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  addUnpackedSint64Iterable(fieldNumber, values) {
+    this.addRepeatedSint64Iterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writeRepeatedSint64(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets a single sint64 value into the field for the given field number at
+   * the given index. How these values are encoded depends on the given write
+   * function.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @param {function(!Writer, number, !Array<!Int64>): undefined} encoder
+   * @throws {!Error} if index is out of range when check mode is critical
+   * @private
+   */
+  setRepeatedSint64Element_(fieldNumber, index, value, encoder) {
+    checkCriticalTypeSignedInt64(value);
+    const array = this.getRepeatedSint64Array_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    array[index] = value;
+    // Needs to set it back to set encoder.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Sets a single sint64 value into the field for the given field number at
+   * the given index. All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedSint64Element(fieldNumber, index, value) {
+    this.setRepeatedSint64Element_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writePackedSint64(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all sint64 values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  setPackedSint64Iterable(fieldNumber, values) {
+    const array = Array.from(values);
+    checkCriticalTypeSignedInt64Array(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writePackedSint64(fieldNumber, values);
+    });
+  }
+
+  /**
+   * Sets a single sint64 value into the field for the given field number at
+   * the given index. All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedSint64Element(fieldNumber, index, value) {
+    this.setRepeatedSint64Element_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writeRepeatedSint64(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all sint64 values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  setUnpackedSint64Iterable(fieldNumber, values) {
+    const array = Array.from(values);
+    checkCriticalTypeSignedInt64Array(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writeRepeatedSint64(fieldNumber, values);
+    });
+  }
+
+  /* Uint32 */
+
+  /**
+   * Adds all uint32 values into the field for the given field number.
+   * How these values are encoded depends on the given write function.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   * @param {function(!Writer, number, !Array<number>): undefined} encoder
+   * @private
+   */
+  addRepeatedUint32Iterable_(fieldNumber, values, encoder) {
+    const array = [...this.getRepeatedUint32Array_(fieldNumber), ...values];
+    checkCriticalTypeUnsignedInt32Array(array);
+    // Needs to set it back because the default empty array was not cached.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Adds a single uint32 value into the field for the given field number.
+   * All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addPackedUint32Element(fieldNumber, value) {
+    this.addRepeatedUint32Iterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writePackedUint32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all uint32 values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addPackedUint32Iterable(fieldNumber, values) {
+    this.addRepeatedUint32Iterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writePackedUint32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds a single uint32 value into the field for the given field number.
+   * All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addUnpackedUint32Element(fieldNumber, value) {
+    this.addRepeatedUint32Iterable_(
+        fieldNumber, [value], (writer, fieldNumber, values) => {
+          writer.writeRepeatedUint32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Adds all uint32 values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addUnpackedUint32Iterable(fieldNumber, values) {
+    this.addRepeatedUint32Iterable_(
+        fieldNumber, values, (writer, fieldNumber, values) => {
+          writer.writeRepeatedUint32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets a single uint32 value into the field for the given field number at
+   * the given index. How these values are encoded depends on the given write
+   * function.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @param {function(!Writer, number, !Array<number>): undefined} encoder
+   * @throws {!Error} if index is out of range when check mode is critical
+   * @private
+   */
+  setRepeatedUint32Element_(fieldNumber, index, value, encoder) {
+    checkCriticalTypeUnsignedInt32(value);
+    const array = this.getRepeatedUint32Array_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    array[index] = value;
+    // Needs to set it back to set encoder.
+    this.setField_(fieldNumber, array, encoder);
+  }
+
+  /**
+   * Sets a single uint32 value into the field for the given field number at
+   * the given index. All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedUint32Element(fieldNumber, index, value) {
+    this.setRepeatedUint32Element_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writePackedUint32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all uint32 values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setPackedUint32Iterable(fieldNumber, values) {
+    const array = Array.from(values);
+    checkCriticalTypeUnsignedInt32Array(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writePackedUint32(fieldNumber, values);
+    });
+  }
+
+  /**
+   * Sets a single uint32 value into the field for the given field number at
+   * the given index. All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedUint32Element(fieldNumber, index, value) {
+    this.setRepeatedUint32Element_(
+        fieldNumber, index, value, (writer, fieldNumber, values) => {
+          writer.writeRepeatedUint32(fieldNumber, values);
+        });
+  }
+
+  /**
+   * Sets all uint32 values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setUnpackedUint32Iterable(fieldNumber, values) {
+    const array = Array.from(values);
+    checkCriticalTypeUnsignedInt32Array(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writeRepeatedUint32(fieldNumber, values);
+    });
+  }
+
+  /* Uint64 */
+
+  /**
+   * Adds a single uint64 value into the field for the given field number.
+   * All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  addPackedUint64Element(fieldNumber, value) {
+    this.addPackedInt64Element(fieldNumber, value);
+  }
+
+  /**
+   * Adds all uint64 values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  addPackedUint64Iterable(fieldNumber, values) {
+    this.addPackedInt64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * Adds a single uint64 value into the field for the given field number.
+   * All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  addUnpackedUint64Element(fieldNumber, value) {
+    this.addUnpackedInt64Element(fieldNumber, value);
+  }
+
+  /**
+   * Adds all uint64 values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  addUnpackedUint64Iterable(fieldNumber, values) {
+    this.addUnpackedInt64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * Sets a single uint64 value into the field for the given field number at
+   * the given index. All values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedUint64Element(fieldNumber, index, value) {
+    this.setPackedInt64Element(fieldNumber, index, value);
+  }
+
+  /**
+   * Sets all uint64 values into the field for the given field number.
+   * All these values will be encoded as packed values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  setPackedUint64Iterable(fieldNumber, values) {
+    this.setPackedInt64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * Sets a single uint64 value into the field for the given field number at
+   * the given index. All values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedUint64Element(fieldNumber, index, value) {
+    this.setUnpackedInt64Element(fieldNumber, index, value);
+  }
+
+  /**
+   * Sets all uint64 values into the field for the given field number.
+   * All these values will be encoded as unpacked values.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  setUnpackedUint64Iterable(fieldNumber, values) {
+    this.setUnpackedInt64Iterable(fieldNumber, values);
+  }
+
+  /* Bytes */
+
+  /**
+   * Sets all bytes values into the field for the given field number.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!ByteString>} values
+   */
+  setRepeatedBytesIterable(fieldNumber, values) {
+    const /** !Array<!ByteString> */ array = Array.from(values);
+    checkCriticalTypeByteStringArray(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writeRepeatedBytes(fieldNumber, values);
+    });
+  }
+
+  /**
+   * Adds all bytes values into the field for the given field number.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!ByteString>} values
+   */
+  addRepeatedBytesIterable(fieldNumber, values) {
+    const array = [...this.getRepeatedBytesArray_(fieldNumber), ...values];
+    checkCriticalTypeByteStringArray(array);
+    // Needs to set it back because the default empty array was not cached.
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writeRepeatedBytes(fieldNumber, values);
+    });
+  }
+
+  /**
+   * Sets a single bytes value into the field for the given field number at
+   * the given index.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!ByteString} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setRepeatedBytesElement(fieldNumber, index, value) {
+    checkCriticalTypeByteString(value);
+    const array = this.getRepeatedBytesArray_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    array[index] = value;
+    // Needs to set it back to set encoder.
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writeRepeatedBytes(fieldNumber, values);
+    });
+  }
+
+  /**
+   * Adds a single bytes value into the field for the given field number.
+   * @param {number} fieldNumber
+   * @param {!ByteString} value
+   */
+  addRepeatedBytesElement(fieldNumber, value) {
+    this.addRepeatedBytesIterable(fieldNumber, [value]);
+  }
+
+  /* String */
+
+  /**
+   * Sets all string values into the field for the given field number.
+   * @param {number} fieldNumber
+   * @param {!Iterable<string>} values
+   */
+  setRepeatedStringIterable(fieldNumber, values) {
+    const /** !Array<string> */ array = Array.from(values);
+    checkCriticalTypeStringArray(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writeRepeatedString(fieldNumber, values);
+    });
+  }
+
+  /**
+   * Adds all string values into the field for the given field number.
+   * @param {number} fieldNumber
+   * @param {!Iterable<string>} values
+   */
+  addRepeatedStringIterable(fieldNumber, values) {
+    const array = [...this.getRepeatedStringArray_(fieldNumber), ...values];
+    checkCriticalTypeStringArray(array);
+    // Needs to set it back because the default empty array was not cached.
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writeRepeatedString(fieldNumber, values);
+    });
+  }
+
+  /**
+   * Sets a single string value into the field for the given field number at
+   * the given index.
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {string} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setRepeatedStringElement(fieldNumber, index, value) {
+    checkCriticalTypeString(value);
+    const array = this.getRepeatedStringArray_(fieldNumber);
+    checkCriticalElementIndex(index, array.length);
+    array[index] = value;
+    // Needs to set it back to set encoder.
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writer.writeRepeatedString(fieldNumber, values);
+    });
+  }
+
+  /**
+   * Adds a single string value into the field for the given field number.
+   * @param {number} fieldNumber
+   * @param {string} value
+   */
+  addRepeatedStringElement(fieldNumber, value) {
+    this.addRepeatedStringIterable(fieldNumber, [value]);
+  }
+
+  /* Message */
+
+  /**
+   * Sets all message values into the field for the given field number.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!InternalMessage>} values
+   */
+  setRepeatedMessageIterable(fieldNumber, values) {
+    const /** !Array<!InternalMessage> */ array = Array.from(values);
+    checkCriticalTypeMessageArray(array);
+    this.setField_(fieldNumber, array, (writer, fieldNumber, values) => {
+      writeRepeatedMessage(writer, fieldNumber, values);
+    });
+  }
+
+  /**
+   * Adds all message values into the field for the given field number.
+   * @param {number} fieldNumber
+   * @param {!Iterable<!InternalMessage>} values
+   * @param {function(!LazyAccessor):!InternalMessage} instanceCreator
+   * @param {number=} pivot
+   */
+  addRepeatedMessageIterable(
+      fieldNumber, values, instanceCreator, pivot = undefined) {
+    const array = [
+      ...this.getRepeatedMessageArray_(fieldNumber, instanceCreator, pivot),
+      ...values,
+    ];
+    checkCriticalTypeMessageArray(array);
+    // Needs to set it back with the new array.
+    this.setField_(
+        fieldNumber, array,
+        (writer, fieldNumber, values) =>
+            writeRepeatedMessage(writer, fieldNumber, values));
+  }
+
+  /**
+   * Sets a single message value into the field for the given field number at
+   * the given index.
+   * @param {number} fieldNumber
+   * @param {!InternalMessage} value
+   * @param {function(!LazyAccessor):!InternalMessage} instanceCreator
+   * @param {number} index
+   * @param {number=} pivot
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setRepeatedMessageElement(
+      fieldNumber, value, instanceCreator, index, pivot = undefined) {
+    checkInstanceCreator(instanceCreator);
+    checkCriticalType(
+        value !== null, 'Given value is not a message instance: null');
+    const array =
+        this.getRepeatedMessageArray_(fieldNumber, instanceCreator, pivot);
+    checkCriticalElementIndex(index, array.length);
+    array[index] = value;
+  }
+
+  /**
+   * Adds a single message value into the field for the given field number.
+   * @param {number} fieldNumber
+   * @param {!InternalMessage} value
+   * @param {function(!LazyAccessor):!InternalMessage} instanceCreator
+   * @param {number=} pivot
+   */
+  addRepeatedMessageElement(
+      fieldNumber, value, instanceCreator, pivot = undefined) {
+    this.addRepeatedMessageIterable(
+        fieldNumber, [value], instanceCreator, pivot);
+  }
+}
+
+exports = LazyAccessor;

+ 266 - 0
js/experimental/runtime/kernel/lazy_accessor_compatibility_test.js

@@ -0,0 +1,266 @@
+/**
+ * @fileoverview Tests to make sure LazyAccessor can read data in a backward
+ * compatible way even when protobuf schema changes according to the rules
+ * defined in
+ * https://developers.google.com/protocol-buffers/docs/proto#updating and
+ * https://developers.google.com/protocol-buffers/docs/proto3#updating.
+ *
+ * third_party/protobuf/conformance/binary_json_conformance_suite.cc already
+ * covers many compatibility tests, this file covers only the tests not covered
+ * by binary_json_conformance_suite. Ultimately all of the tests in this file
+ * should be moved to binary_json_conformance_suite.
+ */
+goog.module('protobuf.binary.LazyAccessorCompatibilityTest');
+
+goog.setTestOnly();
+
+const ByteString = goog.require('protobuf.ByteString');
+const Int64 = goog.require('protobuf.Int64');
+const LazyAccessor = goog.require('protobuf.binary.LazyAccessor');
+const TestMessage = goog.require('protobuf.testing.binary.TestMessage');
+const {CHECK_CRITICAL_STATE} = goog.require('protobuf.internal.checks');
+
+/**
+ * @param {...number} bytes
+ * @return {!ArrayBuffer}
+ */
+function createArrayBuffer(...bytes) {
+  return new Uint8Array(bytes).buffer;
+}
+
+/**
+ * Returns the Unicode character codes of a string.
+ * @param {string} str
+ * @return {!Array<number>}
+ */
+function getCharacterCodes(str) {
+  return Array.from(str, (c) => c.charCodeAt(0));
+}
+
+describe('optional -> repeated compatibility', () => {
+  it('is maintained for scalars', () => {
+    const oldAccessor = LazyAccessor.createEmpty();
+    oldAccessor.setInt32(1, 1);
+    const serializedData = oldAccessor.serialize();
+    expect(serializedData).toEqual(createArrayBuffer(0x8, 0x1));
+
+    const newAccessor = LazyAccessor.fromArrayBuffer(serializedData);
+    expect(newAccessor.getRepeatedInt32Size(1)).toEqual(1);
+    expect(newAccessor.getRepeatedInt32Element(1, 0)).toEqual(1);
+  });
+
+  it('is maintained for messages', () => {
+    const message = new TestMessage(LazyAccessor.createEmpty());
+    message.setInt32(1, 1);
+
+    const oldAccessor = LazyAccessor.createEmpty();
+    oldAccessor.setMessage(1, message);
+    const serializedData = oldAccessor.serialize();
+    expect(serializedData).toEqual(createArrayBuffer(0xA, 0x2, 0x8, 0x1));
+
+    const newAccessor = LazyAccessor.fromArrayBuffer(serializedData);
+    expect(newAccessor.getRepeatedMessageSize(1, TestMessage.instanceCreator))
+        .toEqual(1);
+    expect(
+        newAccessor.getRepeatedMessageElement(1, TestMessage.instanceCreator, 0)
+            .serialize())
+        .toEqual(message.serialize());
+  });
+
+  it('is maintained for bytes', () => {
+    const message = new TestMessage(LazyAccessor.createEmpty());
+    message.setInt32(1, 1);
+
+    const oldAccessor = LazyAccessor.createEmpty();
+    oldAccessor.setBytes(
+        1, ByteString.fromArrayBuffer(createArrayBuffer(0xA, 0xB)));
+    const serializedData = oldAccessor.serialize();
+    expect(serializedData).toEqual(createArrayBuffer(0xA, 0x2, 0xA, 0xB));
+
+    const newAccessor = LazyAccessor.fromArrayBuffer(serializedData);
+    expect(newAccessor.getRepeatedBytesSize(1)).toEqual(1);
+    expect(newAccessor.getRepeatedBoolElement(1, 0))
+        .toEqual(ByteString.fromArrayBuffer(createArrayBuffer(0xA, 0xB)));
+  });
+
+  it('is maintained for strings', () => {
+    const oldAccessor = LazyAccessor.createEmpty();
+    oldAccessor.setString(1, 'hello');
+    const serializedData = oldAccessor.serialize();
+    expect(serializedData)
+        .toEqual(createArrayBuffer(0xA, 0x5, 0x68, 0x65, 0x6C, 0x6C, 0x6F));
+
+    const newAccessor = LazyAccessor.fromArrayBuffer(serializedData);
+    expect(newAccessor.getRepeatedStringSize(1)).toEqual(1);
+    expect(newAccessor.getRepeatedStringElement(1, 0)).toEqual('hello');
+  });
+});
+
+describe('LazyAccessor repeated -> optional compatibility', () => {
+  it('is maintained for unpacked scalars', () => {
+    const oldAccessor = LazyAccessor.createEmpty();
+    oldAccessor.addUnpackedInt32Element(1, 0);
+    oldAccessor.addUnpackedInt32Element(1, 1);
+    const serializedData = oldAccessor.serialize();
+    expect(serializedData).toEqual(createArrayBuffer(0x8, 0x0, 0x8, 0x1));
+
+    const newAccessor = LazyAccessor.fromArrayBuffer(serializedData);
+    expect(newAccessor.getInt32WithDefault(1)).toEqual(1);
+    expect(newAccessor.serialize()).toEqual(serializedData);
+  });
+
+  // repeated -> optional transformation is not supported for packed fields yet:
+  // go/proto-schema-repeated
+  it('is not maintained for packed scalars', () => {
+    const oldAccessor = LazyAccessor.createEmpty();
+    oldAccessor.addPackedInt32Element(1, 0);
+    oldAccessor.addPackedInt32Element(1, 1);
+    const serializedData = oldAccessor.serialize();
+    expect(serializedData).toEqual(createArrayBuffer(0xA, 0x2, 0x0, 0x1));
+
+    const newAccessor = LazyAccessor.fromArrayBuffer(serializedData);
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => newAccessor.getInt32WithDefault(1)).toThrow();
+    }
+  });
+
+  it('is maintained for messages', () => {
+    const message1 = new TestMessage(LazyAccessor.createEmpty());
+    message1.setInt32(1, 1);
+    const message2 = new TestMessage(LazyAccessor.createEmpty());
+    message2.setInt32(1, 2);
+    message2.setInt32(2, 3);
+
+    const oldAccessor = LazyAccessor.createEmpty();
+    oldAccessor.addRepeatedMessageElement(
+        1, message1, TestMessage.instanceCreator);
+    oldAccessor.addRepeatedMessageElement(
+        1, message2, TestMessage.instanceCreator);
+    const serializedData = oldAccessor.serialize();
+    expect(serializedData)
+        .toEqual(createArrayBuffer(
+            0xA, 0x2, 0x8, 0x1, 0xA, 0x4, 0x8, 0x2, 0x10, 0x3));
+
+    const newAccessor = LazyAccessor.fromArrayBuffer(serializedData);
+    // Values from message1 and message2 have been merged
+    const newMessage = newAccessor.getMessage(1, TestMessage.instanceCreator);
+    expect(newMessage.getRepeatedInt32Size(1)).toEqual(2);
+    expect(newMessage.getRepeatedInt32Element(1, 0)).toEqual(1);
+    expect(newMessage.getRepeatedInt32Element(1, 1)).toEqual(2);
+    expect(newMessage.getInt32WithDefault(2)).toEqual(3);
+    expect(newMessage.serialize())
+        .toEqual(createArrayBuffer(0x8, 0x1, 0x8, 0x2, 0x10, 0x3));
+  });
+
+  it('is maintained for bytes', () => {
+    const oldAccessor = LazyAccessor.createEmpty();
+    oldAccessor.addRepeatedBytesElement(
+        1, ByteString.fromArrayBuffer(createArrayBuffer(0xA, 0xB)));
+    oldAccessor.addRepeatedBytesElement(
+        1, ByteString.fromArrayBuffer(createArrayBuffer(0xC, 0xD)));
+    const serializedData = oldAccessor.serialize();
+    expect(serializedData)
+        .toEqual(createArrayBuffer(0xA, 0x2, 0xA, 0xB, 0xA, 0x2, 0xC, 0xD));
+
+    const newAccessor = LazyAccessor.fromArrayBuffer(serializedData);
+    expect(newAccessor.getBytesWithDefault(1))
+        .toEqual(ByteString.fromArrayBuffer(createArrayBuffer(0xC, 0xD)));
+    expect(newAccessor.serialize()).toEqual(serializedData);
+  });
+
+  it('is maintained for strings', () => {
+    const oldAccessor = LazyAccessor.createEmpty();
+    oldAccessor.addRepeatedStringElement(1, 'hello');
+    oldAccessor.addRepeatedStringElement(1, 'world');
+    const serializedData = oldAccessor.serialize();
+    expect(serializedData)
+        .toEqual(createArrayBuffer(
+            0xA, 0x5, ...getCharacterCodes('hello'), 0xA, 0x5,
+            ...getCharacterCodes('world')));
+
+    const newAccessor = LazyAccessor.fromArrayBuffer(serializedData);
+    expect(newAccessor.getStringWithDefault(1)).toEqual('world');
+    expect(newAccessor.serialize()).toEqual(serializedData);
+  });
+});
+
+describe('Type change', () => {
+  it('is supported for fixed32 -> sfixed32', () => {
+    const oldAccessor = LazyAccessor.createEmpty();
+    oldAccessor.setFixed32(1, 4294967295);
+    const serializedData = oldAccessor.serialize();
+    expect(serializedData)
+        .toEqual(createArrayBuffer(0xD, 0xFF, 0xFF, 0xFF, 0xFF));
+
+    const newAccessor = LazyAccessor.fromArrayBuffer(serializedData);
+    expect(newAccessor.getSfixed32WithDefault(1)).toEqual(-1);
+    expect(newAccessor.serialize()).toEqual(serializedData);
+  });
+
+  it('is supported for sfixed32 -> fixed32', () => {
+    const oldAccessor = LazyAccessor.createEmpty();
+    oldAccessor.setSfixed32(1, -1);
+    const serializedData = oldAccessor.serialize();
+    expect(serializedData)
+        .toEqual(createArrayBuffer(0xD, 0xFF, 0xFF, 0xFF, 0xFF));
+
+    const newAccessor = LazyAccessor.fromArrayBuffer(serializedData);
+    expect(newAccessor.getFixed32WithDefault(1)).toEqual(4294967295);
+    expect(newAccessor.serialize()).toEqual(serializedData);
+  });
+
+  it('is supported for fixed64 -> sfixed64', () => {
+    const oldAccessor = LazyAccessor.createEmpty();
+    oldAccessor.setFixed64(1, Int64.fromHexString('0xFFFFFFFFFFFFFFFF'));
+    const serializedData = oldAccessor.serialize();
+    expect(serializedData)
+        .toEqual(createArrayBuffer(
+            0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF));
+
+    const newAccessor = LazyAccessor.fromArrayBuffer(serializedData);
+    expect(newAccessor.getSfixed64WithDefault(1)).toEqual(Int64.fromInt(-1));
+    expect(newAccessor.serialize()).toEqual(serializedData);
+  });
+
+  it('is supported for sfixed64 -> fixed64', () => {
+    const oldAccessor = LazyAccessor.createEmpty();
+    oldAccessor.setSfixed64(1, Int64.fromInt(-1));
+    const serializedData = oldAccessor.serialize();
+    expect(serializedData)
+        .toEqual(createArrayBuffer(
+            0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF));
+
+    const newAccessor = LazyAccessor.fromArrayBuffer(serializedData);
+    expect(newAccessor.getFixed64WithDefault(1))
+        .toEqual(Int64.fromHexString('0xFFFFFFFFFFFFFFFF'));
+    expect(newAccessor.serialize()).toEqual(serializedData);
+  });
+
+  it('is supported for bytes -> message', () => {
+    const oldAccessor = LazyAccessor.createEmpty();
+    oldAccessor.setBytes(
+        1, ByteString.fromArrayBuffer(createArrayBuffer(0x8, 0x1)));
+    const serializedData = oldAccessor.serialize();
+    expect(serializedData).toEqual(createArrayBuffer(0xA, 0x2, 0x8, 0x1));
+
+    const newAccessor = LazyAccessor.fromArrayBuffer(serializedData);
+    const message = newAccessor.getMessage(1, TestMessage.instanceCreator);
+    expect(message.getInt32WithDefault(1)).toEqual(1);
+    expect(message.serialize()).toEqual(createArrayBuffer(0x8, 0x1));
+    expect(newAccessor.serialize()).toEqual(serializedData);
+  });
+
+  it('is supported for message -> bytes', () => {
+    const oldAccessor = LazyAccessor.createEmpty();
+    const message = new TestMessage(LazyAccessor.createEmpty());
+    message.setInt32(1, 1);
+    oldAccessor.setMessage(1, message);
+    const serializedData = oldAccessor.serialize();
+    expect(serializedData).toEqual(createArrayBuffer(0xA, 0x2, 0x8, 0x1));
+
+    const newAccessor = LazyAccessor.fromArrayBuffer(serializedData);
+    expect(newAccessor.getBytesWithDefault(1))
+        .toEqual(ByteString.fromArrayBuffer(createArrayBuffer(0x8, 0x1)));
+    expect(newAccessor.serialize()).toEqual(serializedData);
+  });
+});

+ 7443 - 0
js/experimental/runtime/kernel/lazy_accessor_repeated_test.js

@@ -0,0 +1,7443 @@
+/**
+ * @fileoverview Tests for repeated methods in lazy_accessor.js.
+ */
+goog.module('protobuf.binary.LazyAccessorTest');
+
+goog.setTestOnly();
+
+const ByteString = goog.require('protobuf.ByteString');
+const Int64 = goog.require('protobuf.Int64');
+const InternalMessage = goog.require('protobuf.binary.InternalMessage');
+const LazyAccessor = goog.require('protobuf.binary.LazyAccessor');
+const TestMessage = goog.require('protobuf.testing.binary.TestMessage');
+// Note to the reader:
+// Since the lazy accessor behavior changes with the checking level some of the
+// tests in this file have to know which checking level is enable to make
+// correct assertions.
+const {CHECK_CRITICAL_STATE} = goog.require('protobuf.internal.checks');
+
+/**
+ * @param {...number} bytes
+ * @return {!ArrayBuffer}
+ */
+function createArrayBuffer(...bytes) {
+  return new Uint8Array(bytes).buffer;
+}
+
+/**
+ * Expects the Iterable instance yield the same values as the expected array.
+ * @param {!Iterable<T>} iterable
+ * @param {!Array<T>} expected
+ * @template T
+ * TODO: Implement this as a custom matcher.
+ */
+function expectEqualToArray(iterable, expected) {
+  const array = Array.from(iterable);
+  expect(array).toEqual(expected);
+}
+
+/**
+ * Expects the Iterable instance yield qualified values.
+ * @param {!Iterable<T>} iterable
+ * @param {(function(T): boolean)=} verify
+ * @template T
+ */
+function expectQualifiedIterable(iterable, verify) {
+  if (verify) {
+    for (const value of iterable) {
+      expect(verify(value)).toBe(true);
+    }
+  }
+}
+
+/**
+ * Expects the Iterable instance yield the same values as the expected array of
+ * messages.
+ * @param {!Iterable<!TestMessage>} iterable
+ * @param {!Array<!TestMessage>} expected
+ * @template T
+ * TODO: Implement this as a custom matcher.
+ */
+function expectEqualToMessageArray(iterable, expected) {
+  const array = Array.from(iterable);
+  expect(array.length).toEqual(expected.length);
+  for (let i = 0; i < array.length; i++) {
+    const value = array[i].getBoolWithDefault(1, false);
+    const expectedValue = expected[i].getBoolWithDefault(1, false);
+    expect(value).toBe(expectedValue);
+  }
+}
+
+describe('LazyAccessor for repeated boolean does', () => {
+  it('return empty array for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+    expectEqualToArray(accessor.getRepeatedBoolIterable(1), []);
+  });
+
+  it('ensure not the same instance returned for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const list1 = accessor.getRepeatedBoolIterable(1);
+    const list2 = accessor.getRepeatedBoolIterable(1);
+    expect(list1).not.toBe(list2);
+  });
+
+  it('return size for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+    expect(accessor.getRepeatedBoolSize(1)).toEqual(0);
+  });
+
+  it('return unpacked values from the input', () => {
+    const bytes = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expectEqualToArray(accessor.getRepeatedBoolIterable(1), [true, false]);
+  });
+
+  it('ensure not the same instance returned for unpacked values', () => {
+    const bytes = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const list1 = accessor.getRepeatedBoolIterable(1);
+    const list2 = accessor.getRepeatedBoolIterable(1);
+    expect(list1).not.toBe(list2);
+  });
+
+  it('return unpacked multibytes values from the input', () => {
+    const bytes = createArrayBuffer(0x08, 0x80, 0x01, 0x08, 0x80, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expectEqualToArray(accessor.getRepeatedBoolIterable(1), [true, false]);
+  });
+
+  it('return for adding single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    accessor.addUnpackedBoolElement(1, true);
+    expectEqualToArray(accessor.getRepeatedBoolIterable(1), [true]);
+    accessor.addUnpackedBoolElement(1, false);
+    expectEqualToArray(accessor.getRepeatedBoolIterable(1), [true, false]);
+  });
+
+  it('return for adding unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+    accessor.addUnpackedBoolIterable(1, [true]);
+    expectEqualToArray(accessor.getRepeatedBoolIterable(1), [true]);
+    accessor.addUnpackedBoolIterable(1, [false]);
+    expectEqualToArray(accessor.getRepeatedBoolIterable(1), [true, false]);
+  });
+
+  it('return for setting single unpacked value', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x00, 0x08, 0x01));
+    accessor.setUnpackedBoolElement(1, 0, true);
+    expectEqualToArray(accessor.getRepeatedBoolIterable(1), [true, true]);
+  });
+
+  it('return for setting unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+    accessor.setUnpackedBoolIterable(1, [true]);
+    expectEqualToArray(accessor.getRepeatedBoolIterable(1), [true]);
+    accessor.setUnpackedBoolIterable(1, [false]);
+    expectEqualToArray(accessor.getRepeatedBoolIterable(1), [false]);
+  });
+
+  it('encode for adding single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const bytes = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
+    accessor.addUnpackedBoolElement(1, true);
+    accessor.addUnpackedBoolElement(1, false);
+    expect(accessor.serialize()).toEqual(bytes);
+  });
+
+  it('encode for adding unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const bytes = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
+    accessor.addUnpackedBoolIterable(1, [true, false]);
+    expect(accessor.serialize()).toEqual(bytes);
+  });
+
+  it('encode for setting single unpacked value', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x0A, 0x02, 0x00, 0x01));
+    const bytes = createArrayBuffer(0x08, 0x01, 0x08, 0x01);
+    accessor.setUnpackedBoolElement(1, 0, true);
+    expect(accessor.serialize()).toEqual(bytes);
+  });
+
+  it('encode for setting unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const bytes = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
+    accessor.setUnpackedBoolIterable(1, [true, false]);
+    expect(accessor.serialize()).toEqual(bytes);
+  });
+
+  it('return packed values from the input', () => {
+    const bytes = createArrayBuffer(0x0A, 0x02, 0x01, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expectEqualToArray(accessor.getRepeatedBoolIterable(1), [true, false]);
+  });
+
+  it('ensure not the same instance returned for packed values', () => {
+    const bytes = createArrayBuffer(0x0A, 0x02, 0x01, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const list1 = accessor.getRepeatedBoolIterable(1);
+    const list2 = accessor.getRepeatedBoolIterable(1);
+    expect(list1).not.toBe(list2);
+  });
+
+  it('return packed multibytes values from the input', () => {
+    const bytes = createArrayBuffer(0x0A, 0x04, 0x80, 0x01, 0x80, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expectEqualToArray(accessor.getRepeatedBoolIterable(1), [true, false]);
+  });
+
+  it('return for adding single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    accessor.addPackedBoolElement(1, true);
+    expectEqualToArray(accessor.getRepeatedBoolIterable(1), [true]);
+    accessor.addPackedBoolElement(1, false);
+    expectEqualToArray(accessor.getRepeatedBoolIterable(1), [true, false]);
+  });
+
+  it('return for adding packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+    accessor.addPackedBoolIterable(1, [true]);
+    expectEqualToArray(accessor.getRepeatedBoolIterable(1), [true]);
+    accessor.addPackedBoolIterable(1, [false]);
+    expectEqualToArray(accessor.getRepeatedBoolIterable(1), [true, false]);
+  });
+
+  it('return for setting single packed value', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x00, 0x08, 0x01));
+    accessor.setPackedBoolElement(1, 0, true);
+    expectEqualToArray(accessor.getRepeatedBoolIterable(1), [true, true]);
+  });
+
+  it('return for setting packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+    accessor.setPackedBoolIterable(1, [true]);
+    expectEqualToArray(accessor.getRepeatedBoolIterable(1), [true]);
+    accessor.setPackedBoolIterable(1, [false]);
+    expectEqualToArray(accessor.getRepeatedBoolIterable(1), [false]);
+  });
+
+  it('encode for adding single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const bytes = createArrayBuffer(0x0A, 0x02, 0x01, 0x00);
+    accessor.addPackedBoolElement(1, true);
+    accessor.addPackedBoolElement(1, false);
+    expect(accessor.serialize()).toEqual(bytes);
+  });
+
+  it('encode for adding packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const bytes = createArrayBuffer(0x0A, 0x02, 0x01, 0x00);
+    accessor.addPackedBoolIterable(1, [true, false]);
+    expect(accessor.serialize()).toEqual(bytes);
+  });
+
+  it('encode for setting single packed value', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x00, 0x08, 0x01));
+    const bytes = createArrayBuffer(0x0A, 0x02, 0x01, 0x01);
+    accessor.setPackedBoolElement(1, 0, true);
+    expect(accessor.serialize()).toEqual(bytes);
+  });
+
+  it('encode for setting packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const bytes = createArrayBuffer(0x0A, 0x02, 0x01, 0x00);
+    accessor.setPackedBoolIterable(1, [true, false]);
+    expect(accessor.serialize()).toEqual(bytes);
+  });
+
+  it('return combined values from the input', () => {
+    const bytes =
+        createArrayBuffer(0x08, 0x01, 0x0A, 0x02, 0x01, 0x00, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expectEqualToArray(
+        accessor.getRepeatedBoolIterable(1), [true, true, false, false]);
+  });
+
+  it('return the repeated field element from the input', () => {
+    const bytes = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getRepeatedBoolElement(
+               /* fieldNumber= */ 1, /* index= */ 0))
+        .toEqual(true);
+    expect(accessor.getRepeatedBoolElement(
+               /* fieldNumber= */ 1, /* index= */ 1))
+        .toEqual(false);
+  });
+
+  it('return the size from the input', () => {
+    const bytes = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getRepeatedBoolSize(1)).toEqual(2);
+  });
+
+  it('fail when getting unpacked bool value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedBoolIterable(1);
+      }).toThrowError('Expected wire type: 0 but found: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expectEqualToArray(accessor.getRepeatedBoolIterable(1), [true]);
+    }
+  });
+
+  it('fail when adding unpacked bool values with number value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeBoolean = /** @type {boolean} */ (/** @type {*} */ (2));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedBoolIterable(1, [fakeBoolean]))
+          .toThrowError('Must be a boolean, but got: 2');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedBoolIterable(1, [fakeBoolean]);
+      expectEqualToArray(accessor.getRepeatedBoolIterable(1), [fakeBoolean]);
+    }
+  });
+
+  it('fail when adding single unpacked bool value with number value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeBoolean = /** @type {boolean} */ (/** @type {*} */ (2));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedBoolElement(1, fakeBoolean))
+          .toThrowError('Must be a boolean, but got: 2');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedBoolElement(1, fakeBoolean);
+      expectEqualToArray(accessor.getRepeatedBoolIterable(1), [fakeBoolean]);
+    }
+  });
+
+  it('fail when setting unpacked bool values with number value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeBoolean = /** @type {boolean} */ (/** @type {*} */ (2));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedBoolIterable(1, [fakeBoolean]))
+          .toThrowError('Must be a boolean, but got: 2');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedBoolIterable(1, [fakeBoolean]);
+      expectEqualToArray(accessor.getRepeatedBoolIterable(1), [fakeBoolean]);
+    }
+  });
+
+  it('fail when setting single unpacked bool value with number value', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x00));
+    const fakeBoolean = /** @type {boolean} */ (/** @type {*} */ (2));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedBoolElement(1, 0, fakeBoolean))
+          .toThrowError('Must be a boolean, but got: 2');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedBoolElement(1, 0, fakeBoolean);
+      expectEqualToArray(accessor.getRepeatedBoolIterable(1), [fakeBoolean]);
+    }
+  });
+
+  it('fail when adding packed bool values with number value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeBoolean = /** @type {boolean} */ (/** @type {*} */ (2));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedBoolIterable(1, [fakeBoolean]))
+          .toThrowError('Must be a boolean, but got: 2');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedBoolIterable(1, [fakeBoolean]);
+      expectEqualToArray(accessor.getRepeatedBoolIterable(1), [fakeBoolean]);
+    }
+  });
+
+  it('fail when adding single packed bool value with number value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeBoolean = /** @type {boolean} */ (/** @type {*} */ (2));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedBoolElement(1, fakeBoolean))
+          .toThrowError('Must be a boolean, but got: 2');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedBoolElement(1, fakeBoolean);
+      expectEqualToArray(accessor.getRepeatedBoolIterable(1), [fakeBoolean]);
+    }
+  });
+
+  it('fail when setting packed bool values with number value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeBoolean = /** @type {boolean} */ (/** @type {*} */ (2));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedBoolIterable(1, [fakeBoolean]))
+          .toThrowError('Must be a boolean, but got: 2');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedBoolIterable(1, [fakeBoolean]);
+      expectEqualToArray(accessor.getRepeatedBoolIterable(1), [fakeBoolean]);
+    }
+  });
+
+  it('fail when setting single packed bool value with number value', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x00));
+    const fakeBoolean = /** @type {boolean} */ (/** @type {*} */ (2));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedBoolElement(1, 0, fakeBoolean))
+          .toThrowError('Must be a boolean, but got: 2');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedBoolElement(1, 0, fakeBoolean);
+      expectEqualToArray(accessor.getRepeatedBoolIterable(1), [fakeBoolean]);
+    }
+  });
+
+  it('fail when setting single unpacked with out-of-bound index', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedBoolElement(1, 1, true))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedBoolElement(1, 1, true);
+      expectEqualToArray(accessor.getRepeatedBoolIterable(1), [false, true]);
+    }
+  });
+
+  it('fail when setting single packed with out-of-bound index', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedBoolElement(1, 1, true))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedBoolElement(1, 1, true);
+      expectEqualToArray(accessor.getRepeatedBoolIterable(1), [false, true]);
+    }
+  });
+
+  it('fail when getting element with out-of-range index', () => {
+    const accessor = LazyAccessor.createEmpty();
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedBoolElement(
+            /* fieldNumber= */ 1, /* index= */ 0);
+      }).toThrowError('Index out of bounds: index: 0 size: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getRepeatedBoolElement(
+                 /* fieldNumber= */ 1, /* index= */ 0))
+          .toBe(undefined);
+    }
+  });
+});
+
+describe('LazyAccessor for repeated double does', () => {
+  const value1 = 1;
+  const value2 = 0;
+
+  const unpackedValue1Value2 = createArrayBuffer(
+      0x09,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0xF0,
+      0x3F,  // value1
+      0x09,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,  // value2
+  );
+  const unpackedValue2Value1 = createArrayBuffer(
+      0x09,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,  // value1
+      0x09,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0xF0,
+      0x3F,  // value2
+  );
+
+  const packedValue1Value2 = createArrayBuffer(
+      0x0A,
+      0x10,  // tag
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0xF0,
+      0x3F,  // value1
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,  // value2
+  );
+  const packedValue2Value1 = createArrayBuffer(
+      0x0A,
+      0x10,  // tag
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,  // value1
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0xF0,
+      0x3F,  // value2
+  );
+
+  it('return empty array for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list = accessor.getRepeatedDoubleIterable(1);
+
+    expectEqualToArray(list, []);
+  });
+
+  it('ensure not the same instance returned for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list1 = accessor.getRepeatedDoubleIterable(1);
+    const list2 = accessor.getRepeatedDoubleIterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('return size for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const size = accessor.getRepeatedDoubleSize(1);
+
+    expect(size).toEqual(0);
+  });
+
+  it('return unpacked values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list = accessor.getRepeatedDoubleIterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for unpacked values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list1 = accessor.getRepeatedDoubleIterable(1);
+    const list2 = accessor.getRepeatedDoubleIterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedDoubleElement(1, value1);
+    const list1 = accessor.getRepeatedDoubleIterable(1);
+    accessor.addUnpackedDoubleElement(1, value2);
+    const list2 = accessor.getRepeatedDoubleIterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedDoubleIterable(1, [value1]);
+    const list1 = accessor.getRepeatedDoubleIterable(1);
+    accessor.addUnpackedDoubleIterable(1, [value2]);
+    const list2 = accessor.getRepeatedDoubleIterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    accessor.setUnpackedDoubleElement(1, 1, value1);
+    const list = accessor.getRepeatedDoubleIterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedDoubleIterable(1, [value1]);
+    const list = accessor.getRepeatedDoubleIterable(1);
+
+    expectEqualToArray(list, [value1]);
+  });
+
+  it('encode for adding single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedDoubleElement(1, value1);
+    accessor.addUnpackedDoubleElement(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for adding unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedDoubleIterable(1, [value1]);
+    accessor.addUnpackedDoubleIterable(1, [value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for setting single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setUnpackedDoubleElement(1, 0, value2);
+    accessor.setUnpackedDoubleElement(1, 1, value1);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue2Value1);
+  });
+
+  it('encode for setting unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedDoubleIterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('return packed values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list = accessor.getRepeatedDoubleIterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for packed values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list1 = accessor.getRepeatedDoubleIterable(1);
+    const list2 = accessor.getRepeatedDoubleIterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedDoubleElement(1, value1);
+    const list1 = accessor.getRepeatedDoubleIterable(1);
+    accessor.addPackedDoubleElement(1, value2);
+    const list2 = accessor.getRepeatedDoubleIterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedDoubleIterable(1, [value1]);
+    const list1 = accessor.getRepeatedDoubleIterable(1);
+    accessor.addPackedDoubleIterable(1, [value2]);
+    const list2 = accessor.getRepeatedDoubleIterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedDoubleElement(1, 1, value1);
+    const list = accessor.getRepeatedDoubleIterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedDoubleIterable(1, [value1]);
+    const list1 = accessor.getRepeatedDoubleIterable(1);
+    accessor.setPackedDoubleIterable(1, [value2]);
+    const list2 = accessor.getRepeatedDoubleIterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value2]);
+  });
+
+  it('encode for adding single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedDoubleElement(1, value1);
+    accessor.addPackedDoubleElement(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for adding packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedDoubleIterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for setting single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedDoubleElement(1, 0, value2);
+    accessor.setPackedDoubleElement(1, 1, value1);
+
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue2Value1);
+  });
+
+  it('encode for setting packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedDoubleIterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('return combined values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09,
+        0x00,
+        0x00,
+        0x00,
+        0x00,
+        0x00,
+        0x00,
+        0xF0,
+        0x3F,  // value1
+        0x0A,
+        0x10,  // tag
+        0x00,
+        0x00,
+        0x00,
+        0x00,
+        0x00,
+        0x00,
+        0xF0,
+        0x3F,  // value1
+        0x00,
+        0x00,
+        0x00,
+        0x00,
+        0x00,
+        0x00,
+        0x00,
+        0x00,  // value2
+        0x09,
+        0x00,
+        0x00,
+        0x00,
+        0x00,
+        0x00,
+        0x00,
+        0x00,
+        0x00,  // value2
+        ));
+
+    const list = accessor.getRepeatedDoubleIterable(1);
+
+    expectEqualToArray(list, [value1, value1, value2, value2]);
+  });
+
+  it('return the repeated field element from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const result1 = accessor.getRepeatedDoubleElement(
+        /* fieldNumber= */ 1, /* index= */ 0);
+    const result2 = accessor.getRepeatedDoubleElement(
+        /* fieldNumber= */ 1, /* index= */ 1);
+
+    expect(result1).toEqual(value1);
+    expect(result2).toEqual(value2);
+  });
+
+  it('return the size from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const size = accessor.getRepeatedDoubleSize(1);
+
+    expect(size).toEqual(2);
+  });
+
+  it('fail when getting unpacked double value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedDoubleIterable(1);
+      }).toThrowError('Expected wire type: 1 but found: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expectEqualToArray(
+          accessor.getRepeatedDoubleIterable(1), [2.937446524422997e-306]);
+    }
+  });
+
+  it('fail when adding unpacked double values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeDouble = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedDoubleIterable(1, [fakeDouble]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedDoubleIterable(1, [fakeDouble]);
+      expectEqualToArray(accessor.getRepeatedDoubleIterable(1), [fakeDouble]);
+    }
+  });
+
+  it('fail when adding single unpacked double value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeDouble = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedDoubleElement(1, fakeDouble))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedDoubleElement(1, fakeDouble);
+      expectEqualToArray(accessor.getRepeatedDoubleIterable(1), [fakeDouble]);
+    }
+  });
+
+  it('fail when setting unpacked double values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeDouble = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedDoubleIterable(1, [fakeDouble]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedDoubleIterable(1, [fakeDouble]);
+      expectEqualToArray(accessor.getRepeatedDoubleIterable(1), [fakeDouble]);
+    }
+  });
+
+  it('fail when setting single unpacked double value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00));
+    const fakeDouble = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedDoubleElement(1, 0, fakeDouble))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedDoubleElement(1, 0, fakeDouble);
+      expectEqualToArray(accessor.getRepeatedDoubleIterable(1), [fakeDouble]);
+    }
+  });
+
+  it('fail when adding packed double values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeDouble = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedDoubleIterable(1, [fakeDouble]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedDoubleIterable(1, [fakeDouble]);
+      expectEqualToArray(accessor.getRepeatedDoubleIterable(1), [fakeDouble]);
+    }
+  });
+
+  it('fail when adding single packed double value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeDouble = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedDoubleElement(1, fakeDouble))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedDoubleElement(1, fakeDouble);
+      expectEqualToArray(accessor.getRepeatedDoubleIterable(1), [fakeDouble]);
+    }
+  });
+
+  it('fail when setting packed double values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeDouble = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedDoubleIterable(1, [fakeDouble]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedDoubleIterable(1, [fakeDouble]);
+      expectEqualToArray(accessor.getRepeatedDoubleIterable(1), [fakeDouble]);
+    }
+  });
+
+  it('fail when setting single packed double value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+    const fakeDouble = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedDoubleElement(1, 0, fakeDouble))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedDoubleElement(1, 0, fakeDouble);
+      expectEqualToArray(accessor.getRepeatedDoubleIterable(1), [fakeDouble]);
+    }
+  });
+
+  it('fail when setting single unpacked with out-of-bound index', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedDoubleElement(1, 1, 1))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedDoubleElement(1, 1, 1);
+      expectEqualToArray(accessor.getRepeatedDoubleIterable(1), [0, 1]);
+    }
+  });
+
+  it('fail when setting single packed with out-of-bound index', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedDoubleElement(1, 1, 1))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedDoubleElement(1, 1, 1);
+      expectEqualToArray(accessor.getRepeatedDoubleIterable(1), [0, 1]);
+    }
+  });
+
+  it('fail when getting element with out-of-range index', () => {
+    const accessor = LazyAccessor.createEmpty();
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedDoubleElement(
+            /* fieldNumber= */ 1, /* index= */ 0);
+      }).toThrowError('Index out of bounds: index: 0 size: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getRepeatedDoubleElement(
+                 /* fieldNumber= */ 1, /* index= */ 0))
+          .toBe(undefined);
+    }
+  });
+});
+
+describe('LazyAccessor for repeated fixed32 does', () => {
+  const value1 = 1;
+  const value2 = 0;
+
+  const unpackedValue1Value2 = createArrayBuffer(
+      0x0D, 0x01, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00);
+  const unpackedValue2Value1 = createArrayBuffer(
+      0x0D, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x01, 0x00, 0x00, 0x00);
+
+  const packedValue1Value2 = createArrayBuffer(
+      0x0A, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+  const packedValue2Value1 = createArrayBuffer(
+      0x0A, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00);
+
+  it('return empty array for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list = accessor.getRepeatedFixed32Iterable(1);
+
+    expectEqualToArray(list, []);
+  });
+
+  it('ensure not the same instance returned for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list1 = accessor.getRepeatedFixed32Iterable(1);
+    const list2 = accessor.getRepeatedFixed32Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('return size for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const size = accessor.getRepeatedFixed32Size(1);
+
+    expect(size).toEqual(0);
+  });
+
+  it('return unpacked values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list = accessor.getRepeatedFixed32Iterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for unpacked values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list1 = accessor.getRepeatedFixed32Iterable(1);
+    const list2 = accessor.getRepeatedFixed32Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedFixed32Element(1, value1);
+    const list1 = accessor.getRepeatedFixed32Iterable(1);
+    accessor.addUnpackedFixed32Element(1, value2);
+    const list2 = accessor.getRepeatedFixed32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedFixed32Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedFixed32Iterable(1);
+    accessor.addUnpackedFixed32Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedFixed32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    accessor.setUnpackedFixed32Element(1, 1, value1);
+    const list = accessor.getRepeatedFixed32Iterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedFixed32Iterable(1, [value1]);
+    const list = accessor.getRepeatedFixed32Iterable(1);
+
+    expectEqualToArray(list, [value1]);
+  });
+
+  it('encode for adding single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedFixed32Element(1, value1);
+    accessor.addUnpackedFixed32Element(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for adding unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedFixed32Iterable(1, [value1]);
+    accessor.addUnpackedFixed32Iterable(1, [value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for setting single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setUnpackedFixed32Element(1, 0, value2);
+    accessor.setUnpackedFixed32Element(1, 1, value1);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue2Value1);
+  });
+
+  it('encode for setting unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedFixed32Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('return packed values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list = accessor.getRepeatedFixed32Iterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for packed values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list1 = accessor.getRepeatedFixed32Iterable(1);
+    const list2 = accessor.getRepeatedFixed32Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedFixed32Element(1, value1);
+    const list1 = accessor.getRepeatedFixed32Iterable(1);
+    accessor.addPackedFixed32Element(1, value2);
+    const list2 = accessor.getRepeatedFixed32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedFixed32Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedFixed32Iterable(1);
+    accessor.addPackedFixed32Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedFixed32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedFixed32Element(1, 1, value1);
+    const list = accessor.getRepeatedFixed32Iterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedFixed32Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedFixed32Iterable(1);
+    accessor.setPackedFixed32Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedFixed32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value2]);
+  });
+
+  it('encode for adding single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedFixed32Element(1, value1);
+    accessor.addPackedFixed32Element(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for adding packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedFixed32Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for setting single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedFixed32Element(1, 0, value2);
+    accessor.setPackedFixed32Element(1, 1, value1);
+
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue2Value1);
+  });
+
+  it('encode for setting packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedFixed32Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('return combined values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x0D,
+        0x01,
+        0x00,
+        0x00,
+        0x00,  // value1
+        0x0A,
+        0x08,  // tag
+        0x01,
+        0x00,
+        0x00,
+        0x00,  // value1
+        0x00,
+        0x00,
+        0x00,
+        0x00,  // value2
+        0x0D,
+        0x00,
+        0x00,
+        0x00,
+        0x00,  // value2
+        ));
+
+    const list = accessor.getRepeatedFixed32Iterable(1);
+
+    expectEqualToArray(list, [value1, value1, value2, value2]);
+  });
+
+  it('return the repeated field element from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const result1 = accessor.getRepeatedFixed32Element(
+        /* fieldNumber= */ 1, /* index= */ 0);
+    const result2 = accessor.getRepeatedFixed32Element(
+        /* fieldNumber= */ 1, /* index= */ 1);
+
+    expect(result1).toEqual(value1);
+    expect(result2).toEqual(value2);
+  });
+
+  it('return the size from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const size = accessor.getRepeatedFixed32Size(1);
+
+    expect(size).toEqual(2);
+  });
+
+  it('fail when getting unpacked fixed32 value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x08, 0x80, 0x80, 0x80, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedFixed32Iterable(1);
+      }).toThrowError('Expected wire type: 5 but found: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expectQualifiedIterable(
+          accessor.getRepeatedFixed32Iterable(1),
+          (value) => Number.isInteger(value));
+    }
+  });
+
+  it('fail when adding unpacked fixed32 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeFixed32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedFixed32Iterable(1, [fakeFixed32]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedFixed32Iterable(1, [fakeFixed32]);
+      expectQualifiedIterable(accessor.getRepeatedFixed32Iterable(1));
+    }
+  });
+
+  it('fail when adding single unpacked fixed32 value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeFixed32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedFixed32Element(1, fakeFixed32))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedFixed32Element(1, fakeFixed32);
+      expectQualifiedIterable(accessor.getRepeatedFixed32Iterable(1));
+    }
+  });
+
+  it('fail when setting unpacked fixed32 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeFixed32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedFixed32Iterable(1, [fakeFixed32]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedFixed32Iterable(1, [fakeFixed32]);
+      expectQualifiedIterable(accessor.getRepeatedFixed32Iterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked fixed32 value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x08, 0x80, 0x80, 0x80, 0x00));
+    const fakeFixed32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedFixed32Element(1, 0, fakeFixed32))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedFixed32Element(1, 0, fakeFixed32);
+      expectQualifiedIterable(
+          accessor.getRepeatedFixed32Iterable(1),
+      );
+    }
+  });
+
+  it('fail when adding packed fixed32 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeFixed32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedFixed32Iterable(1, [fakeFixed32]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedFixed32Iterable(1, [fakeFixed32]);
+      expectQualifiedIterable(accessor.getRepeatedFixed32Iterable(1));
+    }
+  });
+
+  it('fail when adding single packed fixed32 value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeFixed32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedFixed32Element(1, fakeFixed32))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedFixed32Element(1, fakeFixed32);
+      expectQualifiedIterable(accessor.getRepeatedFixed32Iterable(1));
+    }
+  });
+
+  it('fail when setting packed fixed32 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeFixed32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedFixed32Iterable(1, [fakeFixed32]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedFixed32Iterable(1, [fakeFixed32]);
+      expectQualifiedIterable(accessor.getRepeatedFixed32Iterable(1));
+    }
+  });
+
+  it('fail when setting single packed fixed32 value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
+    const fakeFixed32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedFixed32Element(1, 0, fakeFixed32))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedFixed32Element(1, 0, fakeFixed32);
+      expectQualifiedIterable(accessor.getRepeatedFixed32Iterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked with out-of-bound index', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0A, 0x04, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedFixed32Element(1, 1, 1))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedFixed32Element(1, 1, 1);
+      expectQualifiedIterable(
+          accessor.getRepeatedFixed32Iterable(1),
+          (value) => Number.isInteger(value));
+    }
+  });
+
+  it('fail when setting single packed with out-of-bound index', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedFixed32Element(1, 1, 1))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedFixed32Element(1, 1, 1);
+      expectQualifiedIterable(
+          accessor.getRepeatedFixed32Iterable(1),
+          (value) => Number.isInteger(value));
+    }
+  });
+
+  it('fail when getting element with out-of-range index', () => {
+    const accessor = LazyAccessor.createEmpty();
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedFixed32Element(
+            /* fieldNumber= */ 1, /* index= */ 0);
+      }).toThrowError('Index out of bounds: index: 0 size: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getRepeatedFixed32Element(
+                 /* fieldNumber= */ 1, /* index= */ 0))
+          .toBe(undefined);
+    }
+  });
+});
+
+describe('LazyAccessor for repeated fixed64 does', () => {
+  const value1 = Int64.fromInt(1);
+  const value2 = Int64.fromInt(0);
+
+  const unpackedValue1Value2 = createArrayBuffer(
+      0x09,
+      0x01,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,  // value1
+      0x09,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,  // value2
+  );
+  const unpackedValue2Value1 = createArrayBuffer(
+      0x09,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,  // value1
+      0x09,
+      0x01,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,  // value2
+  );
+
+  const packedValue1Value2 = createArrayBuffer(
+      0x0A,
+      0x10,  // tag
+      0x01,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,  // value1
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,  // value2
+  );
+  const packedValue2Value1 = createArrayBuffer(
+      0x0A,
+      0x10,  // tag
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,  // value2
+      0x01,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,  // value1
+  );
+
+  it('return empty array for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list = accessor.getRepeatedFixed64Iterable(1);
+
+    expectEqualToArray(list, []);
+  });
+
+  it('ensure not the same instance returned for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list1 = accessor.getRepeatedFixed64Iterable(1);
+    const list2 = accessor.getRepeatedFixed64Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('return size for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const size = accessor.getRepeatedFixed64Size(1);
+
+    expect(size).toEqual(0);
+  });
+
+  it('return unpacked values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list = accessor.getRepeatedFixed64Iterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for unpacked values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list1 = accessor.getRepeatedFixed64Iterable(1);
+    const list2 = accessor.getRepeatedFixed64Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedFixed64Element(1, value1);
+    const list1 = accessor.getRepeatedFixed64Iterable(1);
+    accessor.addUnpackedFixed64Element(1, value2);
+    const list2 = accessor.getRepeatedFixed64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedFixed64Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedFixed64Iterable(1);
+    accessor.addUnpackedFixed64Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedFixed64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    accessor.setUnpackedFixed64Element(1, 1, value1);
+    const list = accessor.getRepeatedFixed64Iterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedFixed64Iterable(1, [value1]);
+    const list = accessor.getRepeatedFixed64Iterable(1);
+
+    expectEqualToArray(list, [value1]);
+  });
+
+  it('encode for adding single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedFixed64Element(1, value1);
+    accessor.addUnpackedFixed64Element(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for adding unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedFixed64Iterable(1, [value1]);
+    accessor.addUnpackedFixed64Iterable(1, [value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for setting single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setUnpackedFixed64Element(1, 0, value2);
+    accessor.setUnpackedFixed64Element(1, 1, value1);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue2Value1);
+  });
+
+  it('encode for setting unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedFixed64Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('return packed values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list = accessor.getRepeatedFixed64Iterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for packed values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list1 = accessor.getRepeatedFixed64Iterable(1);
+    const list2 = accessor.getRepeatedFixed64Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedFixed64Element(1, value1);
+    const list1 = accessor.getRepeatedFixed64Iterable(1);
+    accessor.addPackedFixed64Element(1, value2);
+    const list2 = accessor.getRepeatedFixed64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedFixed64Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedFixed64Iterable(1);
+    accessor.addPackedFixed64Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedFixed64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedFixed64Element(1, 1, value1);
+    const list = accessor.getRepeatedFixed64Iterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedFixed64Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedFixed64Iterable(1);
+    accessor.setPackedFixed64Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedFixed64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value2]);
+  });
+
+  it('encode for adding single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedFixed64Element(1, value1);
+    accessor.addPackedFixed64Element(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for adding packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedFixed64Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for setting single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedFixed64Element(1, 0, value2);
+    accessor.setPackedFixed64Element(1, 1, value1);
+
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue2Value1);
+  });
+
+  it('encode for setting packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedFixed64Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('return combined values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // value1
+        0x0A, 0x10,                                            // tag
+        0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,        // value1
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,        // value2
+        0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00   // value2
+        ));
+
+    const list = accessor.getRepeatedFixed64Iterable(1);
+
+    expectEqualToArray(list, [value1, value1, value2, value2]);
+  });
+
+  it('return the repeated field element from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const result1 = accessor.getRepeatedFixed64Element(
+        /* fieldNumber= */ 1, /* index= */ 0);
+    const result2 = accessor.getRepeatedFixed64Element(
+        /* fieldNumber= */ 1, /* index= */ 1);
+
+    expect(result1).toEqual(value1);
+    expect(result2).toEqual(value2);
+  });
+
+  it('return the size from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const size = accessor.getRepeatedFixed64Size(1);
+
+    expect(size).toEqual(2);
+  });
+
+  it('fail when getting unpacked fixed64 value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedFixed64Iterable(1);
+      }).toThrowError('Expected wire type: 1 but found: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expectQualifiedIterable(
+          accessor.getRepeatedFixed64Iterable(1),
+          (value) => value instanceof Int64);
+    }
+  });
+
+  it('fail when adding unpacked fixed64 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeFixed64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedFixed64Iterable(1, [fakeFixed64]))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedFixed64Iterable(1, [fakeFixed64]);
+      expectQualifiedIterable(accessor.getRepeatedFixed64Iterable(1));
+    }
+  });
+
+  it('fail when adding single unpacked fixed64 value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeFixed64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedFixed64Element(1, fakeFixed64))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedFixed64Element(1, fakeFixed64);
+      expectQualifiedIterable(accessor.getRepeatedFixed64Iterable(1));
+    }
+  });
+
+  it('fail when setting unpacked fixed64 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeFixed64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedFixed64Iterable(1, [fakeFixed64]))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedFixed64Iterable(1, [fakeFixed64]);
+      expectQualifiedIterable(accessor.getRepeatedFixed64Iterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked fixed64 value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00));
+    const fakeFixed64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedFixed64Element(1, 0, fakeFixed64))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedFixed64Element(1, 0, fakeFixed64);
+      expectQualifiedIterable(accessor.getRepeatedFixed64Iterable(1));
+    }
+  });
+
+  it('fail when adding packed fixed64 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeFixed64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedFixed64Iterable(1, [fakeFixed64]))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedFixed64Iterable(1, [fakeFixed64]);
+      expectQualifiedIterable(accessor.getRepeatedFixed64Iterable(1));
+    }
+  });
+
+  it('fail when adding single packed fixed64 value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeFixed64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedFixed64Element(1, fakeFixed64))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedFixed64Element(1, fakeFixed64);
+      expectQualifiedIterable(accessor.getRepeatedFixed64Iterable(1));
+    }
+  });
+
+  it('fail when setting packed fixed64 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeFixed64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedFixed64Iterable(1, [fakeFixed64]))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedFixed64Iterable(1, [fakeFixed64]);
+      expectQualifiedIterable(accessor.getRepeatedFixed64Iterable(1));
+    }
+  });
+
+  it('fail when setting single packed fixed64 value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+    const fakeFixed64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedFixed64Element(1, 0, fakeFixed64))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedFixed64Element(1, 0, fakeFixed64);
+      expectQualifiedIterable(accessor.getRepeatedFixed64Iterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked with out-of-bound index', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedFixed64Element(1, 1, Int64.fromInt(1)))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedFixed64Element(1, 1, Int64.fromInt(1));
+      expectQualifiedIterable(
+          accessor.getRepeatedFixed64Iterable(1),
+          (value) => value instanceof Int64);
+    }
+  });
+
+  it('fail when setting single packed with out-of-bound index', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedFixed64Element(1, 1, Int64.fromInt(1)))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedFixed64Element(1, 1, Int64.fromInt(1));
+      expectQualifiedIterable(
+          accessor.getRepeatedFixed64Iterable(1),
+          (value) => value instanceof Int64);
+    }
+  });
+
+  it('fail when getting element with out-of-range index', () => {
+    const accessor = LazyAccessor.createEmpty();
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedFixed64Element(
+            /* fieldNumber= */ 1, /* index= */ 0);
+      }).toThrowError('Index out of bounds: index: 0 size: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getRepeatedFixed64Element(
+                 /* fieldNumber= */ 1, /* index= */ 0))
+          .toBe(undefined);
+    }
+  });
+});
+
+describe('LazyAccessor for repeated float does', () => {
+  const value1 = 1.6;
+  const value1Float = Math.fround(1.6);
+  const value2 = 0;
+
+  const unpackedValue1Value2 = createArrayBuffer(
+      0x0D, 0xCD, 0xCC, 0xCC, 0x3F, 0x0D, 0x00, 0x00, 0x00, 0x00);
+  const unpackedValue2Value1 = createArrayBuffer(
+      0x0D, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xCD, 0xCC, 0xCC, 0x3F);
+
+  const packedValue1Value2 = createArrayBuffer(
+      0x0A, 0x08, 0xCD, 0xCC, 0xCC, 0x3F, 0x00, 0x00, 0x00, 0x00);
+  const packedValue2Value1 = createArrayBuffer(
+      0x0A, 0x08, 0x00, 0x00, 0x00, 0x00, 0xCD, 0xCC, 0xCC, 0x3F);
+
+  it('return empty array for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list = accessor.getRepeatedFloatIterable(1);
+
+    expectEqualToArray(list, []);
+  });
+
+  it('ensure not the same instance returned for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list1 = accessor.getRepeatedFloatIterable(1);
+    const list2 = accessor.getRepeatedFloatIterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('return size for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const size = accessor.getRepeatedFloatSize(1);
+
+    expect(size).toEqual(0);
+  });
+
+  it('return unpacked values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list = accessor.getRepeatedFloatIterable(1);
+
+    expectEqualToArray(list, [value1Float, value2]);
+  });
+
+  it('ensure not the same instance returned for unpacked values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list1 = accessor.getRepeatedFloatIterable(1);
+    const list2 = accessor.getRepeatedFloatIterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedFloatElement(1, value1);
+    const list1 = accessor.getRepeatedFloatIterable(1);
+    accessor.addUnpackedFloatElement(1, value2);
+    const list2 = accessor.getRepeatedFloatIterable(1);
+
+    expectEqualToArray(list1, [value1Float]);
+    expectEqualToArray(list2, [value1Float, value2]);
+  });
+
+  it('add unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedFloatIterable(1, [value1]);
+    const list1 = accessor.getRepeatedFloatIterable(1);
+    accessor.addUnpackedFloatIterable(1, [value2]);
+    const list2 = accessor.getRepeatedFloatIterable(1);
+
+    expectEqualToArray(list1, [value1Float]);
+    expectEqualToArray(list2, [value1Float, value2]);
+  });
+
+  it('set a single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    accessor.setUnpackedFloatElement(1, 1, value1);
+    const list = accessor.getRepeatedFloatIterable(1);
+
+    expectEqualToArray(list, [value1Float, value1Float]);
+  });
+
+  it('set unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedFloatIterable(1, [value1]);
+    const list = accessor.getRepeatedFloatIterable(1);
+
+    expectEqualToArray(list, [value1Float]);
+  });
+
+  it('encode for adding single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedFloatElement(1, value1);
+    accessor.addUnpackedFloatElement(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for adding unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedFloatIterable(1, [value1]);
+    accessor.addUnpackedFloatIterable(1, [value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for setting single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setUnpackedFloatElement(1, 0, value2);
+    accessor.setUnpackedFloatElement(1, 1, value1);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue2Value1);
+  });
+
+  it('encode for setting unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedFloatIterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('return packed values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list = accessor.getRepeatedFloatIterable(1);
+
+    expectEqualToArray(list, [value1Float, value2]);
+  });
+
+  it('ensure not the same instance returned for packed values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list1 = accessor.getRepeatedFloatIterable(1);
+    const list2 = accessor.getRepeatedFloatIterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedFloatElement(1, value1);
+    const list1 = accessor.getRepeatedFloatIterable(1);
+    accessor.addPackedFloatElement(1, value2);
+    const list2 = accessor.getRepeatedFloatIterable(1);
+
+    expectEqualToArray(list1, [value1Float]);
+    expectEqualToArray(list2, [value1Float, value2]);
+  });
+
+  it('add packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedFloatIterable(1, [value1]);
+    const list1 = accessor.getRepeatedFloatIterable(1);
+    accessor.addPackedFloatIterable(1, [value2]);
+    const list2 = accessor.getRepeatedFloatIterable(1);
+
+    expectEqualToArray(list1, [value1Float]);
+    expectEqualToArray(list2, [value1Float, value2]);
+  });
+
+  it('set a single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedFloatElement(1, 1, value1);
+    const list = accessor.getRepeatedFloatIterable(1);
+
+    expectEqualToArray(list, [value1Float, value1Float]);
+  });
+
+  it('set packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedFloatIterable(1, [value1]);
+    const list1 = accessor.getRepeatedFloatIterable(1);
+    accessor.setPackedFloatIterable(1, [value2]);
+    const list2 = accessor.getRepeatedFloatIterable(1);
+
+    expectEqualToArray(list1, [value1Float]);
+    expectEqualToArray(list2, [value2]);
+  });
+
+  it('encode for adding single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedFloatElement(1, value1);
+    accessor.addPackedFloatElement(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for adding packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedFloatIterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for setting single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedFloatElement(1, 0, value2);
+    accessor.setPackedFloatElement(1, 1, value1);
+
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue2Value1);
+  });
+
+  it('encode for setting packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedFloatIterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('return combined values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x0D,
+        0xCD,
+        0xCC,
+        0xCC,
+        0x3F,  // value1
+        0x0A,
+        0x08,  // tag
+        0xCD,
+        0xCC,
+        0xCC,
+        0x3F,  // value1
+        0x00,
+        0x00,
+        0x00,
+        0x00,  // value2
+        0x0D,
+        0x00,
+        0x00,
+        0x00,
+        0x00,  // value2
+        ));
+
+    const list = accessor.getRepeatedFloatIterable(1);
+
+    expectEqualToArray(list, [value1Float, value1Float, value2, value2]);
+  });
+
+  it('return the repeated field element from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const result1 = accessor.getRepeatedFloatElement(
+        /* fieldNumber= */ 1, /* index= */ 0);
+    const result2 = accessor.getRepeatedFloatElement(
+        /* fieldNumber= */ 1, /* index= */ 1);
+
+    expect(result1).toEqual(value1Float);
+    expect(result2).toEqual(value2);
+  });
+
+  it('return the size from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const size = accessor.getRepeatedFloatSize(1);
+
+    expect(size).toEqual(2);
+  });
+
+  it('fail when getting unpacked float value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x08, 0x80, 0x80, 0x80, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedFloatIterable(1);
+      }).toThrowError('Expected wire type: 5 but found: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expectQualifiedIterable(
+          accessor.getRepeatedFloatIterable(1),
+          (value) => typeof value === 'number');
+    }
+  });
+
+  it('fail when adding unpacked float values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeFloat = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedFloatIterable(1, [fakeFloat]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedFloatIterable(1, [fakeFloat]);
+      expectQualifiedIterable(accessor.getRepeatedFloatIterable(1));
+    }
+  });
+
+  it('fail when adding single unpacked float value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeFloat = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedFloatElement(1, fakeFloat))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedFloatElement(1, fakeFloat);
+      expectQualifiedIterable(accessor.getRepeatedFloatIterable(1));
+    }
+  });
+
+  it('fail when setting unpacked float values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeFloat = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedFloatIterable(1, [fakeFloat]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedFloatIterable(1, [fakeFloat]);
+      expectQualifiedIterable(accessor.getRepeatedFloatIterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked float value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x08, 0x80, 0x80, 0x80, 0x00));
+    const fakeFloat = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedFloatElement(1, 0, fakeFloat))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedFloatElement(1, 0, fakeFloat);
+      expectQualifiedIterable(accessor.getRepeatedFloatIterable(1));
+    }
+  });
+
+  it('fail when adding packed float values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeFloat = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedFloatIterable(1, [fakeFloat]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedFloatIterable(1, [fakeFloat]);
+      expectQualifiedIterable(accessor.getRepeatedFloatIterable(1));
+    }
+  });
+
+  it('fail when adding single packed float value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeFloat = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedFloatElement(1, fakeFloat))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedFloatElement(1, fakeFloat);
+      expectQualifiedIterable(accessor.getRepeatedFloatIterable(1));
+    }
+  });
+
+  it('fail when setting packed float values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeFloat = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedFloatIterable(1, [fakeFloat]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedFloatIterable(1, [fakeFloat]);
+      expectQualifiedIterable(accessor.getRepeatedFloatIterable(1));
+    }
+  });
+
+  it('fail when setting single packed float value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
+    const fakeFloat = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedFloatElement(1, 0, fakeFloat))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedFloatElement(1, 0, fakeFloat);
+      expectQualifiedIterable(accessor.getRepeatedFloatIterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked with out-of-bound index', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedFloatElement(1, 1, 1))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedFloatElement(1, 1, 1);
+      expectQualifiedIterable(
+          accessor.getRepeatedFloatIterable(1),
+          (value) => typeof value === 'number');
+    }
+  });
+
+  it('fail when setting single packed with out-of-bound index', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedFloatElement(1, 1, 1))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedFloatElement(1, 1, 1);
+      expectQualifiedIterable(
+          accessor.getRepeatedFloatIterable(1),
+          (value) => typeof value === 'number');
+    }
+  });
+
+  it('fail when getting element with out-of-range index', () => {
+    const accessor = LazyAccessor.createEmpty();
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedFloatElement(
+            /* fieldNumber= */ 1, /* index= */ 0);
+      }).toThrowError('Index out of bounds: index: 0 size: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getRepeatedFloatElement(
+                 /* fieldNumber= */ 1, /* index= */ 0))
+          .toBe(undefined);
+    }
+  });
+});
+
+describe('LazyAccessor for repeated int32 does', () => {
+  const value1 = 1;
+  const value2 = 0;
+
+  const unpackedValue1Value2 = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
+  const unpackedValue2Value1 = createArrayBuffer(0x08, 0x00, 0x08, 0x01);
+
+  const packedValue1Value2 = createArrayBuffer(0x0A, 0x02, 0x01, 0x00);
+  const packedValue2Value1 = createArrayBuffer(0x0A, 0x02, 0x00, 0x01);
+
+  it('return empty array for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list = accessor.getRepeatedInt32Iterable(1);
+
+    expectEqualToArray(list, []);
+  });
+
+  it('ensure not the same instance returned for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list1 = accessor.getRepeatedInt32Iterable(1);
+    const list2 = accessor.getRepeatedInt32Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('return size for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const size = accessor.getRepeatedInt32Size(1);
+
+    expect(size).toEqual(0);
+  });
+
+  it('return unpacked values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list = accessor.getRepeatedInt32Iterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for unpacked values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list1 = accessor.getRepeatedInt32Iterable(1);
+    const list2 = accessor.getRepeatedInt32Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedInt32Element(1, value1);
+    const list1 = accessor.getRepeatedInt32Iterable(1);
+    accessor.addUnpackedInt32Element(1, value2);
+    const list2 = accessor.getRepeatedInt32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedInt32Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedInt32Iterable(1);
+    accessor.addUnpackedInt32Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedInt32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    accessor.setUnpackedInt32Element(1, 1, value1);
+    const list = accessor.getRepeatedInt32Iterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedInt32Iterable(1, [value1]);
+    const list = accessor.getRepeatedInt32Iterable(1);
+
+    expectEqualToArray(list, [value1]);
+  });
+
+  it('encode for adding single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedInt32Element(1, value1);
+    accessor.addUnpackedInt32Element(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for adding unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedInt32Iterable(1, [value1]);
+    accessor.addUnpackedInt32Iterable(1, [value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for setting single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setUnpackedInt32Element(1, 0, value2);
+    accessor.setUnpackedInt32Element(1, 1, value1);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue2Value1);
+  });
+
+  it('encode for setting unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedInt32Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('return packed values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list = accessor.getRepeatedInt32Iterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for packed values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list1 = accessor.getRepeatedInt32Iterable(1);
+    const list2 = accessor.getRepeatedInt32Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedInt32Element(1, value1);
+    const list1 = accessor.getRepeatedInt32Iterable(1);
+    accessor.addPackedInt32Element(1, value2);
+    const list2 = accessor.getRepeatedInt32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedInt32Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedInt32Iterable(1);
+    accessor.addPackedInt32Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedInt32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedInt32Element(1, 1, value1);
+    const list = accessor.getRepeatedInt32Iterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedInt32Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedInt32Iterable(1);
+    accessor.setPackedInt32Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedInt32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value2]);
+  });
+
+  it('encode for adding single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedInt32Element(1, value1);
+    accessor.addPackedInt32Element(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for adding packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedInt32Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for setting single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedInt32Element(1, 0, value2);
+    accessor.setPackedInt32Element(1, 1, value1);
+
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue2Value1);
+  });
+
+  it('encode for setting packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedInt32Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('return combined values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x08,
+        0x01,  // unpacked value1
+        0x0A,
+        0x02,
+        0x01,
+        0x00,  // packed value1 and value2
+        0x08,
+        0x00,  // unpacked value2
+        ));
+
+    const list = accessor.getRepeatedInt32Iterable(1);
+
+    expectEqualToArray(list, [value1, value1, value2, value2]);
+  });
+
+  it('return the repeated field element from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const result1 = accessor.getRepeatedInt32Element(
+        /* fieldNumber= */ 1, /* index= */ 0);
+    const result2 = accessor.getRepeatedInt32Element(
+        /* fieldNumber= */ 1, /* index= */ 1);
+
+    expect(result1).toEqual(value1);
+    expect(result2).toEqual(value2);
+  });
+
+  it('return the size from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const size = accessor.getRepeatedInt32Size(1);
+
+    expect(size).toEqual(2);
+  });
+
+  it('fail when getting unpacked int32 value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x80, 0x80, 0x80, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedInt32Iterable(1);
+      }).toThrowError('Expected wire type: 0 but found: 5');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expectQualifiedIterable(
+          accessor.getRepeatedInt32Iterable(1),
+          (value) => Number.isInteger(value));
+    }
+  });
+
+  it('fail when adding unpacked int32 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeInt32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedInt32Iterable(1, [fakeInt32]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedInt32Iterable(1, [fakeInt32]);
+      expectQualifiedIterable(accessor.getRepeatedInt32Iterable(1));
+    }
+  });
+
+  it('fail when adding single unpacked int32 value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeInt32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedInt32Element(1, fakeInt32))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedInt32Element(1, fakeInt32);
+      expectQualifiedIterable(accessor.getRepeatedInt32Iterable(1));
+    }
+  });
+
+  it('fail when setting unpacked int32 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeInt32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedInt32Iterable(1, [fakeInt32]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedInt32Iterable(1, [fakeInt32]);
+      expectQualifiedIterable(accessor.getRepeatedInt32Iterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked int32 value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x80, 0x80, 0x80, 0x00));
+    const fakeInt32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedInt32Element(1, 0, fakeInt32))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedInt32Element(1, 0, fakeInt32);
+      expectQualifiedIterable(
+          accessor.getRepeatedInt32Iterable(1),
+      );
+    }
+  });
+
+  it('fail when adding packed int32 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeInt32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedInt32Iterable(1, [fakeInt32]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedInt32Iterable(1, [fakeInt32]);
+      expectQualifiedIterable(accessor.getRepeatedInt32Iterable(1));
+    }
+  });
+
+  it('fail when adding single packed int32 value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeInt32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedInt32Element(1, fakeInt32))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedInt32Element(1, fakeInt32);
+      expectQualifiedIterable(accessor.getRepeatedInt32Iterable(1));
+    }
+  });
+
+  it('fail when setting packed int32 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeInt32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedInt32Iterable(1, [fakeInt32]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedInt32Iterable(1, [fakeInt32]);
+      expectQualifiedIterable(accessor.getRepeatedInt32Iterable(1));
+    }
+  });
+
+  it('fail when setting single packed int32 value with null value', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x00));
+    const fakeInt32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedInt32Element(1, 0, fakeInt32))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedInt32Element(1, 0, fakeInt32);
+      expectQualifiedIterable(accessor.getRepeatedInt32Iterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked with out-of-bound index', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x0A, 0x01, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedInt32Element(1, 1, 1))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedInt32Element(1, 1, 1);
+      expectQualifiedIterable(
+          accessor.getRepeatedInt32Iterable(1),
+          (value) => Number.isInteger(value));
+    }
+  });
+
+  it('fail when setting single packed with out-of-bound index', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedInt32Element(1, 1, 1))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedInt32Element(1, 1, 1);
+      expectQualifiedIterable(
+          accessor.getRepeatedInt32Iterable(1),
+          (value) => Number.isInteger(value));
+    }
+  });
+
+  it('fail when getting element with out-of-range index', () => {
+    const accessor = LazyAccessor.createEmpty();
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedInt32Element(
+            /* fieldNumber= */ 1, /* index= */ 0);
+      }).toThrowError('Index out of bounds: index: 0 size: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getRepeatedInt32Element(
+                 /* fieldNumber= */ 1, /* index= */ 0))
+          .toBe(undefined);
+    }
+  });
+});
+
+describe('LazyAccessor for repeated int64 does', () => {
+  const value1 = Int64.fromInt(1);
+  const value2 = Int64.fromInt(0);
+
+  const unpackedValue1Value2 = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
+  const unpackedValue2Value1 = createArrayBuffer(0x08, 0x00, 0x08, 0x01);
+
+  const packedValue1Value2 = createArrayBuffer(0x0A, 0x02, 0x01, 0x00);
+  const packedValue2Value1 = createArrayBuffer(0x0A, 0x02, 0x00, 0x01);
+
+  it('return empty array for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list = accessor.getRepeatedInt64Iterable(1);
+
+    expectEqualToArray(list, []);
+  });
+
+  it('ensure not the same instance returned for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list1 = accessor.getRepeatedInt64Iterable(1);
+    const list2 = accessor.getRepeatedInt64Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('return size for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const size = accessor.getRepeatedInt64Size(1);
+
+    expect(size).toEqual(0);
+  });
+
+  it('return unpacked values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list = accessor.getRepeatedInt64Iterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for unpacked values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list1 = accessor.getRepeatedInt64Iterable(1);
+    const list2 = accessor.getRepeatedInt64Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedInt64Element(1, value1);
+    const list1 = accessor.getRepeatedInt64Iterable(1);
+    accessor.addUnpackedInt64Element(1, value2);
+    const list2 = accessor.getRepeatedInt64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedInt64Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedInt64Iterable(1);
+    accessor.addUnpackedInt64Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedInt64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    accessor.setUnpackedInt64Element(1, 1, value1);
+    const list = accessor.getRepeatedInt64Iterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedInt64Iterable(1, [value1]);
+    const list = accessor.getRepeatedInt64Iterable(1);
+
+    expectEqualToArray(list, [value1]);
+  });
+
+  it('encode for adding single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedInt64Element(1, value1);
+    accessor.addUnpackedInt64Element(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for adding unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedInt64Iterable(1, [value1]);
+    accessor.addUnpackedInt64Iterable(1, [value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for setting single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setUnpackedInt64Element(1, 0, value2);
+    accessor.setUnpackedInt64Element(1, 1, value1);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue2Value1);
+  });
+
+  it('encode for setting unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedInt64Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('return packed values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list = accessor.getRepeatedInt64Iterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for packed values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list1 = accessor.getRepeatedInt64Iterable(1);
+    const list2 = accessor.getRepeatedInt64Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedInt64Element(1, value1);
+    const list1 = accessor.getRepeatedInt64Iterable(1);
+    accessor.addPackedInt64Element(1, value2);
+    const list2 = accessor.getRepeatedInt64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedInt64Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedInt64Iterable(1);
+    accessor.addPackedInt64Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedInt64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedInt64Element(1, 1, value1);
+    const list = accessor.getRepeatedInt64Iterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedInt64Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedInt64Iterable(1);
+    accessor.setPackedInt64Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedInt64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value2]);
+  });
+
+  it('encode for adding single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedInt64Element(1, value1);
+    accessor.addPackedInt64Element(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for adding packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedInt64Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for setting single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedInt64Element(1, 0, value2);
+    accessor.setPackedInt64Element(1, 1, value1);
+
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue2Value1);
+  });
+
+  it('encode for setting packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedInt64Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('return combined values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x08,
+        0x01,  // unpacked value1
+        0x0A,
+        0x02,
+        0x01,
+        0x00,  // packed value1 and value2
+        0x08,
+        0x00,  // unpacked value2
+        ));
+
+    const list = accessor.getRepeatedInt64Iterable(1);
+
+    expectEqualToArray(list, [value1, value1, value2, value2]);
+  });
+
+  it('return the repeated field element from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const result1 = accessor.getRepeatedInt64Element(
+        /* fieldNumber= */ 1, /* index= */ 0);
+    const result2 = accessor.getRepeatedInt64Element(
+        /* fieldNumber= */ 1, /* index= */ 1);
+
+    expect(result1).toEqual(value1);
+    expect(result2).toEqual(value2);
+  });
+
+  it('return the size from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const size = accessor.getRepeatedInt64Size(1);
+
+    expect(size).toEqual(2);
+  });
+
+  it('fail when getting unpacked int64 value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x80, 0x80, 0x80, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedInt64Iterable(1);
+      }).toThrowError('Expected wire type: 0 but found: 5');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expectQualifiedIterable(
+          accessor.getRepeatedInt64Iterable(1),
+          (value) => value instanceof Int64);
+    }
+  });
+
+  it('fail when adding unpacked int64 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeInt64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedInt64Iterable(1, [fakeInt64]))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedInt64Iterable(1, [fakeInt64]);
+      expectQualifiedIterable(accessor.getRepeatedInt64Iterable(1));
+    }
+  });
+
+  it('fail when adding single unpacked int64 value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeInt64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedInt64Element(1, fakeInt64))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedInt64Element(1, fakeInt64);
+      expectQualifiedIterable(accessor.getRepeatedInt64Iterable(1));
+    }
+  });
+
+  it('fail when setting unpacked int64 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeInt64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedInt64Iterable(1, [fakeInt64]))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedInt64Iterable(1, [fakeInt64]);
+      expectQualifiedIterable(accessor.getRepeatedInt64Iterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked int64 value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x80, 0x80, 0x80, 0x00));
+    const fakeInt64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedInt64Element(1, 0, fakeInt64))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedInt64Element(1, 0, fakeInt64);
+      expectQualifiedIterable(accessor.getRepeatedInt64Iterable(1));
+    }
+  });
+
+  it('fail when adding packed int64 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeInt64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedInt64Iterable(1, [fakeInt64]))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedInt64Iterable(1, [fakeInt64]);
+      expectQualifiedIterable(accessor.getRepeatedInt64Iterable(1));
+    }
+  });
+
+  it('fail when adding single packed int64 value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeInt64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedInt64Element(1, fakeInt64))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedInt64Element(1, fakeInt64);
+      expectQualifiedIterable(accessor.getRepeatedInt64Iterable(1));
+    }
+  });
+
+  it('fail when setting packed int64 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeInt64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedInt64Iterable(1, [fakeInt64]))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedInt64Iterable(1, [fakeInt64]);
+      expectQualifiedIterable(accessor.getRepeatedInt64Iterable(1));
+    }
+  });
+
+  it('fail when setting single packed int64 value with null value', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x00));
+    const fakeInt64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedInt64Element(1, 0, fakeInt64))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedInt64Element(1, 0, fakeInt64);
+      expectQualifiedIterable(accessor.getRepeatedInt64Iterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked with out-of-bound index', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x0A, 0x01, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedInt64Element(1, 1, Int64.fromInt(1)))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedInt64Element(1, 1, Int64.fromInt(1));
+      expectQualifiedIterable(
+          accessor.getRepeatedInt64Iterable(1),
+          (value) => value instanceof Int64);
+    }
+  });
+
+  it('fail when setting single packed with out-of-bound index', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedInt64Element(1, 1, Int64.fromInt(1)))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedInt64Element(1, 1, Int64.fromInt(1));
+      expectQualifiedIterable(
+          accessor.getRepeatedInt64Iterable(1),
+          (value) => value instanceof Int64);
+    }
+  });
+
+  it('fail when getting element with out-of-range index', () => {
+    const accessor = LazyAccessor.createEmpty();
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedInt64Element(
+            /* fieldNumber= */ 1, /* index= */ 0);
+      }).toThrowError('Index out of bounds: index: 0 size: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getRepeatedInt64Element(
+                 /* fieldNumber= */ 1, /* index= */ 0))
+          .toBe(undefined);
+    }
+  });
+});
+
+describe('LazyAccessor for repeated sfixed32 does', () => {
+  const value1 = 1;
+  const value2 = 0;
+
+  const unpackedValue1Value2 = createArrayBuffer(
+      0x0D, 0x01, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00);
+  const unpackedValue2Value1 = createArrayBuffer(
+      0x0D, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x01, 0x00, 0x00, 0x00);
+
+  const packedValue1Value2 = createArrayBuffer(
+      0x0A, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+  const packedValue2Value1 = createArrayBuffer(
+      0x0A, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00);
+
+  it('return empty array for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list = accessor.getRepeatedSfixed32Iterable(1);
+
+    expectEqualToArray(list, []);
+  });
+
+  it('ensure not the same instance returned for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list1 = accessor.getRepeatedSfixed32Iterable(1);
+    const list2 = accessor.getRepeatedSfixed32Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('return size for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const size = accessor.getRepeatedSfixed32Size(1);
+
+    expect(size).toEqual(0);
+  });
+
+  it('return unpacked values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list = accessor.getRepeatedSfixed32Iterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for unpacked values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list1 = accessor.getRepeatedSfixed32Iterable(1);
+    const list2 = accessor.getRepeatedSfixed32Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedSfixed32Element(1, value1);
+    const list1 = accessor.getRepeatedSfixed32Iterable(1);
+    accessor.addUnpackedSfixed32Element(1, value2);
+    const list2 = accessor.getRepeatedSfixed32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedSfixed32Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedSfixed32Iterable(1);
+    accessor.addUnpackedSfixed32Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedSfixed32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    accessor.setUnpackedSfixed32Element(1, 1, value1);
+    const list = accessor.getRepeatedSfixed32Iterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedSfixed32Iterable(1, [value1]);
+    const list = accessor.getRepeatedSfixed32Iterable(1);
+
+    expectEqualToArray(list, [value1]);
+  });
+
+  it('encode for adding single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedSfixed32Element(1, value1);
+    accessor.addUnpackedSfixed32Element(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for adding unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedSfixed32Iterable(1, [value1]);
+    accessor.addUnpackedSfixed32Iterable(1, [value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for setting single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setUnpackedSfixed32Element(1, 0, value2);
+    accessor.setUnpackedSfixed32Element(1, 1, value1);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue2Value1);
+  });
+
+  it('encode for setting unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedSfixed32Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('return packed values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list = accessor.getRepeatedSfixed32Iterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for packed values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list1 = accessor.getRepeatedSfixed32Iterable(1);
+    const list2 = accessor.getRepeatedSfixed32Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedSfixed32Element(1, value1);
+    const list1 = accessor.getRepeatedSfixed32Iterable(1);
+    accessor.addPackedSfixed32Element(1, value2);
+    const list2 = accessor.getRepeatedSfixed32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedSfixed32Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedSfixed32Iterable(1);
+    accessor.addPackedSfixed32Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedSfixed32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedSfixed32Element(1, 1, value1);
+    const list = accessor.getRepeatedSfixed32Iterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedSfixed32Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedSfixed32Iterable(1);
+    accessor.setPackedSfixed32Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedSfixed32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value2]);
+  });
+
+  it('encode for adding single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedSfixed32Element(1, value1);
+    accessor.addPackedSfixed32Element(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for adding packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedSfixed32Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for setting single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedSfixed32Element(1, 0, value2);
+    accessor.setPackedSfixed32Element(1, 1, value1);
+
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue2Value1);
+  });
+
+  it('encode for setting packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedSfixed32Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('return combined values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x0D,
+        0x01,
+        0x00,
+        0x00,
+        0x00,  // value1
+        0x0A,
+        0x08,  // tag
+        0x01,
+        0x00,
+        0x00,
+        0x00,  // value1
+        0x00,
+        0x00,
+        0x00,
+        0x00,  // value2
+        0x0D,
+        0x00,
+        0x00,
+        0x00,
+        0x00,  // value2
+        ));
+
+    const list = accessor.getRepeatedSfixed32Iterable(1);
+
+    expectEqualToArray(list, [value1, value1, value2, value2]);
+  });
+
+  it('return the repeated field element from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const result1 = accessor.getRepeatedSfixed32Element(
+        /* fieldNumber= */ 1, /* index= */ 0);
+    const result2 = accessor.getRepeatedSfixed32Element(
+        /* fieldNumber= */ 1, /* index= */ 1);
+
+    expect(result1).toEqual(value1);
+    expect(result2).toEqual(value2);
+  });
+
+  it('return the size from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const size = accessor.getRepeatedSfixed32Size(1);
+
+    expect(size).toEqual(2);
+  });
+
+  it('fail when getting unpacked sfixed32 value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x08, 0x80, 0x80, 0x80, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedSfixed32Iterable(1);
+      }).toThrowError('Expected wire type: 5 but found: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expectQualifiedIterable(
+          accessor.getRepeatedSfixed32Iterable(1),
+          (value) => typeof value === 'number');
+    }
+  });
+
+  it('fail when adding unpacked sfixed32 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSfixed32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedSfixed32Iterable(1, [fakeSfixed32]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedSfixed32Iterable(1, [fakeSfixed32]);
+      expectQualifiedIterable(accessor.getRepeatedSfixed32Iterable(1));
+    }
+  });
+
+  it('fail when adding single unpacked sfixed32 value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSfixed32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedSfixed32Element(1, fakeSfixed32))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedSfixed32Element(1, fakeSfixed32);
+      expectQualifiedIterable(accessor.getRepeatedSfixed32Iterable(1));
+    }
+  });
+
+  it('fail when setting unpacked sfixed32 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSfixed32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedSfixed32Iterable(1, [fakeSfixed32]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedSfixed32Iterable(1, [fakeSfixed32]);
+      expectQualifiedIterable(accessor.getRepeatedSfixed32Iterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked sfixed32 value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x08, 0x80, 0x80, 0x80, 0x00));
+    const fakeSfixed32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedSfixed32Element(1, 0, fakeSfixed32))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedSfixed32Element(1, 0, fakeSfixed32);
+      expectQualifiedIterable(accessor.getRepeatedSfixed32Iterable(1));
+    }
+  });
+
+  it('fail when adding packed sfixed32 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSfixed32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedSfixed32Iterable(1, [fakeSfixed32]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedSfixed32Iterable(1, [fakeSfixed32]);
+      expectQualifiedIterable(accessor.getRepeatedSfixed32Iterable(1));
+    }
+  });
+
+  it('fail when adding single packed sfixed32 value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSfixed32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedSfixed32Element(1, fakeSfixed32))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedSfixed32Element(1, fakeSfixed32);
+      expectQualifiedIterable(accessor.getRepeatedSfixed32Iterable(1));
+    }
+  });
+
+  it('fail when setting packed sfixed32 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSfixed32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedSfixed32Iterable(1, [fakeSfixed32]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedSfixed32Iterable(1, [fakeSfixed32]);
+      expectQualifiedIterable(accessor.getRepeatedSfixed32Iterable(1));
+    }
+  });
+
+  it('fail when setting single packed sfixed32 value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
+    const fakeSfixed32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedSfixed32Element(1, 0, fakeSfixed32))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedSfixed32Element(1, 0, fakeSfixed32);
+      expectQualifiedIterable(accessor.getRepeatedSfixed32Iterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked with out-of-bound index', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedSfixed32Element(1, 1, 1))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedSfixed32Element(1, 1, 1);
+      expectQualifiedIterable(
+          accessor.getRepeatedSfixed32Iterable(1),
+          (value) => typeof value === 'number');
+    }
+  });
+
+  it('fail when setting single packed with out-of-bound index', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedSfixed32Element(1, 1, 1))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedSfixed32Element(1, 1, 1);
+      expectQualifiedIterable(
+          accessor.getRepeatedSfixed32Iterable(1),
+          (value) => typeof value === 'number');
+    }
+  });
+
+  it('fail when getting element with out-of-range index', () => {
+    const accessor = LazyAccessor.createEmpty();
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedSfixed32Element(
+            /* fieldNumber= */ 1, /* index= */ 0);
+      }).toThrowError('Index out of bounds: index: 0 size: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getRepeatedSfixed32Element(
+                 /* fieldNumber= */ 1, /* index= */ 0))
+          .toBe(undefined);
+    }
+  });
+});
+
+describe('LazyAccessor for repeated sfixed64 does', () => {
+  const value1 = Int64.fromInt(1);
+  const value2 = Int64.fromInt(0);
+
+  const unpackedValue1Value2 = createArrayBuffer(
+      0x09,
+      0x01,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,  // value1
+      0x09,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,  // value2
+  );
+  const unpackedValue2Value1 = createArrayBuffer(
+      0x09,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,  // value1
+      0x09,
+      0x01,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,  // value2
+  );
+
+  const packedValue1Value2 = createArrayBuffer(
+      0x0A,
+      0x10,  // tag
+      0x01,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,  // value1
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,  // value2
+  );
+  const packedValue2Value1 = createArrayBuffer(
+      0x0A,
+      0x10,  // tag
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,  // value2
+      0x01,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,
+      0x00,  // value1
+  );
+
+  it('return empty array for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list = accessor.getRepeatedSfixed64Iterable(1);
+
+    expectEqualToArray(list, []);
+  });
+
+  it('ensure not the same instance returned for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list1 = accessor.getRepeatedSfixed64Iterable(1);
+    const list2 = accessor.getRepeatedSfixed64Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('return size for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const size = accessor.getRepeatedSfixed64Size(1);
+
+    expect(size).toEqual(0);
+  });
+
+  it('return unpacked values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list = accessor.getRepeatedSfixed64Iterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for unpacked values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list1 = accessor.getRepeatedSfixed64Iterable(1);
+    const list2 = accessor.getRepeatedSfixed64Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedSfixed64Element(1, value1);
+    const list1 = accessor.getRepeatedSfixed64Iterable(1);
+    accessor.addUnpackedSfixed64Element(1, value2);
+    const list2 = accessor.getRepeatedSfixed64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedSfixed64Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedSfixed64Iterable(1);
+    accessor.addUnpackedSfixed64Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedSfixed64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    accessor.setUnpackedSfixed64Element(1, 1, value1);
+    const list = accessor.getRepeatedSfixed64Iterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedSfixed64Iterable(1, [value1]);
+    const list = accessor.getRepeatedSfixed64Iterable(1);
+
+    expectEqualToArray(list, [value1]);
+  });
+
+  it('encode for adding single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedSfixed64Element(1, value1);
+    accessor.addUnpackedSfixed64Element(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for adding unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedSfixed64Iterable(1, [value1]);
+    accessor.addUnpackedSfixed64Iterable(1, [value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for setting single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setUnpackedSfixed64Element(1, 0, value2);
+    accessor.setUnpackedSfixed64Element(1, 1, value1);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue2Value1);
+  });
+
+  it('encode for setting unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedSfixed64Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('return packed values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list = accessor.getRepeatedSfixed64Iterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for packed values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list1 = accessor.getRepeatedSfixed64Iterable(1);
+    const list2 = accessor.getRepeatedSfixed64Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedSfixed64Element(1, value1);
+    const list1 = accessor.getRepeatedSfixed64Iterable(1);
+    accessor.addPackedSfixed64Element(1, value2);
+    const list2 = accessor.getRepeatedSfixed64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedSfixed64Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedSfixed64Iterable(1);
+    accessor.addPackedSfixed64Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedSfixed64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedSfixed64Element(1, 1, value1);
+    const list = accessor.getRepeatedSfixed64Iterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedSfixed64Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedSfixed64Iterable(1);
+    accessor.setPackedSfixed64Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedSfixed64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value2]);
+  });
+
+  it('encode for adding single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedSfixed64Element(1, value1);
+    accessor.addPackedSfixed64Element(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for adding packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedSfixed64Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for setting single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedSfixed64Element(1, 0, value2);
+    accessor.setPackedSfixed64Element(1, 1, value1);
+
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue2Value1);
+  });
+
+  it('encode for setting packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedSfixed64Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('return combined values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // value1
+        0x0A, 0x10,                                            // tag
+        0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,        // value1
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,        // value2
+        0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00   // value2
+        ));
+
+    const list = accessor.getRepeatedSfixed64Iterable(1);
+
+    expectEqualToArray(list, [value1, value1, value2, value2]);
+  });
+
+  it('return the repeated field element from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const result1 = accessor.getRepeatedSfixed64Element(
+        /* fieldNumber= */ 1, /* index= */ 0);
+    const result2 = accessor.getRepeatedSfixed64Element(
+        /* fieldNumber= */ 1, /* index= */ 1);
+
+    expect(result1).toEqual(value1);
+    expect(result2).toEqual(value2);
+  });
+
+  it('return the size from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const size = accessor.getRepeatedSfixed64Size(1);
+
+    expect(size).toEqual(2);
+  });
+
+  it('fail when getting unpacked sfixed64 value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedSfixed64Iterable(1);
+      }).toThrowError('Expected wire type: 1 but found: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expectQualifiedIterable(
+          accessor.getRepeatedSfixed64Iterable(1),
+          (value) => value instanceof Int64);
+    }
+  });
+
+  it('fail when adding unpacked sfixed64 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSfixed64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedSfixed64Iterable(1, [fakeSfixed64]))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedSfixed64Iterable(1, [fakeSfixed64]);
+      expectQualifiedIterable(accessor.getRepeatedSfixed64Iterable(1));
+    }
+  });
+
+  it('fail when adding single unpacked sfixed64 value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSfixed64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedSfixed64Element(1, fakeSfixed64))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedSfixed64Element(1, fakeSfixed64);
+      expectQualifiedIterable(accessor.getRepeatedSfixed64Iterable(1));
+    }
+  });
+
+  it('fail when setting unpacked sfixed64 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSfixed64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedSfixed64Iterable(1, [fakeSfixed64]))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedSfixed64Iterable(1, [fakeSfixed64]);
+      expectQualifiedIterable(accessor.getRepeatedSfixed64Iterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked sfixed64 value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00));
+    const fakeSfixed64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedSfixed64Element(1, 0, fakeSfixed64))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedSfixed64Element(1, 0, fakeSfixed64);
+      expectQualifiedIterable(accessor.getRepeatedSfixed64Iterable(1));
+    }
+  });
+
+  it('fail when adding packed sfixed64 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSfixed64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedSfixed64Iterable(1, [fakeSfixed64]))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedSfixed64Iterable(1, [fakeSfixed64]);
+      expectQualifiedIterable(accessor.getRepeatedSfixed64Iterable(1));
+    }
+  });
+
+  it('fail when adding single packed sfixed64 value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSfixed64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedSfixed64Element(1, fakeSfixed64))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedSfixed64Element(1, fakeSfixed64);
+      expectQualifiedIterable(accessor.getRepeatedSfixed64Iterable(1));
+    }
+  });
+
+  it('fail when setting packed sfixed64 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSfixed64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedSfixed64Iterable(1, [fakeSfixed64]))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedSfixed64Iterable(1, [fakeSfixed64]);
+      expectQualifiedIterable(accessor.getRepeatedSfixed64Iterable(1));
+    }
+  });
+
+  it('fail when setting single packed sfixed64 value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+    const fakeSfixed64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedSfixed64Element(1, 0, fakeSfixed64))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedSfixed64Element(1, 0, fakeSfixed64);
+      expectQualifiedIterable(accessor.getRepeatedSfixed64Iterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked with out-of-bound index', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedSfixed64Element(1, 1, Int64.fromInt(1)))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedSfixed64Element(1, 1, Int64.fromInt(1));
+      expectQualifiedIterable(
+          accessor.getRepeatedSfixed64Iterable(1),
+          (value) => value instanceof Int64);
+    }
+  });
+
+  it('fail when setting single packed with out-of-bound index', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedSfixed64Element(1, 1, Int64.fromInt(1)))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedSfixed64Element(1, 1, Int64.fromInt(1));
+      expectQualifiedIterable(
+          accessor.getRepeatedSfixed64Iterable(1),
+          (value) => value instanceof Int64);
+    }
+  });
+
+  it('fail when getting element with out-of-range index', () => {
+    const accessor = LazyAccessor.createEmpty();
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedSfixed64Element(
+            /* fieldNumber= */ 1, /* index= */ 0);
+      }).toThrowError('Index out of bounds: index: 0 size: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getRepeatedSfixed64Element(
+                 /* fieldNumber= */ 1, /* index= */ 0))
+          .toBe(undefined);
+    }
+  });
+});
+
+describe('LazyAccessor for repeated sint32 does', () => {
+  const value1 = -1;
+  const value2 = 0;
+
+  const unpackedValue1Value2 = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
+  const unpackedValue2Value1 = createArrayBuffer(0x08, 0x00, 0x08, 0x01);
+
+  const packedValue1Value2 = createArrayBuffer(0x0A, 0x02, 0x01, 0x00);
+  const packedValue2Value1 = createArrayBuffer(0x0A, 0x02, 0x00, 0x01);
+
+  it('return empty array for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list = accessor.getRepeatedSint32Iterable(1);
+
+    expectEqualToArray(list, []);
+  });
+
+  it('ensure not the same instance returned for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list1 = accessor.getRepeatedSint32Iterable(1);
+    const list2 = accessor.getRepeatedSint32Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('return size for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const size = accessor.getRepeatedSint32Size(1);
+
+    expect(size).toEqual(0);
+  });
+
+  it('return unpacked values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list = accessor.getRepeatedSint32Iterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for unpacked values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list1 = accessor.getRepeatedSint32Iterable(1);
+    const list2 = accessor.getRepeatedSint32Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedSint32Element(1, value1);
+    const list1 = accessor.getRepeatedSint32Iterable(1);
+    accessor.addUnpackedSint32Element(1, value2);
+    const list2 = accessor.getRepeatedSint32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedSint32Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedSint32Iterable(1);
+    accessor.addUnpackedSint32Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedSint32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    accessor.setUnpackedSint32Element(1, 1, value1);
+    const list = accessor.getRepeatedSint32Iterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedSint32Iterable(1, [value1]);
+    const list = accessor.getRepeatedSint32Iterable(1);
+
+    expectEqualToArray(list, [value1]);
+  });
+
+  it('encode for adding single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedSint32Element(1, value1);
+    accessor.addUnpackedSint32Element(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for adding unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedSint32Iterable(1, [value1]);
+    accessor.addUnpackedSint32Iterable(1, [value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for setting single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setUnpackedSint32Element(1, 0, value2);
+    accessor.setUnpackedSint32Element(1, 1, value1);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue2Value1);
+  });
+
+  it('encode for setting unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedSint32Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('return packed values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list = accessor.getRepeatedSint32Iterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for packed values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list1 = accessor.getRepeatedSint32Iterable(1);
+    const list2 = accessor.getRepeatedSint32Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedSint32Element(1, value1);
+    const list1 = accessor.getRepeatedSint32Iterable(1);
+    accessor.addPackedSint32Element(1, value2);
+    const list2 = accessor.getRepeatedSint32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedSint32Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedSint32Iterable(1);
+    accessor.addPackedSint32Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedSint32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedSint32Element(1, 1, value1);
+    const list = accessor.getRepeatedSint32Iterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedSint32Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedSint32Iterable(1);
+    accessor.setPackedSint32Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedSint32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value2]);
+  });
+
+  it('encode for adding single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedSint32Element(1, value1);
+    accessor.addPackedSint32Element(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for adding packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedSint32Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for setting single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedSint32Element(1, 0, value2);
+    accessor.setPackedSint32Element(1, 1, value1);
+
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue2Value1);
+  });
+
+  it('encode for setting packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedSint32Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('return combined values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x08,
+        0x01,  // unpacked value1
+        0x0A,
+        0x02,
+        0x01,
+        0x00,  // packed value1 and value2
+        0x08,
+        0x00,  // unpacked value2
+        ));
+
+    const list = accessor.getRepeatedSint32Iterable(1);
+
+    expectEqualToArray(list, [value1, value1, value2, value2]);
+  });
+
+  it('return the repeated field element from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const result1 = accessor.getRepeatedSint32Element(
+        /* fieldNumber= */ 1, /* index= */ 0);
+    const result2 = accessor.getRepeatedSint32Element(
+        /* fieldNumber= */ 1, /* index= */ 1);
+
+    expect(result1).toEqual(value1);
+    expect(result2).toEqual(value2);
+  });
+
+  it('return the size from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const size = accessor.getRepeatedSint32Size(1);
+
+    expect(size).toEqual(2);
+  });
+
+  it('fail when getting unpacked sint32 value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x80, 0x80, 0x80, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedSint32Iterable(1);
+      }).toThrowError('Expected wire type: 0 but found: 5');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expectQualifiedIterable(
+          accessor.getRepeatedSint32Iterable(1),
+          (value) => Number.isInteger(value));
+    }
+  });
+
+  it('fail when adding unpacked sint32 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSint32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedSint32Iterable(1, [fakeSint32]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedSint32Iterable(1, [fakeSint32]);
+      expectQualifiedIterable(accessor.getRepeatedSint32Iterable(1));
+    }
+  });
+
+  it('fail when adding single unpacked sint32 value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSint32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedSint32Element(1, fakeSint32))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedSint32Element(1, fakeSint32);
+      expectQualifiedIterable(accessor.getRepeatedSint32Iterable(1));
+    }
+  });
+
+  it('fail when setting unpacked sint32 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSint32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedSint32Iterable(1, [fakeSint32]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedSint32Iterable(1, [fakeSint32]);
+      expectQualifiedIterable(accessor.getRepeatedSint32Iterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked sint32 value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x80, 0x80, 0x80, 0x00));
+    const fakeSint32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedSint32Element(1, 0, fakeSint32))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedSint32Element(1, 0, fakeSint32);
+      expectQualifiedIterable(
+          accessor.getRepeatedSint32Iterable(1),
+      );
+    }
+  });
+
+  it('fail when adding packed sint32 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSint32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedSint32Iterable(1, [fakeSint32]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedSint32Iterable(1, [fakeSint32]);
+      expectQualifiedIterable(accessor.getRepeatedSint32Iterable(1));
+    }
+  });
+
+  it('fail when adding single packed sint32 value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSint32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedSint32Element(1, fakeSint32))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedSint32Element(1, fakeSint32);
+      expectQualifiedIterable(accessor.getRepeatedSint32Iterable(1));
+    }
+  });
+
+  it('fail when setting packed sint32 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSint32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedSint32Iterable(1, [fakeSint32]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedSint32Iterable(1, [fakeSint32]);
+      expectQualifiedIterable(accessor.getRepeatedSint32Iterable(1));
+    }
+  });
+
+  it('fail when setting single packed sint32 value with null value', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x00));
+    const fakeSint32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedSint32Element(1, 0, fakeSint32))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedSint32Element(1, 0, fakeSint32);
+      expectQualifiedIterable(accessor.getRepeatedSint32Iterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked with out-of-bound index', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x0A, 0x01, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedSint32Element(1, 1, 1))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedSint32Element(1, 1, 1);
+      expectQualifiedIterable(
+          accessor.getRepeatedSint32Iterable(1),
+          (value) => Number.isInteger(value));
+    }
+  });
+
+  it('fail when setting single packed with out-of-bound index', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedSint32Element(1, 1, 1))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedSint32Element(1, 1, 1);
+      expectQualifiedIterable(
+          accessor.getRepeatedSint32Iterable(1),
+          (value) => Number.isInteger(value));
+    }
+  });
+
+  it('fail when getting element with out-of-range index', () => {
+    const accessor = LazyAccessor.createEmpty();
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedSint32Element(
+            /* fieldNumber= */ 1, /* index= */ 0);
+      }).toThrowError('Index out of bounds: index: 0 size: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getRepeatedSint32Element(
+                 /* fieldNumber= */ 1, /* index= */ 0))
+          .toBe(undefined);
+    }
+  });
+});
+
+describe('LazyAccessor for repeated sint64 does', () => {
+  const value1 = Int64.fromInt(-1);
+  const value2 = Int64.fromInt(0);
+
+  const unpackedValue1Value2 = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
+  const unpackedValue2Value1 = createArrayBuffer(0x08, 0x00, 0x08, 0x01);
+
+  const packedValue1Value2 = createArrayBuffer(0x0A, 0x02, 0x01, 0x00);
+  const packedValue2Value1 = createArrayBuffer(0x0A, 0x02, 0x00, 0x01);
+
+  it('return empty array for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list = accessor.getRepeatedSint64Iterable(1);
+
+    expectEqualToArray(list, []);
+  });
+
+  it('ensure not the same instance returned for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list1 = accessor.getRepeatedSint64Iterable(1);
+    const list2 = accessor.getRepeatedSint64Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('return size for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const size = accessor.getRepeatedSint64Size(1);
+
+    expect(size).toEqual(0);
+  });
+
+  it('return unpacked values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list = accessor.getRepeatedSint64Iterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for unpacked values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list1 = accessor.getRepeatedSint64Iterable(1);
+    const list2 = accessor.getRepeatedSint64Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedSint64Element(1, value1);
+    const list1 = accessor.getRepeatedSint64Iterable(1);
+    accessor.addUnpackedSint64Element(1, value2);
+    const list2 = accessor.getRepeatedSint64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedSint64Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedSint64Iterable(1);
+    accessor.addUnpackedSint64Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedSint64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    accessor.setUnpackedSint64Element(1, 1, value1);
+    const list = accessor.getRepeatedSint64Iterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedSint64Iterable(1, [value1]);
+    const list = accessor.getRepeatedSint64Iterable(1);
+
+    expectEqualToArray(list, [value1]);
+  });
+
+  it('encode for adding single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedSint64Element(1, value1);
+    accessor.addUnpackedSint64Element(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for adding unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedSint64Iterable(1, [value1]);
+    accessor.addUnpackedSint64Iterable(1, [value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for setting single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setUnpackedSint64Element(1, 0, value2);
+    accessor.setUnpackedSint64Element(1, 1, value1);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue2Value1);
+  });
+
+  it('encode for setting unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedSint64Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('return packed values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list = accessor.getRepeatedSint64Iterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for packed values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list1 = accessor.getRepeatedSint64Iterable(1);
+    const list2 = accessor.getRepeatedSint64Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedSint64Element(1, value1);
+    const list1 = accessor.getRepeatedSint64Iterable(1);
+    accessor.addPackedSint64Element(1, value2);
+    const list2 = accessor.getRepeatedSint64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedSint64Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedSint64Iterable(1);
+    accessor.addPackedSint64Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedSint64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedSint64Element(1, 1, value1);
+    const list = accessor.getRepeatedSint64Iterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedSint64Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedSint64Iterable(1);
+    accessor.setPackedSint64Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedSint64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value2]);
+  });
+
+  it('encode for adding single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedSint64Element(1, value1);
+    accessor.addPackedSint64Element(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for adding packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedSint64Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for setting single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedSint64Element(1, 0, value2);
+    accessor.setPackedSint64Element(1, 1, value1);
+
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue2Value1);
+  });
+
+  it('encode for setting packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedSint64Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('return combined values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x08,
+        0x01,  // unpacked value1
+        0x0A,
+        0x02,
+        0x01,
+        0x00,  // packed value1 and value2
+        0x08,
+        0x00,  // unpacked value2
+        ));
+
+    const list = accessor.getRepeatedSint64Iterable(1);
+
+    expectEqualToArray(list, [value1, value1, value2, value2]);
+  });
+
+  it('return the repeated field element from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const result1 = accessor.getRepeatedSint64Element(
+        /* fieldNumber= */ 1, /* index= */ 0);
+    const result2 = accessor.getRepeatedSint64Element(
+        /* fieldNumber= */ 1, /* index= */ 1);
+
+    expect(result1).toEqual(value1);
+    expect(result2).toEqual(value2);
+  });
+
+  it('return the size from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const size = accessor.getRepeatedSint64Size(1);
+
+    expect(size).toEqual(2);
+  });
+
+  it('fail when getting unpacked sint64 value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x80, 0x80, 0x80, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedSint64Iterable(1);
+      }).toThrowError('Expected wire type: 0 but found: 5');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expectQualifiedIterable(
+          accessor.getRepeatedSint64Iterable(1),
+          (value) => value instanceof Int64);
+    }
+  });
+
+  it('fail when adding unpacked sint64 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSint64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedSint64Iterable(1, [fakeSint64]))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedSint64Iterable(1, [fakeSint64]);
+      expectQualifiedIterable(accessor.getRepeatedSint64Iterable(1));
+    }
+  });
+
+  it('fail when adding single unpacked sint64 value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSint64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedSint64Element(1, fakeSint64))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedSint64Element(1, fakeSint64);
+      expectQualifiedIterable(accessor.getRepeatedSint64Iterable(1));
+    }
+  });
+
+  it('fail when setting unpacked sint64 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSint64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedSint64Iterable(1, [fakeSint64]))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedSint64Iterable(1, [fakeSint64]);
+      expectQualifiedIterable(accessor.getRepeatedSint64Iterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked sint64 value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x80, 0x80, 0x80, 0x00));
+    const fakeSint64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedSint64Element(1, 0, fakeSint64))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedSint64Element(1, 0, fakeSint64);
+      expectQualifiedIterable(accessor.getRepeatedSint64Iterable(1));
+    }
+  });
+
+  it('fail when adding packed sint64 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSint64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedSint64Iterable(1, [fakeSint64]))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedSint64Iterable(1, [fakeSint64]);
+      expectQualifiedIterable(accessor.getRepeatedSint64Iterable(1));
+    }
+  });
+
+  it('fail when adding single packed sint64 value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSint64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedSint64Element(1, fakeSint64))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedSint64Element(1, fakeSint64);
+      expectQualifiedIterable(accessor.getRepeatedSint64Iterable(1));
+    }
+  });
+
+  it('fail when setting packed sint64 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeSint64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedSint64Iterable(1, [fakeSint64]))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedSint64Iterable(1, [fakeSint64]);
+      expectQualifiedIterable(accessor.getRepeatedSint64Iterable(1));
+    }
+  });
+
+  it('fail when setting single packed sint64 value with null value', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x00));
+    const fakeSint64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedSint64Element(1, 0, fakeSint64))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedSint64Element(1, 0, fakeSint64);
+      expectQualifiedIterable(accessor.getRepeatedSint64Iterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked with out-of-bound index', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x0A, 0x01, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedSint64Element(1, 1, Int64.fromInt(1)))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedSint64Element(1, 1, Int64.fromInt(1));
+      expectQualifiedIterable(
+          accessor.getRepeatedSint64Iterable(1),
+          (value) => value instanceof Int64);
+    }
+  });
+
+  it('fail when setting single packed with out-of-bound index', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedSint64Element(1, 1, Int64.fromInt(1)))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedSint64Element(1, 1, Int64.fromInt(1));
+      expectQualifiedIterable(
+          accessor.getRepeatedSint64Iterable(1),
+          (value) => value instanceof Int64);
+    }
+  });
+
+  it('fail when getting element with out-of-range index', () => {
+    const accessor = LazyAccessor.createEmpty();
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedSint64Element(
+            /* fieldNumber= */ 1, /* index= */ 0);
+      }).toThrowError('Index out of bounds: index: 0 size: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getRepeatedSint64Element(
+                 /* fieldNumber= */ 1, /* index= */ 0))
+          .toBe(undefined);
+    }
+  });
+});
+
+describe('LazyAccessor for repeated uint32 does', () => {
+  const value1 = 1;
+  const value2 = 0;
+
+  const unpackedValue1Value2 = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
+  const unpackedValue2Value1 = createArrayBuffer(0x08, 0x00, 0x08, 0x01);
+
+  const packedValue1Value2 = createArrayBuffer(0x0A, 0x02, 0x01, 0x00);
+  const packedValue2Value1 = createArrayBuffer(0x0A, 0x02, 0x00, 0x01);
+
+  it('return empty array for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list = accessor.getRepeatedUint32Iterable(1);
+
+    expectEqualToArray(list, []);
+  });
+
+  it('ensure not the same instance returned for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list1 = accessor.getRepeatedUint32Iterable(1);
+    const list2 = accessor.getRepeatedUint32Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('return size for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const size = accessor.getRepeatedUint32Size(1);
+
+    expect(size).toEqual(0);
+  });
+
+  it('return unpacked values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list = accessor.getRepeatedUint32Iterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for unpacked values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list1 = accessor.getRepeatedUint32Iterable(1);
+    const list2 = accessor.getRepeatedUint32Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedUint32Element(1, value1);
+    const list1 = accessor.getRepeatedUint32Iterable(1);
+    accessor.addUnpackedUint32Element(1, value2);
+    const list2 = accessor.getRepeatedUint32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedUint32Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedUint32Iterable(1);
+    accessor.addUnpackedUint32Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedUint32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    accessor.setUnpackedUint32Element(1, 1, value1);
+    const list = accessor.getRepeatedUint32Iterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedUint32Iterable(1, [value1]);
+    const list = accessor.getRepeatedUint32Iterable(1);
+
+    expectEqualToArray(list, [value1]);
+  });
+
+  it('encode for adding single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedUint32Element(1, value1);
+    accessor.addUnpackedUint32Element(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for adding unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedUint32Iterable(1, [value1]);
+    accessor.addUnpackedUint32Iterable(1, [value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for setting single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setUnpackedUint32Element(1, 0, value2);
+    accessor.setUnpackedUint32Element(1, 1, value1);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue2Value1);
+  });
+
+  it('encode for setting unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedUint32Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('return packed values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list = accessor.getRepeatedUint32Iterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for packed values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list1 = accessor.getRepeatedUint32Iterable(1);
+    const list2 = accessor.getRepeatedUint32Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedUint32Element(1, value1);
+    const list1 = accessor.getRepeatedUint32Iterable(1);
+    accessor.addPackedUint32Element(1, value2);
+    const list2 = accessor.getRepeatedUint32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedUint32Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedUint32Iterable(1);
+    accessor.addPackedUint32Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedUint32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedUint32Element(1, 1, value1);
+    const list = accessor.getRepeatedUint32Iterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedUint32Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedUint32Iterable(1);
+    accessor.setPackedUint32Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedUint32Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value2]);
+  });
+
+  it('encode for adding single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedUint32Element(1, value1);
+    accessor.addPackedUint32Element(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for adding packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedUint32Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for setting single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedUint32Element(1, 0, value2);
+    accessor.setPackedUint32Element(1, 1, value1);
+
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue2Value1);
+  });
+
+  it('encode for setting packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedUint32Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('return combined values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x08,
+        0x01,  // unpacked value1
+        0x0A,
+        0x02,
+        0x01,
+        0x00,  // packed value1 and value2
+        0x08,
+        0x00,  // unpacked value2
+        ));
+
+    const list = accessor.getRepeatedUint32Iterable(1);
+
+    expectEqualToArray(list, [value1, value1, value2, value2]);
+  });
+
+  it('return the repeated field element from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const result1 = accessor.getRepeatedUint32Element(
+        /* fieldNumber= */ 1, /* index= */ 0);
+    const result2 = accessor.getRepeatedUint32Element(
+        /* fieldNumber= */ 1, /* index= */ 1);
+
+    expect(result1).toEqual(value1);
+    expect(result2).toEqual(value2);
+  });
+
+  it('return the size from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const size = accessor.getRepeatedUint32Size(1);
+
+    expect(size).toEqual(2);
+  });
+
+  it('fail when getting unpacked uint32 value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x80, 0x80, 0x80, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedUint32Iterable(1);
+      }).toThrowError('Expected wire type: 0 but found: 5');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expectQualifiedIterable(
+          accessor.getRepeatedUint32Iterable(1),
+          (value) => Number.isInteger(value));
+    }
+  });
+
+  it('fail when adding unpacked uint32 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeUint32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedUint32Iterable(1, [fakeUint32]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedUint32Iterable(1, [fakeUint32]);
+      expectQualifiedIterable(accessor.getRepeatedUint32Iterable(1));
+    }
+  });
+
+  it('fail when adding single unpacked uint32 value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeUint32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedUint32Element(1, fakeUint32))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedUint32Element(1, fakeUint32);
+      expectQualifiedIterable(accessor.getRepeatedUint32Iterable(1));
+    }
+  });
+
+  it('fail when setting unpacked uint32 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeUint32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedUint32Iterable(1, [fakeUint32]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedUint32Iterable(1, [fakeUint32]);
+      expectQualifiedIterable(accessor.getRepeatedUint32Iterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked uint32 value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x80, 0x80, 0x80, 0x00));
+    const fakeUint32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedUint32Element(1, 0, fakeUint32))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedUint32Element(1, 0, fakeUint32);
+      expectQualifiedIterable(
+          accessor.getRepeatedUint32Iterable(1),
+      );
+    }
+  });
+
+  it('fail when adding packed uint32 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeUint32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedUint32Iterable(1, [fakeUint32]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedUint32Iterable(1, [fakeUint32]);
+      expectQualifiedIterable(accessor.getRepeatedUint32Iterable(1));
+    }
+  });
+
+  it('fail when adding single packed uint32 value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeUint32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedUint32Element(1, fakeUint32))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedUint32Element(1, fakeUint32);
+      expectQualifiedIterable(accessor.getRepeatedUint32Iterable(1));
+    }
+  });
+
+  it('fail when setting packed uint32 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeUint32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedUint32Iterable(1, [fakeUint32]))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedUint32Iterable(1, [fakeUint32]);
+      expectQualifiedIterable(accessor.getRepeatedUint32Iterable(1));
+    }
+  });
+
+  it('fail when setting single packed uint32 value with null value', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x00));
+    const fakeUint32 = /** @type {number} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedUint32Element(1, 0, fakeUint32))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedUint32Element(1, 0, fakeUint32);
+      expectQualifiedIterable(accessor.getRepeatedUint32Iterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked with out-of-bound index', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x0A, 0x01, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedUint32Element(1, 1, 1))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedUint32Element(1, 1, 1);
+      expectQualifiedIterable(
+          accessor.getRepeatedUint32Iterable(1),
+          (value) => Number.isInteger(value));
+    }
+  });
+
+  it('fail when setting single packed with out-of-bound index', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedUint32Element(1, 1, 1))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedUint32Element(1, 1, 1);
+      expectQualifiedIterable(
+          accessor.getRepeatedUint32Iterable(1),
+          (value) => Number.isInteger(value));
+    }
+  });
+
+  it('fail when getting element with out-of-range index', () => {
+    const accessor = LazyAccessor.createEmpty();
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedUint32Element(
+            /* fieldNumber= */ 1, /* index= */ 0);
+      }).toThrowError('Index out of bounds: index: 0 size: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getRepeatedUint32Element(
+                 /* fieldNumber= */ 1, /* index= */ 0))
+          .toBe(undefined);
+    }
+  });
+});
+
+describe('LazyAccessor for repeated uint64 does', () => {
+  const value1 = Int64.fromInt(1);
+  const value2 = Int64.fromInt(0);
+
+  const unpackedValue1Value2 = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
+  const unpackedValue2Value1 = createArrayBuffer(0x08, 0x00, 0x08, 0x01);
+
+  const packedValue1Value2 = createArrayBuffer(0x0A, 0x02, 0x01, 0x00);
+  const packedValue2Value1 = createArrayBuffer(0x0A, 0x02, 0x00, 0x01);
+
+  it('return empty array for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list = accessor.getRepeatedUint64Iterable(1);
+
+    expectEqualToArray(list, []);
+  });
+
+  it('ensure not the same instance returned for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list1 = accessor.getRepeatedUint64Iterable(1);
+    const list2 = accessor.getRepeatedUint64Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('return size for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const size = accessor.getRepeatedUint64Size(1);
+
+    expect(size).toEqual(0);
+  });
+
+  it('return unpacked values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list = accessor.getRepeatedUint64Iterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for unpacked values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const list1 = accessor.getRepeatedUint64Iterable(1);
+    const list2 = accessor.getRepeatedUint64Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedUint64Element(1, value1);
+    const list1 = accessor.getRepeatedUint64Iterable(1);
+    accessor.addUnpackedUint64Element(1, value2);
+    const list2 = accessor.getRepeatedUint64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedUint64Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedUint64Iterable(1);
+    accessor.addUnpackedUint64Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedUint64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    accessor.setUnpackedUint64Element(1, 1, value1);
+    const list = accessor.getRepeatedUint64Iterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedUint64Iterable(1, [value1]);
+    const list = accessor.getRepeatedUint64Iterable(1);
+
+    expectEqualToArray(list, [value1]);
+  });
+
+  it('encode for adding single unpacked value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedUint64Element(1, value1);
+    accessor.addUnpackedUint64Element(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for adding unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedUint64Iterable(1, [value1]);
+    accessor.addUnpackedUint64Iterable(1, [value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('encode for setting single unpacked value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setUnpackedUint64Element(1, 0, value2);
+    accessor.setUnpackedUint64Element(1, 1, value1);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue2Value1);
+  });
+
+  it('encode for setting unpacked values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setUnpackedUint64Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(unpackedValue1Value2);
+  });
+
+  it('return packed values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list = accessor.getRepeatedUint64Iterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for packed values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(packedValue1Value2);
+
+    const list1 = accessor.getRepeatedUint64Iterable(1);
+    const list2 = accessor.getRepeatedUint64Iterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedUint64Element(1, value1);
+    const list1 = accessor.getRepeatedUint64Iterable(1);
+    accessor.addPackedUint64Element(1, value2);
+    const list2 = accessor.getRepeatedUint64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedUint64Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedUint64Iterable(1);
+    accessor.addPackedUint64Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedUint64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedUint64Element(1, 1, value1);
+    const list = accessor.getRepeatedUint64Iterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedUint64Iterable(1, [value1]);
+    const list1 = accessor.getRepeatedUint64Iterable(1);
+    accessor.setPackedUint64Iterable(1, [value2]);
+    const list2 = accessor.getRepeatedUint64Iterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value2]);
+  });
+
+  it('encode for adding single packed value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedUint64Element(1, value1);
+    accessor.addPackedUint64Element(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for adding packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addPackedUint64Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('encode for setting single packed value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    accessor.setPackedUint64Element(1, 0, value2);
+    accessor.setPackedUint64Element(1, 1, value1);
+
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue2Value1);
+  });
+
+  it('encode for setting packed values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setPackedUint64Iterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(packedValue1Value2);
+  });
+
+  it('return combined values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x08,
+        0x01,  // unpacked value1
+        0x0A,
+        0x02,
+        0x01,
+        0x00,  // packed value1 and value2
+        0x08,
+        0x00,  // unpacked value2
+        ));
+
+    const list = accessor.getRepeatedUint64Iterable(1);
+
+    expectEqualToArray(list, [value1, value1, value2, value2]);
+  });
+
+  it('return the repeated field element from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const result1 = accessor.getRepeatedUint64Element(
+        /* fieldNumber= */ 1, /* index= */ 0);
+    const result2 = accessor.getRepeatedUint64Element(
+        /* fieldNumber= */ 1, /* index= */ 1);
+
+    expect(result1).toEqual(value1);
+    expect(result2).toEqual(value2);
+  });
+
+  it('return the size from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(unpackedValue1Value2);
+
+    const size = accessor.getRepeatedUint64Size(1);
+
+    expect(size).toEqual(2);
+  });
+
+  it('fail when getting unpacked uint64 value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x80, 0x80, 0x80, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedUint64Iterable(1);
+      }).toThrowError('Expected wire type: 0 but found: 5');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expectQualifiedIterable(
+          accessor.getRepeatedUint64Iterable(1),
+          (value) => value instanceof Int64);
+    }
+  });
+
+  it('fail when adding unpacked uint64 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeUint64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedUint64Iterable(1, [fakeUint64]))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedUint64Iterable(1, [fakeUint64]);
+      expectQualifiedIterable(accessor.getRepeatedUint64Iterable(1));
+    }
+  });
+
+  it('fail when adding single unpacked uint64 value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeUint64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addUnpackedUint64Element(1, fakeUint64))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addUnpackedUint64Element(1, fakeUint64);
+      expectQualifiedIterable(accessor.getRepeatedUint64Iterable(1));
+    }
+  });
+
+  it('fail when setting unpacked uint64 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeUint64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedUint64Iterable(1, [fakeUint64]))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedUint64Iterable(1, [fakeUint64]);
+      expectQualifiedIterable(accessor.getRepeatedUint64Iterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked uint64 value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x80, 0x80, 0x80, 0x00));
+    const fakeUint64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedUint64Element(1, 0, fakeUint64))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedUint64Element(1, 0, fakeUint64);
+      expectQualifiedIterable(accessor.getRepeatedUint64Iterable(1));
+    }
+  });
+
+  it('fail when adding packed uint64 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeUint64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedUint64Iterable(1, [fakeUint64]))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedUint64Iterable(1, [fakeUint64]);
+      expectQualifiedIterable(accessor.getRepeatedUint64Iterable(1));
+    }
+  });
+
+  it('fail when adding single packed uint64 value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeUint64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addPackedUint64Element(1, fakeUint64))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addPackedUint64Element(1, fakeUint64);
+      expectQualifiedIterable(accessor.getRepeatedUint64Iterable(1));
+    }
+  });
+
+  it('fail when setting packed uint64 values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeUint64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedUint64Iterable(1, [fakeUint64]))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedUint64Iterable(1, [fakeUint64]);
+      expectQualifiedIterable(accessor.getRepeatedUint64Iterable(1));
+    }
+  });
+
+  it('fail when setting single packed uint64 value with null value', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x00));
+    const fakeUint64 = /** @type {!Int64} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedUint64Element(1, 0, fakeUint64))
+          .toThrowError('Must be Int64 instance, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedUint64Element(1, 0, fakeUint64);
+      expectQualifiedIterable(accessor.getRepeatedUint64Iterable(1));
+    }
+  });
+
+  it('fail when setting single unpacked with out-of-bound index', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x0A, 0x01, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setUnpackedUint64Element(1, 1, Int64.fromInt(1)))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setUnpackedUint64Element(1, 1, Int64.fromInt(1));
+      expectQualifiedIterable(
+          accessor.getRepeatedUint64Iterable(1),
+          (value) => value instanceof Int64);
+    }
+  });
+
+  it('fail when setting single packed with out-of-bound index', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setPackedUint64Element(1, 1, Int64.fromInt(1)))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setPackedUint64Element(1, 1, Int64.fromInt(1));
+      expectQualifiedIterable(
+          accessor.getRepeatedUint64Iterable(1),
+          (value) => value instanceof Int64);
+    }
+  });
+
+  it('fail when getting element with out-of-range index', () => {
+    const accessor = LazyAccessor.createEmpty();
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedUint64Element(
+            /* fieldNumber= */ 1, /* index= */ 0);
+      }).toThrowError('Index out of bounds: index: 0 size: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getRepeatedUint64Element(
+                 /* fieldNumber= */ 1, /* index= */ 0))
+          .toBe(undefined);
+    }
+  });
+});
+
+describe('LazyAccessor for repeated bytes does', () => {
+  const value1 = ByteString.fromArrayBuffer((createArrayBuffer(0x61)));
+  const value2 = ByteString.fromArrayBuffer((createArrayBuffer(0x62)));
+
+  const repeatedValue1Value2 = createArrayBuffer(
+      0x0A,
+      0x01,
+      0x61,  // value1
+      0x0A,
+      0x01,
+      0x62,  // value2
+  );
+  const repeatedValue2Value1 = createArrayBuffer(
+      0x0A,
+      0x01,
+      0x62,  // value2
+      0x0A,
+      0x01,
+      0x61,  // value1
+  );
+
+  it('return empty array for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list = accessor.getRepeatedBytesIterable(1);
+
+    expectEqualToArray(list, []);
+  });
+
+  it('ensure not the same instance returned for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list1 = accessor.getRepeatedBytesIterable(1);
+    const list2 = accessor.getRepeatedBytesIterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('return size for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const size = accessor.getRepeatedBytesSize(1);
+
+    expect(size).toEqual(0);
+  });
+
+  it('return values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(repeatedValue1Value2);
+
+    const list = accessor.getRepeatedBytesIterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(repeatedValue1Value2);
+
+    const list1 = accessor.getRepeatedBytesIterable(1);
+    const list2 = accessor.getRepeatedBytesIterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addRepeatedBytesElement(1, value1);
+    const list1 = accessor.getRepeatedBytesIterable(1);
+    accessor.addRepeatedBytesElement(1, value2);
+    const list2 = accessor.getRepeatedBytesIterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addRepeatedBytesIterable(1, [value1]);
+    const list1 = accessor.getRepeatedBytesIterable(1);
+    accessor.addRepeatedBytesIterable(1, [value2]);
+    const list2 = accessor.getRepeatedBytesIterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(repeatedValue1Value2);
+
+    accessor.setRepeatedBytesElement(1, 1, value1);
+    const list = accessor.getRepeatedBytesIterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setRepeatedBytesIterable(1, [value1]);
+    const list = accessor.getRepeatedBytesIterable(1);
+
+    expectEqualToArray(list, [value1]);
+  });
+
+  it('encode for adding single value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addRepeatedBytesElement(1, value1);
+    accessor.addRepeatedBytesElement(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(repeatedValue1Value2);
+  });
+
+  it('encode for adding values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addRepeatedBytesIterable(1, [value1]);
+    accessor.addRepeatedBytesIterable(1, [value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(repeatedValue1Value2);
+  });
+
+  it('encode for setting single value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(repeatedValue1Value2);
+
+    accessor.setRepeatedBytesElement(1, 0, value2);
+    accessor.setRepeatedBytesElement(1, 1, value1);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(repeatedValue2Value1);
+  });
+
+  it('encode for setting values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setRepeatedBytesIterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(repeatedValue1Value2);
+  });
+
+  it('return the repeated field element from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(repeatedValue1Value2);
+
+    const result1 = accessor.getRepeatedBytesElement(
+        /* fieldNumber= */ 1, /* index= */ 0);
+    const result2 = accessor.getRepeatedBytesElement(
+        /* fieldNumber= */ 1, /* index= */ 1);
+
+    expect(result1).toEqual(value1);
+    expect(result2).toEqual(value2);
+  });
+
+  it('return the size from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(repeatedValue1Value2);
+
+    const size = accessor.getRepeatedBytesSize(1);
+
+    expect(size).toEqual(2);
+  });
+
+  it('fail when getting bytes value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedBytesIterable(1);
+      }).toThrowError('Expected wire type: 2 but found: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expectQualifiedIterable(
+          accessor.getRepeatedBytesIterable(1),
+          (value) => value instanceof ByteString);
+    }
+  });
+
+  it('fail when adding bytes values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeBytes = /** @type {!ByteString} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addRepeatedBytesIterable(1, [fakeBytes]))
+          .toThrowError('Must be a ByteString, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addRepeatedBytesIterable(1, [fakeBytes]);
+      expectQualifiedIterable(accessor.getRepeatedBytesIterable(1));
+    }
+  });
+
+  it('fail when adding single bytes value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeBytes = /** @type {!ByteString} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addRepeatedBytesElement(1, fakeBytes))
+          .toThrowError('Must be a ByteString, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addRepeatedBytesElement(1, fakeBytes);
+      expectQualifiedIterable(accessor.getRepeatedBytesIterable(1));
+    }
+  });
+
+  it('fail when setting bytes values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeBytes = /** @type {!ByteString} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setRepeatedBytesIterable(1, [fakeBytes]))
+          .toThrowError('Must be a ByteString, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setRepeatedBytesIterable(1, [fakeBytes]);
+      expectQualifiedIterable(accessor.getRepeatedBytesIterable(1));
+    }
+  });
+
+  it('fail when setting single bytes value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00));
+    const fakeBytes = /** @type {!ByteString} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setRepeatedBytesElement(1, 0, fakeBytes))
+          .toThrowError('Must be a ByteString, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setRepeatedBytesElement(1, 0, fakeBytes);
+      expectQualifiedIterable(accessor.getRepeatedBytesIterable(1));
+    }
+  });
+
+  it('fail when setting single with out-of-bound index', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x0A, 0x01, 0x61));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setRepeatedBytesElement(1, 1, value1))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setRepeatedBytesElement(1, 1, value1);
+      expectQualifiedIterable(
+          accessor.getRepeatedBytesIterable(1),
+          (value) => value instanceof ByteString);
+    }
+  });
+
+  it('fail when getting element with out-of-range index', () => {
+    const accessor = LazyAccessor.createEmpty();
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedBytesElement(
+            /* fieldNumber= */ 1, /* index= */ 0);
+      }).toThrowError('Index out of bounds: index: 0 size: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getRepeatedBytesElement(
+                 /* fieldNumber= */ 1, /* index= */ 0))
+          .toBe(undefined);
+    }
+  });
+});
+
+describe('LazyAccessor for repeated string does', () => {
+  const value1 = 'a';
+  const value2 = 'b';
+
+  const repeatedValue1Value2 = createArrayBuffer(
+      0x0A,
+      0x01,
+      0x61,  // value1
+      0x0A,
+      0x01,
+      0x62,  // value2
+  );
+  const repeatedValue2Value1 = createArrayBuffer(
+      0x0A,
+      0x01,
+      0x62,  // value2
+      0x0A,
+      0x01,
+      0x61,  // value1
+  );
+
+  it('return empty array for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list = accessor.getRepeatedStringIterable(1);
+
+    expectEqualToArray(list, []);
+  });
+
+  it('ensure not the same instance returned for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const list1 = accessor.getRepeatedStringIterable(1);
+    const list2 = accessor.getRepeatedStringIterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('return size for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    const size = accessor.getRepeatedStringSize(1);
+
+    expect(size).toEqual(0);
+  });
+
+  it('return values from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(repeatedValue1Value2);
+
+    const list = accessor.getRepeatedStringIterable(1);
+
+    expectEqualToArray(list, [value1, value2]);
+  });
+
+  it('ensure not the same instance returned for values', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(repeatedValue1Value2);
+
+    const list1 = accessor.getRepeatedStringIterable(1);
+    const list2 = accessor.getRepeatedStringIterable(1);
+
+    expect(list1).not.toBe(list2);
+  });
+
+  it('add single value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addRepeatedStringElement(1, value1);
+    const list1 = accessor.getRepeatedStringIterable(1);
+    accessor.addRepeatedStringElement(1, value2);
+    const list2 = accessor.getRepeatedStringIterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('add values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addRepeatedStringIterable(1, [value1]);
+    const list1 = accessor.getRepeatedStringIterable(1);
+    accessor.addRepeatedStringIterable(1, [value2]);
+    const list2 = accessor.getRepeatedStringIterable(1);
+
+    expectEqualToArray(list1, [value1]);
+    expectEqualToArray(list2, [value1, value2]);
+  });
+
+  it('set a single value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(repeatedValue1Value2);
+
+    accessor.setRepeatedStringElement(1, 1, value1);
+    const list = accessor.getRepeatedStringIterable(1);
+
+    expectEqualToArray(list, [value1, value1]);
+  });
+
+  it('set values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setRepeatedStringIterable(1, [value1]);
+    const list = accessor.getRepeatedStringIterable(1);
+
+    expectEqualToArray(list, [value1]);
+  });
+
+  it('encode for adding single value', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addRepeatedStringElement(1, value1);
+    accessor.addRepeatedStringElement(1, value2);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(repeatedValue1Value2);
+  });
+
+  it('encode for adding values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addRepeatedStringIterable(1, [value1]);
+    accessor.addRepeatedStringIterable(1, [value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(repeatedValue1Value2);
+  });
+
+  it('encode for setting single value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(repeatedValue1Value2);
+
+    accessor.setRepeatedStringElement(1, 0, value2);
+    accessor.setRepeatedStringElement(1, 1, value1);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(repeatedValue2Value1);
+  });
+
+  it('encode for setting values', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.setRepeatedStringIterable(1, [value1, value2]);
+    const serialized = accessor.serialize();
+
+    expect(serialized).toEqual(repeatedValue1Value2);
+  });
+
+  it('return the repeated field element from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(repeatedValue1Value2);
+
+    const result1 = accessor.getRepeatedStringElement(
+        /* fieldNumber= */ 1, /* index= */ 0);
+    const result2 = accessor.getRepeatedStringElement(
+        /* fieldNumber= */ 1, /* index= */ 1);
+
+    expect(result1).toEqual(value1);
+    expect(result2).toEqual(value2);
+  });
+
+  it('return the size from the input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(repeatedValue1Value2);
+
+    const size = accessor.getRepeatedStringSize(1);
+
+    expect(size).toEqual(2);
+  });
+
+  it('fail when getting string value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedStringIterable(1);
+      }).toThrowError('Expected wire type: 2 but found: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expectQualifiedIterable(
+          accessor.getRepeatedStringIterable(1),
+          (value) => typeof value === 'string');
+    }
+  });
+
+  it('fail when adding string values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeString = /** @type {string} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addRepeatedStringIterable(1, [fakeString]))
+          .toThrowError('Must be string, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addRepeatedStringIterable(1, [fakeString]);
+      expectQualifiedIterable(accessor.getRepeatedStringIterable(1));
+    }
+  });
+
+  it('fail when adding single string value with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeString = /** @type {string} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.addRepeatedStringElement(1, fakeString))
+          .toThrowError('Must be string, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addRepeatedStringElement(1, fakeString);
+      expectQualifiedIterable(accessor.getRepeatedStringIterable(1));
+    }
+  });
+
+  it('fail when setting string values with null value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeString = /** @type {string} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setRepeatedStringIterable(1, [fakeString]))
+          .toThrowError('Must be string, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setRepeatedStringIterable(1, [fakeString]);
+      expectQualifiedIterable(accessor.getRepeatedStringIterable(1));
+    }
+  });
+
+  it('fail when setting single string value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00));
+    const fakeString = /** @type {string} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setRepeatedStringElement(1, 0, fakeString))
+          .toThrowError('Must be string, but got: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setRepeatedStringElement(1, 0, fakeString);
+      expectQualifiedIterable(accessor.getRepeatedStringIterable(1));
+    }
+  });
+
+  it('fail when setting single with out-of-bound index', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x0A, 0x01, 0x61));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setRepeatedStringElement(1, 1, value1))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setRepeatedStringElement(1, 1, value1);
+      expectQualifiedIterable(
+          accessor.getRepeatedStringIterable(1),
+          (value) => typeof value === 'string');
+    }
+  });
+
+  it('fail when getting element with out-of-range index', () => {
+    const accessor = LazyAccessor.createEmpty();
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedStringElement(
+            /* fieldNumber= */ 1, /* index= */ 0);
+      }).toThrowError('Index out of bounds: index: 0 size: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getRepeatedStringElement(
+                 /* fieldNumber= */ 1, /* index= */ 0))
+          .toBe(undefined);
+    }
+  });
+});
+
+describe('LazyAccessor for repeated message does', () => {
+  it('return empty array for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+    expectEqualToArray(
+        accessor.getRepeatedMessageIterable(1, TestMessage.instanceCreator),
+        []);
+  });
+
+  it('return empty accessor array for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+    expectEqualToArray(accessor.getRepeatedMessageAccessorIterable(1), []);
+  });
+
+  it('ensure not the same instance returned for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const list1 =
+        accessor.getRepeatedMessageIterable(1, TestMessage.instanceCreator);
+    const list2 =
+        accessor.getRepeatedMessageIterable(1, TestMessage.instanceCreator);
+    expect(list1).not.toBe(list2);
+  });
+
+  it('return size for the empty input', () => {
+    const accessor = LazyAccessor.createEmpty();
+    expect(accessor.getRepeatedMessageSize(1, TestMessage.instanceCreator))
+        .toEqual(0);
+  });
+
+  it('return values from the input', () => {
+    const bytes1 = createArrayBuffer(0x08, 0x01);
+    const bytes2 = createArrayBuffer(0x08, 0x00);
+    const msg1 = new TestMessage(LazyAccessor.fromArrayBuffer(bytes1));
+    const msg2 = new TestMessage(LazyAccessor.fromArrayBuffer(bytes2));
+
+    const bytes =
+        createArrayBuffer(0x0A, 0x02, 0x08, 0x01, 0x0A, 0x02, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expectEqualToMessageArray(
+        accessor.getRepeatedMessageIterable(1, TestMessage.instanceCreator),
+        [msg1, msg2]);
+  });
+
+  it('ensure not the same array instance returned', () => {
+    const bytes =
+        createArrayBuffer(0x0A, 0x02, 0x08, 0x01, 0x0A, 0x02, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const list1 =
+        accessor.getRepeatedMessageIterable(1, TestMessage.instanceCreator);
+    const list2 =
+        accessor.getRepeatedMessageIterable(1, TestMessage.instanceCreator);
+    expect(list1).not.toBe(list2);
+  });
+
+  it('ensure the same array element returned for get iterable', () => {
+    const bytes =
+        createArrayBuffer(0x0A, 0x02, 0x08, 0x01, 0x0A, 0x02, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const list1 =
+        accessor.getRepeatedMessageIterable(1, TestMessage.instanceCreator);
+    const list2 = accessor.getRepeatedMessageIterable(
+        1, TestMessage.instanceCreator, /* pivot= */ 0);
+    const array1 = Array.from(list1);
+    const array2 = Array.from(list2);
+    for (let i = 0; i < array1.length; i++) {
+      expect(array1[i]).toBe(array2[i]);
+    }
+  });
+
+  it('return accessors from the input', () => {
+    const bytes =
+        createArrayBuffer(0x0A, 0x02, 0x08, 0x01, 0x0A, 0x02, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const [accessor1, accessor2] =
+        [...accessor.getRepeatedMessageAccessorIterable(1)];
+    expect(accessor1.getInt32WithDefault(1)).toEqual(1);
+    expect(accessor2.getInt32WithDefault(1)).toEqual(0);
+  });
+
+  it('return accessors from the input when pivot is set', () => {
+    const bytes =
+        createArrayBuffer(0x0A, 0x02, 0x08, 0x01, 0x0A, 0x02, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const [accessor1, accessor2] =
+        [...accessor.getRepeatedMessageAccessorIterable(1, /* pivot= */ 0)];
+    expect(accessor1.getInt32WithDefault(1)).toEqual(1);
+    expect(accessor2.getInt32WithDefault(1)).toEqual(0);
+  });
+
+  it('return the repeated field element from the input', () => {
+    const bytes =
+        createArrayBuffer(0x0A, 0x02, 0x08, 0x01, 0x0A, 0x02, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const msg1 = accessor.getRepeatedMessageElement(
+        /* fieldNumber= */ 1, TestMessage.instanceCreator,
+        /* index= */ 0);
+    const msg2 = accessor.getRepeatedMessageElement(
+        /* fieldNumber= */ 1, TestMessage.instanceCreator,
+        /* index= */ 1, /* pivot= */ 0);
+    expect(msg1.getBoolWithDefault(
+               /* fieldNumber= */ 1, /* default= */ false))
+        .toEqual(true);
+    expect(msg2.getBoolWithDefault(
+               /* fieldNumber= */ 1, /* default= */ false))
+        .toEqual(false);
+  });
+
+  it('ensure the same array element returned', () => {
+    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const msg1 = accessor.getRepeatedMessageElement(
+        /* fieldNumber= */ 1, TestMessage.instanceCreator,
+        /* index= */ 0);
+    const msg2 = accessor.getRepeatedMessageElement(
+        /* fieldNumber= */ 1, TestMessage.instanceCreator,
+        /* index= */ 0);
+    expect(msg1).toBe(msg2);
+  });
+
+  it('return the size from the input', () => {
+    const bytes =
+        createArrayBuffer(0x0A, 0x02, 0x08, 0x01, 0x0A, 0x02, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getRepeatedMessageSize(1, TestMessage.instanceCreator))
+        .toEqual(2);
+  });
+
+  it('encode repeated message from the input', () => {
+    const bytes =
+        createArrayBuffer(0x0A, 0x02, 0x08, 0x01, 0x0A, 0x02, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.serialize()).toEqual(bytes);
+  });
+
+  it('add a single value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const bytes1 = createArrayBuffer(0x08, 0x01);
+    const msg1 = new TestMessage(LazyAccessor.fromArrayBuffer(bytes1));
+    const bytes2 = createArrayBuffer(0x08, 0x00);
+    const msg2 = new TestMessage(LazyAccessor.fromArrayBuffer(bytes2));
+
+    accessor.addRepeatedMessageElement(1, msg1, TestMessage.instanceCreator);
+    accessor.addRepeatedMessageElement(1, msg2, TestMessage.instanceCreator);
+    const result =
+        accessor.getRepeatedMessageIterable(1, TestMessage.instanceCreator);
+
+    expect(Array.from(result)).toEqual([msg1, msg2]);
+  });
+
+  it('add values', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const bytes1 = createArrayBuffer(0x08, 0x01);
+    const msg1 = new TestMessage(LazyAccessor.fromArrayBuffer(bytes1));
+    const bytes2 = createArrayBuffer(0x08, 0x00);
+    const msg2 = new TestMessage(LazyAccessor.fromArrayBuffer(bytes2));
+
+    accessor.addRepeatedMessageIterable(1, [msg1], TestMessage.instanceCreator);
+    accessor.addRepeatedMessageIterable(1, [msg2], TestMessage.instanceCreator);
+    const result =
+        accessor.getRepeatedMessageIterable(1, TestMessage.instanceCreator);
+
+    expect(Array.from(result)).toEqual([msg1, msg2]);
+  });
+
+  it('set a single value', () => {
+    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const subbytes = createArrayBuffer(0x08, 0x01);
+    const submsg = new TestMessage(LazyAccessor.fromArrayBuffer(subbytes));
+
+    accessor.setRepeatedMessageElement(
+        /* fieldNumber= */ 1, submsg, TestMessage.instanceCreator,
+        /* index= */ 0);
+    const result =
+        accessor.getRepeatedMessageIterable(1, TestMessage.instanceCreator);
+
+    expect(Array.from(result)).toEqual([submsg]);
+  });
+
+  it('write submessage changes made via getRepeatedMessagElement', () => {
+    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x05);
+    const expected = createArrayBuffer(0x0A, 0x02, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const submsg = accessor.getRepeatedMessageElement(
+        /* fieldNumber= */ 1, TestMessage.instanceCreator,
+        /* index= */ 0);
+    expect(submsg.getInt32WithDefault(1, 0)).toEqual(5);
+    submsg.setInt32(1, 0);
+
+    expect(accessor.serialize()).toEqual(expected);
+  });
+
+  it('set values', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const subbytes = createArrayBuffer(0x08, 0x01);
+    const submsg = new TestMessage(LazyAccessor.fromArrayBuffer(subbytes));
+
+    accessor.setRepeatedMessageIterable(1, [submsg]);
+    const result =
+        accessor.getRepeatedMessageIterable(1, TestMessage.instanceCreator);
+
+    expect(Array.from(result)).toEqual([submsg]);
+  });
+
+  it('encode for adding single value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const bytes1 = createArrayBuffer(0x08, 0x01);
+    const msg1 = new TestMessage(LazyAccessor.fromArrayBuffer(bytes1));
+    const bytes2 = createArrayBuffer(0x08, 0x00);
+    const msg2 = new TestMessage(LazyAccessor.fromArrayBuffer(bytes2));
+    const expected =
+        createArrayBuffer(0x0A, 0x02, 0x08, 0x01, 0x0A, 0x02, 0x08, 0x00);
+
+    accessor.addRepeatedMessageElement(1, msg1, TestMessage.instanceCreator);
+    accessor.addRepeatedMessageElement(1, msg2, TestMessage.instanceCreator);
+    const result = accessor.serialize();
+
+    expect(result).toEqual(expected);
+  });
+
+  it('encode for adding values', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const bytes1 = createArrayBuffer(0x08, 0x01);
+    const msg1 = new TestMessage(LazyAccessor.fromArrayBuffer(bytes1));
+    const bytes2 = createArrayBuffer(0x08, 0x00);
+    const msg2 = new TestMessage(LazyAccessor.fromArrayBuffer(bytes2));
+    const expected =
+        createArrayBuffer(0x0A, 0x02, 0x08, 0x01, 0x0A, 0x02, 0x08, 0x00);
+
+    accessor.addRepeatedMessageIterable(
+        1, [msg1, msg2], TestMessage.instanceCreator);
+    const result = accessor.serialize();
+
+    expect(result).toEqual(expected);
+  });
+
+  it('encode for setting single value', () => {
+    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const subbytes = createArrayBuffer(0x08, 0x01);
+    const submsg = new TestMessage(LazyAccessor.fromArrayBuffer(subbytes));
+    const expected = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
+
+    accessor.setRepeatedMessageElement(
+        /* fieldNumber= */ 1, submsg, TestMessage.instanceCreator,
+        /* index= */ 0);
+    const result = accessor.serialize();
+
+    expect(result).toEqual(expected);
+  });
+
+  it('encode for setting values', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const subbytes = createArrayBuffer(0x08, 0x01);
+    const submsg = new TestMessage(LazyAccessor.fromArrayBuffer(subbytes));
+    const expected = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
+
+    accessor.setRepeatedMessageIterable(1, [submsg]);
+    const result = accessor.serialize();
+
+    expect(result).toEqual(expected);
+  });
+
+  it('get accessors from set values.', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const bytes1 = createArrayBuffer(0x08, 0x01);
+    const msg1 = new TestMessage(LazyAccessor.fromArrayBuffer(bytes1));
+    const bytes2 = createArrayBuffer(0x08, 0x00);
+    const msg2 = new TestMessage(LazyAccessor.fromArrayBuffer(bytes2));
+
+    accessor.addRepeatedMessageIterable(
+        1, [msg1, msg2], TestMessage.instanceCreator);
+
+    const [accessor1, accessor2] =
+        [...accessor.getRepeatedMessageAccessorIterable(1)];
+    expect(accessor1.getInt32WithDefault(1)).toEqual(1);
+    expect(accessor2.getInt32WithDefault(1)).toEqual(0);
+
+    // Retrieved accessors are the exact same accessors as the added messages.
+    expect(accessor1).toBe(
+        (/** @type {!InternalMessage} */ (msg1)).internalGetKernel());
+    expect(accessor2).toBe(
+        (/** @type {!InternalMessage} */ (msg2)).internalGetKernel());
+  });
+
+  it('fail when getting message value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => {
+        accessor.getRepeatedMessageIterable(1, TestMessage.instanceCreator);
+      }).toThrow();
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      const [msg1] =
+          accessor.getRepeatedMessageIterable(1, TestMessage.instanceCreator);
+      expect(msg1.serialize()).toEqual(createArrayBuffer());
+    }
+  });
+
+  it('fail when adding message values with wrong type value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeValue = /** @type {!TestMessage} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(
+          () => accessor.addRepeatedMessageIterable(
+              1, [fakeValue], TestMessage.instanceCreator))
+          .toThrowError('Given value is not a message instance: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addRepeatedMessageIterable(
+          1, [fakeValue], TestMessage.instanceCreator);
+      const list =
+          accessor.getRepeatedMessageIterable(1, TestMessage.instanceCreator);
+      expect(Array.from(list)).toEqual([null]);
+    }
+  });
+
+  it('fail when adding single message value with wrong type value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeValue = /** @type {!TestMessage} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(
+          () => accessor.addRepeatedMessageElement(
+              1, fakeValue, TestMessage.instanceCreator))
+          .toThrowError('Given value is not a message instance: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.addRepeatedMessageElement(
+          1, fakeValue, TestMessage.instanceCreator);
+      const list =
+          accessor.getRepeatedMessageIterable(1, TestMessage.instanceCreator);
+      expect(Array.from(list)).toEqual([null]);
+    }
+  });
+
+  it('fail when setting message values with wrong type value', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const fakeValue = /** @type {!TestMessage} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => accessor.setRepeatedMessageIterable(1, [fakeValue]))
+          .toThrowError('Given value is not a message instance: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setRepeatedMessageIterable(1, [fakeValue]);
+      const list =
+          accessor.getRepeatedMessageIterable(1, TestMessage.instanceCreator);
+      expect(Array.from(list)).toEqual([null]);
+    }
+  });
+
+  it('fail when setting single value with wrong type value', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x0A, 0x02, 0x08, 0x00));
+    const fakeValue = /** @type {!TestMessage} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_STATE) {
+      expect(
+          () => accessor.setRepeatedMessageElement(
+              /* fieldNumber= */ 1, fakeValue, TestMessage.instanceCreator,
+              /* index= */ 0))
+          .toThrowError('Given value is not a message instance: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setRepeatedMessageElement(
+          /* fieldNumber= */ 1, fakeValue, TestMessage.instanceCreator,
+          /* index= */ 0);
+      const list =
+          accessor.getRepeatedMessageIterable(1, TestMessage.instanceCreator);
+      expect(Array.from(list).length).toEqual(1);
+    }
+  });
+
+  it('fail when setting single value with out-of-bound index', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x0A, 0x02, 0x08, 0x00));
+    const msg1 =
+        accessor.getRepeatedMessageElement(1, TestMessage.instanceCreator, 0);
+    const bytes2 = createArrayBuffer(0x08, 0x01);
+    const msg2 = new TestMessage(LazyAccessor.fromArrayBuffer(bytes2));
+    if (CHECK_CRITICAL_STATE) {
+      expect(
+          () => accessor.setRepeatedMessageElement(
+              /* fieldNumber= */ 1, msg2, TestMessage.instanceCreator,
+              /* index= */ 1))
+          .toThrowError('Index out of bounds: index: 1 size: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setRepeatedMessageElement(
+          /* fieldNumber= */ 1, msg2, TestMessage.instanceCreator,
+          /* index= */ 1);
+      expectEqualToArray(
+          accessor.getRepeatedMessageIterable(1, TestMessage.instanceCreator),
+          [msg1, msg2]);
+    }
+  });
+});

+ 2054 - 0
js/experimental/runtime/kernel/lazy_accessor_test.js

@@ -0,0 +1,2054 @@
+/**
+ * @fileoverview Tests for lazy_accessor.js.
+ */
+goog.module('protobuf.binary.LazyAccessorTest');
+
+goog.setTestOnly();
+
+const ByteString = goog.require('protobuf.ByteString');
+const Int64 = goog.require('protobuf.Int64');
+const InternalMessage = goog.require('protobuf.binary.InternalMessage');
+const LazyAccessor = goog.require('protobuf.binary.LazyAccessor');
+const TestMessage = goog.require('protobuf.testing.binary.TestMessage');
+// Note to the reader:
+// Since the lazy accessor behavior changes with the checking level some of the
+// tests in this file have to know which checking level is enable to make
+// correct assertions.
+const {CHECK_BOUNDS, CHECK_CRITICAL_STATE, CHECK_CRITICAL_TYPE, CHECK_TYPE, MAX_FIELD_NUMBER} = goog.require('protobuf.internal.checks');
+
+/**
+ * @param {...number} bytes
+ * @return {!ArrayBuffer}
+ */
+function createArrayBuffer(...bytes) {
+  return new Uint8Array(bytes).buffer;
+}
+
+describe('LazyAccessor', () => {
+  it('encodes none for the empty input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(new ArrayBuffer(0));
+    expect(accessor.serialize()).toEqual(new ArrayBuffer(0));
+  });
+
+  it('uses the default pivot point', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(new ArrayBuffer(0));
+    expect(accessor.getPivot()).toBe(24);
+  });
+
+  it('makes the pivot point configurable', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(new ArrayBuffer(0), 50);
+    expect(accessor.getPivot()).toBe(50);
+  });
+});
+
+describe('LazyAccessor hasFieldNumber', () => {
+  it('returns false for empty input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(new ArrayBuffer(0));
+    expect(accessor.hasFieldNumber(1)).toBe(false);
+  });
+
+  it('returns true for non-empty input', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.hasFieldNumber(1)).toBe(true);
+  });
+
+  it('returns false for empty array', () => {
+    const accessor = LazyAccessor.createEmpty();
+    accessor.setPackedBoolIterable(1, []);
+    expect(accessor.hasFieldNumber(1)).toBe(false);
+  });
+
+  it('returns true for non-empty array', () => {
+    const accessor = LazyAccessor.createEmpty();
+    accessor.setPackedBoolIterable(1, [true]);
+    expect(accessor.hasFieldNumber(1)).toBe(true);
+  });
+
+  it('updates value after write', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(new ArrayBuffer(0));
+    expect(accessor.hasFieldNumber(1)).toBe(false);
+    accessor.setBool(1, false);
+    expect(accessor.hasFieldNumber(1)).toBe(true);
+  });
+});
+
+describe('LazyAccessor clear field does', () => {
+  it('clear the field set', () => {
+    const accessor = LazyAccessor.createEmpty();
+    accessor.setBool(1, true);
+    accessor.clearField(1);
+
+    expect(accessor.hasFieldNumber(1)).toEqual(false);
+    expect(accessor.serialize()).toEqual(new ArrayBuffer(0));
+    expect(accessor.getBoolWithDefault(1)).toEqual(false);
+  });
+
+  it('clear the field decoded', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    accessor.clearField(1);
+
+    expect(accessor.hasFieldNumber(1)).toEqual(false);
+    expect(accessor.serialize()).toEqual(new ArrayBuffer(0));
+    expect(accessor.getBoolWithDefault(1)).toEqual(false);
+  });
+
+  it('clear the field read', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getBoolWithDefault(1)).toEqual(true);
+    accessor.clearField(1);
+
+    expect(accessor.hasFieldNumber(1)).toEqual(false);
+    expect(accessor.serialize()).toEqual(new ArrayBuffer(0));
+    expect(accessor.getBoolWithDefault(1)).toEqual(false);
+  });
+
+  it('clear set and copied fields without affecting the old', () => {
+    const accessor = LazyAccessor.createEmpty();
+    accessor.setBool(1, true);
+
+    const clonedAccessor = accessor.shallowCopy();
+    clonedAccessor.clearField(1);
+
+    expect(accessor.hasFieldNumber(1)).toEqual(true);
+    expect(accessor.getBoolWithDefault(1)).toEqual(true);
+    expect(clonedAccessor.hasFieldNumber(1)).toEqual(false);
+    expect(clonedAccessor.serialize()).toEqual(new ArrayBuffer(0));
+    expect(clonedAccessor.getBoolWithDefault(1)).toEqual(false);
+  });
+
+  it('clear decoded and copied fields without affecting the old', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+
+    const clonedAccessor = accessor.shallowCopy();
+    clonedAccessor.clearField(1);
+
+    expect(accessor.hasFieldNumber(1)).toEqual(true);
+    expect(accessor.getBoolWithDefault(1)).toEqual(true);
+    expect(clonedAccessor.hasFieldNumber(1)).toEqual(false);
+    expect(clonedAccessor.serialize()).toEqual(new ArrayBuffer(0));
+    expect(clonedAccessor.getBoolWithDefault(1)).toEqual(false);
+  });
+
+  it('clear read and copied fields without affecting the old', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getBoolWithDefault(1)).toEqual(true);
+
+    const clonedAccessor = accessor.shallowCopy();
+    clonedAccessor.clearField(1);
+
+    expect(accessor.hasFieldNumber(1)).toEqual(true);
+    expect(accessor.getBoolWithDefault(1)).toEqual(true);
+    expect(clonedAccessor.hasFieldNumber(1)).toEqual(false);
+    expect(clonedAccessor.serialize()).toEqual(new ArrayBuffer(0));
+    expect(clonedAccessor.getBoolWithDefault(1)).toEqual(false);
+  });
+});
+
+describe('LazyAccessor shallow copy does', () => {
+  it('work for singular fields', () => {
+    const accessor = LazyAccessor.createEmpty();
+    accessor.setBool(1, true);
+    const clonedAccessor = accessor.shallowCopy();
+    expect(clonedAccessor.getBoolWithDefault(1)).toEqual(true);
+
+    accessor.setBool(1, false);
+    expect(clonedAccessor.getBoolWithDefault(1)).toEqual(true);
+  });
+
+  it('work for repeated fields', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedBoolIterable(2, [true, true]);
+
+    const clonedAccessor = accessor.shallowCopy();
+
+    // Modify a repeated field after clone
+    accessor.addUnpackedBoolElement(2, true);
+
+    const array = Array.from(clonedAccessor.getRepeatedBoolIterable(2));
+    expect(array).toEqual([true, true]);
+  });
+
+  it('work for repeated fields', () => {
+    const accessor = LazyAccessor.createEmpty();
+
+    accessor.addUnpackedBoolIterable(2, [true, true]);
+
+    const clonedAccessor = accessor.shallowCopy();
+
+    // Modify a repeated field after clone
+    accessor.addUnpackedBoolElement(2, true);
+
+    const array = Array.from(clonedAccessor.getRepeatedBoolIterable(2));
+    expect(array).toEqual([true, true]);
+  });
+
+  it('return the correct bytes after serialization', () => {
+    const bytes = createArrayBuffer(0x08, 0x01, 0x10, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes, /* pivot= */ 1);
+    const clonedAccessor = accessor.shallowCopy();
+
+    accessor.setBool(1, false);
+
+    expect(clonedAccessor.getBoolWithDefault(1)).toEqual(true);
+    expect(clonedAccessor.serialize()).toEqual(bytes);
+  });
+});
+
+describe('LazyAccessor for singular boolean does', () => {
+  it('return false for the empty input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(new ArrayBuffer(0));
+    expect(accessor.getBoolWithDefault(
+               /* fieldNumber= */ 1))
+        .toBe(false);
+  });
+
+  it('return the value from the input', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getBoolWithDefault(
+               /* fieldNumber= */ 1))
+        .toBe(true);
+  });
+
+  it('encode the value from the input', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.serialize()).toEqual(bytes);
+  });
+
+  it('encode the value from the input after read', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    accessor.getBoolWithDefault(
+        /* fieldNumber= */ 1);
+    expect(accessor.serialize()).toEqual(bytes);
+  });
+
+  it('return the value from multiple inputs', () => {
+    const bytes = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getBoolWithDefault(
+               /* fieldNumber= */ 1))
+        .toBe(false);
+  });
+
+  it('encode the value from multiple inputs', () => {
+    const bytes = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.serialize()).toEqual(bytes);
+  });
+
+  it('encode the value from multiple inputs after read', () => {
+    const bytes = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    accessor.getBoolWithDefault(/* fieldNumber= */ 1);
+    expect(accessor.serialize()).toEqual(bytes);
+  });
+
+  it('return the value from setter', () => {
+    const bytes = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    accessor.setBool(1, true);
+    expect(accessor.getBoolWithDefault(
+               /* fieldNumber= */ 1))
+        .toBe(true);
+  });
+
+  it('encode the value from setter', () => {
+    const bytes = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const newBytes = createArrayBuffer(0x08, 0x01);
+    accessor.setBool(1, true);
+    expect(accessor.serialize()).toEqual(newBytes);
+  });
+
+  it('return the bool value from cache', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getBoolWithDefault(
+               /* fieldNumber= */ 1))
+        .toBe(true);
+    // Make sure the value is cached.
+    bytes[1] = 0x00;
+    expect(accessor.getBoolWithDefault(
+               /* fieldNumber= */ 1))
+        .toBe(true);
+  });
+
+  it('fail when getting bool value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_TYPE) {
+      expect(() => {
+        accessor.getBoolWithDefault(/* fieldNumber= */ 1);
+      }).toThrowError('Expected wire type: 0 but found: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getBoolWithDefault(
+                 /* fieldNumber= */ 1))
+          .toBe(true);
+    }
+  });
+
+  it('fail when setting bool value with out-of-range field number', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(new ArrayBuffer(0));
+    if (CHECK_TYPE) {
+      expect(() => accessor.setBool(MAX_FIELD_NUMBER + 1, false))
+          .toThrowError('Field number is out of range: 536870912');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setBool(MAX_FIELD_NUMBER + 1, false);
+      expect(accessor.getBoolWithDefault(MAX_FIELD_NUMBER + 1)).toBe(false);
+    }
+  });
+
+  it('fail when setting bool value with number value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(new ArrayBuffer(0));
+    const fakeBoolean = /** @type {boolean} */ (/** @type {*} */ (2));
+    if (CHECK_CRITICAL_TYPE) {
+      expect(() => accessor.setBool(1, fakeBoolean))
+          .toThrowError('Must be a boolean, but got: 2');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setBool(1, fakeBoolean);
+      expect(accessor.getBoolWithDefault(
+                 /* fieldNumber= */ 1))
+          .toBe(2);
+    }
+  });
+});
+
+describe('LazyAccessor for singular message does', () => {
+  it('return message from the input', () => {
+    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const msg = accessor.getMessageOrNull(1, TestMessage.instanceCreator);
+    expect(msg.getBoolWithDefault(1, false)).toBe(true);
+  });
+
+  it('return message from the input when pivot is set', () => {
+    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes, /* pivot= */ 0);
+    const msg = accessor.getMessageOrNull(1, TestMessage.instanceCreator);
+    expect(msg.getBoolWithDefault(1, false)).toBe(true);
+  });
+
+  it('encode message from the input', () => {
+    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.serialize()).toEqual(bytes);
+  });
+
+  it('encode message from the input after read', () => {
+    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    accessor.getMessageOrNull(1, TestMessage.instanceCreator);
+    expect(accessor.serialize()).toEqual(bytes);
+  });
+
+  it('return message from multiple inputs', () => {
+    const bytes =
+        createArrayBuffer(0x0A, 0x02, 0x08, 0x01, 0x0A, 0x02, 0x10, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const msg = accessor.getMessageOrNull(1, TestMessage.instanceCreator);
+    expect(msg.getBoolWithDefault(1, false)).toBe(true);
+    expect(msg.getBoolWithDefault(2, false)).toBe(true);
+  });
+
+  it('encode message from multiple inputs', () => {
+    const bytes =
+        createArrayBuffer(0x0A, 0x02, 0x08, 0x01, 0x0A, 0x02, 0x10, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.serialize()).toEqual(bytes);
+  });
+
+  it('encode message merged from multiple inputs after read', () => {
+    const bytes =
+        createArrayBuffer(0x0A, 0x02, 0x08, 0x01, 0x0A, 0x02, 0x10, 0x01);
+    const expected = createArrayBuffer(0x0A, 0x04, 0x08, 0x01, 0x10, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    accessor.getMessageOrNull(1, TestMessage.instanceCreator);
+    expect(accessor.serialize()).toEqual(expected);
+  });
+
+  it('return null for generic accessor', () => {
+    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const accessor1 = accessor.getMessageAccessorOrNull(7);
+    expect(accessor1).toBe(null);
+  });
+
+  it('return null for generic accessor when pivot is set', () => {
+    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const accessor1 = accessor.getMessageAccessorOrNull(7, /* pivot= */ 0);
+    expect(accessor1).toBe(null);
+  });
+
+  it('return generic accessor from the input', () => {
+    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const accessor1 = accessor.getMessageAccessorOrNull(1);
+    expect(accessor1.getBoolWithDefault(1, false)).toBe(true);
+    // Second call returns a new instance, isn't cached.
+    const accessor2 = accessor.getMessageAccessorOrNull(1);
+    expect(accessor2.getBoolWithDefault(1, false)).toBe(true);
+    expect(accessor2).not.toBe(accessor1);
+  });
+
+  it('return generic accessor from the cached input', () => {
+    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const wrappedMessage =
+        accessor.getMessageOrNull(1, TestMessage.instanceCreator);
+
+    // Returns accessor from the cached wrapper instance.
+    const accessor1 = accessor.getMessageAccessorOrNull(1);
+    expect(accessor1.getBoolWithDefault(1, false)).toBe(true);
+    expect(accessor1).toBe(
+        (/** @type {!InternalMessage} */ (wrappedMessage)).internalGetKernel());
+
+    // Second call returns exact same instance.
+    const accessor2 = accessor.getMessageAccessorOrNull(1);
+    expect(accessor2.getBoolWithDefault(1, false)).toBe(true);
+    expect(accessor2).toBe(
+        (/** @type {!InternalMessage} */ (wrappedMessage)).internalGetKernel());
+    expect(accessor2).toBe(accessor1);
+  });
+
+  it('return message from setter', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(new ArrayBuffer(0));
+    const subaccessor = LazyAccessor.fromArrayBuffer(bytes);
+    const submsg1 = new TestMessage(subaccessor);
+    accessor.setMessage(1, submsg1);
+    const submsg2 = accessor.getMessage(1, TestMessage.instanceCreator);
+    expect(submsg1).toBe(submsg2);
+  });
+
+  it('encode message from setter', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(new ArrayBuffer(0));
+    const subaccessor = LazyAccessor.fromArrayBuffer(new ArrayBuffer(0));
+    const subsubaccessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x01));
+    const subsubmsg = new TestMessage(subsubaccessor);
+    subaccessor.setMessage(1, subsubmsg);
+    const submsg = new TestMessage(subaccessor);
+    accessor.setMessage(1, submsg);
+    const expected = createArrayBuffer(0x0A, 0x04, 0x0A, 0x02, 0x08, 0x01);
+    expect(accessor.serialize()).toEqual(expected);
+  });
+
+  it('encode message with multiple submessage from setter', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(new ArrayBuffer(0));
+    const subaccessor = LazyAccessor.fromArrayBuffer(new ArrayBuffer(0));
+    const subsubaccessor1 =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x01));
+    const subsubaccessor2 =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x02));
+
+    const subsubmsg1 = new TestMessage(subsubaccessor1);
+    const subsubmsg2 = new TestMessage(subsubaccessor2);
+
+    subaccessor.setMessage(1, subsubmsg1);
+    subaccessor.setMessage(2, subsubmsg2);
+
+    const submsg = new TestMessage(subaccessor);
+    accessor.setMessage(1, submsg);
+
+    const expected = createArrayBuffer(
+        0x0A, 0x08, 0x0A, 0x02, 0x08, 0x01, 0x12, 0x02, 0x08, 0x02);
+    expect(accessor.serialize()).toEqual(expected);
+  });
+
+  it('leave hasFieldNumber unchanged after getMessageOrNull', () => {
+    const accessor = LazyAccessor.createEmpty();
+    expect(accessor.hasFieldNumber(1)).toBe(false);
+    expect(accessor.getMessageOrNull(1, TestMessage.instanceCreator))
+        .toBe(null);
+    expect(accessor.hasFieldNumber(1)).toBe(false);
+  });
+
+  it('serialize changes to submessages made with getMessageOrNull', () => {
+    const intTwoBytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x02);
+    const accessor = LazyAccessor.fromArrayBuffer(intTwoBytes);
+    const mutableSubMessage =
+        accessor.getMessageOrNull(1, TestMessage.instanceCreator);
+    mutableSubMessage.setInt32(1, 10);
+    const intTenBytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x0A);
+    expect(accessor.serialize()).toEqual(intTenBytes);
+  });
+
+  it('serialize additions to submessages made with getMessageOrNull', () => {
+    const intTwoBytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x02);
+    const accessor = LazyAccessor.fromArrayBuffer(intTwoBytes);
+    const mutableSubMessage =
+        accessor.getMessageOrNull(1, TestMessage.instanceCreator);
+    mutableSubMessage.setInt32(2, 3);
+    // Sub message contains the original field, plus the new one.
+    expect(accessor.serialize())
+        .toEqual(createArrayBuffer(0x0A, 0x04, 0x08, 0x02, 0x10, 0x03));
+  });
+
+  it('fail with getMessageOrNull if immutable message exist in cache', () => {
+    const intTwoBytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x02);
+    const accessor = LazyAccessor.fromArrayBuffer(intTwoBytes);
+
+    const readOnly = accessor.getMessage(1, TestMessage.instanceCreator);
+    if (CHECK_TYPE) {
+      expect(() => accessor.getMessageOrNull(1, TestMessage.instanceCreator))
+          .toThrow();
+    } else {
+      const mutableSubMessage =
+          accessor.getMessageOrNull(1, TestMessage.instanceCreator);
+      // The instance returned by getMessageOrNull is the exact same instance.
+      expect(mutableSubMessage).toBe(readOnly);
+
+      // Serializing the submessage does not write the changes
+      mutableSubMessage.setInt32(1, 0);
+      expect(accessor.serialize()).toEqual(intTwoBytes);
+    }
+  });
+
+  it('change hasFieldNumber after getMessageAttach', () => {
+    const accessor = LazyAccessor.createEmpty();
+    expect(accessor.hasFieldNumber(1)).toBe(false);
+    expect(accessor.getMessageAttach(1, TestMessage.instanceCreator))
+        .not.toBe(null);
+    expect(accessor.hasFieldNumber(1)).toBe(true);
+  });
+
+  it('change hasFieldNumber after getMessageAttach when pivot is set', () => {
+    const accessor = LazyAccessor.createEmpty();
+    expect(accessor.hasFieldNumber(1)).toBe(false);
+    expect(accessor.getMessageAttach(
+               1, TestMessage.instanceCreator, /* pivot= */ 1))
+        .not.toBe(null);
+    expect(accessor.hasFieldNumber(1)).toBe(true);
+  });
+
+  it('serialize submessages made with getMessageAttach', () => {
+    const accessor = LazyAccessor.createEmpty();
+    const mutableSubMessage =
+        accessor.getMessageAttach(1, TestMessage.instanceCreator);
+    mutableSubMessage.setInt32(1, 10);
+    const intTenBytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x0A);
+    expect(accessor.serialize()).toEqual(intTenBytes);
+  });
+
+  it('serialize additions to submessages using getMessageAttach', () => {
+    const intTwoBytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x02);
+    const accessor = LazyAccessor.fromArrayBuffer(intTwoBytes);
+    const mutableSubMessage =
+        accessor.getMessageAttach(1, TestMessage.instanceCreator);
+    mutableSubMessage.setInt32(2, 3);
+    // Sub message contains the original field, plus the new one.
+    expect(accessor.serialize())
+        .toEqual(createArrayBuffer(0x0A, 0x04, 0x08, 0x02, 0x10, 0x03));
+  });
+
+  it('fail with getMessageAttach if immutable message exist in cache', () => {
+    const intTwoBytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x02);
+    const accessor = LazyAccessor.fromArrayBuffer(intTwoBytes);
+
+    const readOnly = accessor.getMessage(1, TestMessage.instanceCreator);
+    if (CHECK_TYPE) {
+      expect(() => accessor.getMessageAttach(1, TestMessage.instanceCreator))
+          .toThrow();
+    } else {
+      const mutableSubMessage =
+          accessor.getMessageAttach(1, TestMessage.instanceCreator);
+      // The instance returned by getMessageOrNull is the exact same instance.
+      expect(mutableSubMessage).toBe(readOnly);
+
+      // Serializing the submessage does not write the changes
+      mutableSubMessage.setInt32(1, 0);
+      expect(accessor.serialize()).toEqual(intTwoBytes);
+    }
+  });
+
+  it('read default message return empty message with getMessage', () => {
+    const bytes = new ArrayBuffer(0);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getMessage(1, TestMessage.instanceCreator)).toBeTruthy();
+    expect(accessor.getMessage(1, TestMessage.instanceCreator).serialize())
+        .toEqual(bytes);
+  });
+
+  it('read default message return null with getMessageOrNull', () => {
+    const bytes = new ArrayBuffer(0);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getMessageOrNull(1, TestMessage.instanceCreator))
+        .toBe(null);
+  });
+
+  it('read message preserve reference equality', () => {
+    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const msg1 = accessor.getMessageOrNull(1, TestMessage.instanceCreator);
+    const msg2 = accessor.getMessageOrNull(1, TestMessage.instanceCreator);
+    const msg3 = accessor.getMessageAttach(1, TestMessage.instanceCreator);
+    expect(msg1).toBe(msg2);
+    expect(msg1).toBe(msg3);
+  });
+
+  it('fail when getting message with other wire types', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x01));
+    expect(() => accessor.getMessageOrNull(1, TestMessage.instanceCreator))
+        .toThrow();
+  });
+
+  it('fail when submessage has incomplete data', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x0A, 0x01, 0x08));
+    expect(() => accessor.getMessageOrNull(1, TestMessage.instanceCreator))
+        .toThrow();
+  });
+
+  it('fail when mutable submessage has incomplete data', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x0A, 0x01, 0x08));
+    expect(() => accessor.getMessageAttach(1, TestMessage.instanceCreator))
+        .toThrow();
+  });
+
+  it('fail when getting message with null instance constructor', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x0A, 0x02, 0x08, 0x01));
+    const nullMessage = /** @type {function(!LazyAccessor):!TestMessage} */
+        (/** @type {*} */ (null));
+    expect(() => accessor.getMessageOrNull(1, nullMessage)).toThrow();
+  });
+
+  it('fail when setting message value with null value', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(new ArrayBuffer(0));
+    const fakeMessage = /** @type {!TestMessage} */ (/** @type {*} */ (null));
+    if (CHECK_CRITICAL_TYPE) {
+      expect(() => accessor.setMessage(1, fakeMessage))
+          .toThrowError('Given value is not a message instance: null');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      accessor.setMessage(1, fakeMessage);
+      expect(accessor.getMessageOrNull(
+                 /* fieldNumber= */ 1, TestMessage.instanceCreator))
+          .toBeNull();
+    }
+  });
+});
+
+describe('Bytes access', () => {
+  const simpleByteString = ByteString.fromArrayBuffer(createArrayBuffer(1));
+
+  it('returns default value for empty input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getBytesWithDefault(1)).toEqual(ByteString.EMPTY);
+  });
+
+  it('returns the default from parameter', () => {
+    const defaultByteString = ByteString.fromArrayBuffer(createArrayBuffer(1));
+    const returnValue = ByteString.fromArrayBuffer(createArrayBuffer(1));
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getBytesWithDefault(1, defaultByteString))
+        .toEqual(returnValue);
+  });
+
+  it('decodes value from wire', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x0A, 0x01, 0x01));
+    expect(accessor.getBytesWithDefault(1)).toEqual(simpleByteString);
+  });
+
+  it('decodes value from wire with multple values being present', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0A, 0x01, 0x00, 0x0A, 0x01, 0x01));
+    expect(accessor.getBytesWithDefault(1)).toEqual(simpleByteString);
+  });
+
+  it('fails when getting value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01));
+    if (CHECK_CRITICAL_TYPE) {
+      expect(() => {
+        accessor.getBytesWithDefault(1);
+      }).toThrowError('Expected wire type: 2 but found: 1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      const arrayBuffer = createArrayBuffer(1);
+      expect(accessor.getBytesWithDefault(1))
+          .toEqual(ByteString.fromArrayBuffer(arrayBuffer));
+    }
+  });
+
+  it('throws in getter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(
+          () => LazyAccessor.createEmpty().getBytesWithDefault(
+              -1, simpleByteString))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      expect(
+          LazyAccessor.createEmpty().getBytesWithDefault(-1, simpleByteString))
+          .toEqual(simpleByteString);
+    }
+  });
+
+  it('returns the value from setter', () => {
+    const bytes = createArrayBuffer(0x0A, 0x01, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    accessor.setBytes(1, simpleByteString);
+    expect(accessor.getBytesWithDefault(1)).toEqual(simpleByteString);
+  });
+
+  it('encode the value from setter', () => {
+    const bytes = createArrayBuffer(0x0A, 0x01, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const newBytes = createArrayBuffer(0x0A, 0x01, 0x01);
+    accessor.setBytes(1, simpleByteString);
+    expect(accessor.serialize()).toEqual(newBytes);
+  });
+
+  it('returns value from cache', () => {
+    const bytes = createArrayBuffer(0x0A, 0x01, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getBytesWithDefault(1)).toEqual(simpleByteString);
+    // Make sure the value is cached.
+    bytes[2] = 0x00;
+    expect(accessor.getBytesWithDefault(1)).toEqual(simpleByteString);
+  });
+
+  it('throws in setter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().setBytes(-1, simpleByteString))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setBytes(-1, simpleByteString);
+      expect(accessor.getBytesWithDefault(-1)).toEqual(simpleByteString);
+    }
+  });
+
+  it('throws in setter for invalid value', () => {
+    if (CHECK_CRITICAL_TYPE) {
+      expect(
+          () => LazyAccessor.createEmpty().setBytes(
+              1, /** @type {!ByteString} */ (/** @type {*} */ (null))))
+          .toThrow();
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setBytes(
+          1, /** @type {!ByteString} */ (/** @type {*} */ (null)));
+      expect(accessor.getBytesWithDefault(1)).toEqual(null);
+    }
+  });
+});
+
+describe('Fixed32 access', () => {
+  it('returns default value for empty input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getFixed32WithDefault(1)).toEqual(0);
+  });
+
+  it('returns the default from parameter', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getFixed32WithDefault(1, 2)).toEqual(2);
+  });
+
+  it('decodes value from wire', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x01, 0x00, 0x00, 0x00));
+    expect(accessor.getFixed32WithDefault(1)).toEqual(1);
+  });
+
+  it('decodes value from wire with multple values being present', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x0D, 0x01, 0x00, 0x80, 0x00, 0x0D, 0x02, 0x00, 0x00, 0x00));
+    expect(accessor.getFixed32WithDefault(1)).toEqual(2);
+  });
+
+  it('fails when getting value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x08, 0x80, 0x80, 0x80, 0x00));
+    if (CHECK_CRITICAL_TYPE) {
+      expect(() => {
+        accessor.getFixed32WithDefault(1);
+      }).toThrowError('Expected wire type: 5 but found: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getFixed32WithDefault(1)).toEqual(8421504);
+    }
+  });
+
+  it('throws in getter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().getFixed32WithDefault(-1, 1))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      expect(LazyAccessor.createEmpty().getFixed32WithDefault(-1, 1))
+          .toEqual(1);
+    }
+  });
+
+  it('returns the value from setter', () => {
+    const bytes = createArrayBuffer(0x0D, 0x01, 0x00, 0x00, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    accessor.setFixed32(1, 2);
+    expect(accessor.getFixed32WithDefault(1)).toEqual(2);
+  });
+
+  it('encode the value from setter', () => {
+    const bytes = createArrayBuffer(0x0D, 0x01, 0x00, 0x00, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const newBytes = createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00);
+    accessor.setFixed32(1, 0);
+    expect(accessor.serialize()).toEqual(newBytes);
+  });
+
+  it('returns value from cache', () => {
+    const bytes = createArrayBuffer(0x0D, 0x01, 0x00, 0x00, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getFixed32WithDefault(1)).toBe(1);
+    // Make sure the value is cached.
+    bytes[2] = 0x00;
+    expect(accessor.getFixed32WithDefault(1)).toBe(1);
+  });
+
+  it('throws in setter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().setFixed32(-1, 1))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setFixed32(-1, 1);
+      expect(accessor.getFixed32WithDefault(-1)).toEqual(1);
+    }
+  });
+
+  it('throws in setter for invalid value', () => {
+    if (CHECK_CRITICAL_TYPE) {
+      expect(
+          () => LazyAccessor.createEmpty().setFixed32(
+              1, /** @type {number} */ (/** @type {*} */ (null))))
+          .toThrow();
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setFixed32(1, /** @type {number} */ (/** @type {*} */ (null)));
+      expect(accessor.getFixed32WithDefault(1)).toEqual(null);
+    }
+  });
+});
+
+describe('Fixed64 access', () => {
+  it('returns default value for empty input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getFixed64WithDefault(1)).toEqual(Int64.fromInt(0));
+  });
+
+  it('returns the default from parameter', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getFixed64WithDefault(1, Int64.fromInt(2)))
+        .toEqual(Int64.fromInt(2));
+  });
+
+  it('decodes value from wire', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+    expect(accessor.getFixed64WithDefault(1)).toEqual(Int64.fromInt(1));
+  });
+
+  it('decodes value from wire with multple values being present', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x02, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+    expect(accessor.getFixed64WithDefault(1)).toEqual(Int64.fromInt(2));
+  });
+
+  if (CHECK_CRITICAL_STATE) {
+    it('fails when getting value with other wire types', () => {
+      const accessor = LazyAccessor.fromArrayBuffer(
+          createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
+      expect(() => {
+        accessor.getFixed64WithDefault(1);
+      }).toThrow();
+    });
+  }
+
+  it('throws in getter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(
+          () => LazyAccessor.createEmpty().getFixed64WithDefault(
+              -1, Int64.fromInt(1)))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      expect(LazyAccessor.createEmpty().getFixed64WithDefault(
+                 -1, Int64.fromInt(1)))
+          .toEqual(Int64.fromInt(1));
+    }
+  });
+
+  it('returns the value from setter', () => {
+    const bytes =
+        createArrayBuffer(0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    accessor.setFixed64(1, Int64.fromInt(2));
+    expect(accessor.getFixed64WithDefault(1)).toEqual(Int64.fromInt(2));
+  });
+
+  it('encode the value from setter', () => {
+    const bytes =
+        createArrayBuffer(0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const newBytes =
+        createArrayBuffer(0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+    accessor.setFixed64(1, Int64.fromInt(0));
+    expect(accessor.serialize()).toEqual(newBytes);
+  });
+
+  it('returns value from cache', () => {
+    const bytes =
+        createArrayBuffer(0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getFixed64WithDefault(1)).toEqual(Int64.fromInt(1));
+    // Make sure the value is cached.
+    bytes[2] = 0x00;
+    expect(accessor.getFixed64WithDefault(1)).toEqual(Int64.fromInt(1));
+  });
+
+  it('throws in setter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().setFixed64(-1, Int64.fromInt(1)))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setFixed64(-1, Int64.fromInt(1));
+      expect(accessor.getFixed64WithDefault(-1)).toEqual(Int64.fromInt(1));
+    }
+  });
+
+  it('throws in setter for invalid value', () => {
+    if (CHECK_CRITICAL_TYPE) {
+      expect(
+          () => LazyAccessor.createEmpty().setSfixed64(
+              1, /** @type {!Int64} */ (/** @type {*} */ (null))))
+          .toThrow();
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setFixed64(1, /** @type {!Int64} */ (/** @type {*} */ (null)));
+      expect(accessor.getFixed64WithDefault(1)).toEqual(null);
+    }
+  });
+});
+
+describe('Float access', () => {
+  it('returns default value for empty input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getFloatWithDefault(1)).toEqual(0);
+  });
+
+  it('returns the default from parameter', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getFloatWithDefault(1, 2)).toEqual(2);
+  });
+
+  it('decodes value from wire', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x00, 0x00, 0x80, 0x3F));
+    expect(accessor.getFloatWithDefault(1)).toEqual(1);
+  });
+
+  it('decodes value from wire with multple values being present', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x0D, 0x00, 0x00, 0x80, 0x3F, 0x0D, 0x00, 0x00, 0x80, 0xBF));
+    expect(accessor.getFloatWithDefault(1)).toEqual(-1);
+  });
+
+  if (CHECK_CRITICAL_STATE) {
+    it('fails when getting float value with other wire types', () => {
+      const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+          0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F));
+      expect(() => {
+        accessor.getFloatWithDefault(1);
+      }).toThrow();
+    });
+  }
+
+
+  it('throws in getter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().getFloatWithDefault(-1, 1))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      expect(LazyAccessor.createEmpty().getFloatWithDefault(-1, 1)).toEqual(1);
+    }
+  });
+
+  it('returns the value from setter', () => {
+    const bytes = createArrayBuffer(0x0D, 0x00, 0x00, 0x80, 0x3F);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    accessor.setFloat(1, 1.6);
+    expect(accessor.getFloatWithDefault(1)).toEqual(Math.fround(1.6));
+  });
+
+  it('encode the value from setter', () => {
+    const bytes = createArrayBuffer(0x0D, 0x00, 0x00, 0x80, 0x3F);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const newBytes = createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00);
+    accessor.setFloat(1, 0);
+    expect(accessor.serialize()).toEqual(newBytes);
+  });
+
+  it('returns float value from cache', () => {
+    const bytes = createArrayBuffer(0x0D, 0x00, 0x00, 0x80, 0x3F);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getFloatWithDefault(1)).toBe(1);
+    // Make sure the value is cached.
+    bytes[2] = 0x00;
+    expect(accessor.getFloatWithDefault(1)).toBe(1);
+  });
+
+  it('throws in setter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().setFloat(-1, 1))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setFloat(-1, 1);
+      expect(accessor.getFloatWithDefault(-1)).toEqual(1);
+    }
+  });
+
+  it('throws in setter for invalid value', () => {
+    if (CHECK_CRITICAL_TYPE) {
+      expect(
+          () => LazyAccessor.createEmpty().setFloat(
+              1, /** @type {number} */ (/** @type {*} */ (null))))
+          .toThrow();
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setFloat(1, /** @type {number} */ (/** @type {*} */ (null)));
+      expect(accessor.getFloatWithDefault(1)).toEqual(0);
+    }
+  });
+
+  it('throws in setter for value outside of float32 precision', () => {
+    if (CHECK_CRITICAL_TYPE) {
+      expect(() => LazyAccessor.createEmpty().setFloat(1, Number.MAX_VALUE))
+          .toThrow();
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setFloat(1, Number.MAX_VALUE);
+      expect(accessor.getFloatWithDefault(1)).toEqual(Infinity);
+    }
+  });
+});
+
+describe('Int32 access', () => {
+  it('returns default value for empty input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getInt32WithDefault(1)).toEqual(0);
+  });
+
+  it('returns the default from parameter', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getInt32WithDefault(1, 2)).toEqual(2);
+  });
+
+  it('decodes value from wire', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x01));
+    expect(accessor.getInt32WithDefault(1)).toEqual(1);
+  });
+
+  it('decodes value from wire with multple values being present', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x01, 0x08, 0x02));
+    expect(accessor.getInt32WithDefault(1)).toEqual(2);
+  });
+
+  it('fails when getting value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_TYPE) {
+      expect(() => {
+        accessor.getInt32WithDefault(1);
+      }).toThrowError('Expected wire type: 0 but found: 5');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getInt32WithDefault(1)).toEqual(0);
+    }
+  });
+
+  it('throws in getter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().getInt32WithDefault(-1, 1))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      expect(LazyAccessor.createEmpty().getInt32WithDefault(-1, 1)).toEqual(1);
+    }
+  });
+
+  it('returns the value from setter', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    accessor.setInt32(1, 2);
+    expect(accessor.getInt32WithDefault(1)).toEqual(2);
+  });
+
+  it('encode the value from setter', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const newBytes = createArrayBuffer(0x08, 0x00);
+    accessor.setInt32(1, 0);
+    expect(accessor.serialize()).toEqual(newBytes);
+  });
+
+  it('returns value from cache', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getInt32WithDefault(1)).toBe(1);
+    // Make sure the value is cached.
+    bytes[2] = 0x00;
+    expect(accessor.getInt32WithDefault(1)).toBe(1);
+  });
+
+  it('throws in setter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().setInt32(-1, 1))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setInt32(-1, 1);
+      expect(accessor.getInt32WithDefault(-1)).toEqual(1);
+    }
+  });
+
+  it('throws in setter for invalid value', () => {
+    if (CHECK_CRITICAL_TYPE) {
+      expect(
+          () => LazyAccessor.createEmpty().setInt32(
+              1, /** @type {number} */ (/** @type {*} */ (null))))
+          .toThrow();
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setInt32(1, /** @type {number} */ (/** @type {*} */ (null)));
+      expect(accessor.getInt32WithDefault(1)).toEqual(null);
+    }
+  });
+});
+
+describe('Int64 access', () => {
+  it('returns default value for empty input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getInt64WithDefault(1)).toEqual(Int64.fromInt(0));
+  });
+
+  it('returns the default from parameter', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getInt64WithDefault(1, Int64.fromInt(2)))
+        .toEqual(Int64.fromInt(2));
+  });
+
+  it('decodes value from wire', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x01));
+    expect(accessor.getInt64WithDefault(1)).toEqual(Int64.fromInt(1));
+  });
+
+  it('decodes value from wire with multple values being present', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x01, 0x08, 0x02));
+    expect(accessor.getInt64WithDefault(1)).toEqual(Int64.fromInt(2));
+  });
+
+  it('fails when getting value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_TYPE) {
+      expect(() => {
+        accessor.getInt64WithDefault(1);
+      }).toThrowError('Expected wire type: 0 but found: 5');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getInt64WithDefault(1)).toEqual(Int64.fromInt(0));
+    }
+  });
+
+  it('throws in getter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(
+          () => LazyAccessor.createEmpty().getInt64WithDefault(
+              -1, Int64.fromInt(1)))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      expect(
+          LazyAccessor.createEmpty().getInt64WithDefault(-1, Int64.fromInt(1)))
+          .toEqual(Int64.fromInt(1));
+    }
+  });
+
+  it('returns the value from setter', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    accessor.setInt64(1, Int64.fromInt(2));
+    expect(accessor.getInt64WithDefault(1)).toEqual(Int64.fromInt(2));
+  });
+
+  it('encode the value from setter', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const newBytes = createArrayBuffer(0x08, 0x00);
+    accessor.setInt64(1, Int64.fromInt(0));
+    expect(accessor.serialize()).toEqual(newBytes);
+  });
+
+  it('returns value from cache', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getInt64WithDefault(1)).toEqual(Int64.fromInt(1));
+    // Make sure the value is cached.
+    bytes[2] = 0x00;
+    expect(accessor.getInt64WithDefault(1)).toEqual(Int64.fromInt(1));
+  });
+
+  it('throws in setter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().setInt64(-1, Int64.fromInt(1)))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setInt64(-1, Int64.fromInt(1));
+      expect(accessor.getInt64WithDefault(-1)).toEqual(Int64.fromInt(1));
+    }
+  });
+
+  it('throws in setter for invalid value', () => {
+    if (CHECK_CRITICAL_TYPE) {
+      expect(
+          () => LazyAccessor.createEmpty().setInt64(
+              1, /** @type {!Int64} */ (/** @type {*} */ (null))))
+          .toThrow();
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setInt64(1, /** @type {!Int64} */ (/** @type {*} */ (null)));
+      expect(accessor.getInt64WithDefault(1)).toEqual(null);
+    }
+  });
+});
+
+describe('Sfixed32 access', () => {
+  it('returns default value for empty input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getSfixed32WithDefault(1)).toEqual(0);
+  });
+
+  it('returns the default from parameter', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getSfixed32WithDefault(1, 2)).toEqual(2);
+  });
+
+  it('decodes value from wire', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x01, 0x00, 0x00, 0x00));
+    expect(accessor.getSfixed32WithDefault(1)).toEqual(1);
+  });
+
+  it('decodes value from wire with multple values being present', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x0D, 0x01, 0x00, 0x80, 0x00, 0x0D, 0x02, 0x00, 0x00, 0x00));
+    expect(accessor.getSfixed32WithDefault(1)).toEqual(2);
+  });
+
+  it('fails when getting value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x08, 0x80, 0x80, 0x80, 0x00));
+    if (CHECK_CRITICAL_TYPE) {
+      expect(() => {
+        accessor.getSfixed32WithDefault(1);
+      }).toThrowError('Expected wire type: 5 but found: 0');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getSfixed32WithDefault(1)).toEqual(8421504);
+    }
+  });
+
+  it('throws in getter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().getSfixed32WithDefault(-1, 1))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      expect(LazyAccessor.createEmpty().getSfixed32WithDefault(-1, 1))
+          .toEqual(1);
+    }
+  });
+
+  it('returns the value from setter', () => {
+    const bytes = createArrayBuffer(0x0D, 0x01, 0x00, 0x00, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    accessor.setSfixed32(1, 2);
+    expect(accessor.getSfixed32WithDefault(1)).toEqual(2);
+  });
+
+  it('encode the value from setter', () => {
+    const bytes = createArrayBuffer(0x0D, 0x01, 0x00, 0x00, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const newBytes = createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00);
+    accessor.setSfixed32(1, 0);
+    expect(accessor.serialize()).toEqual(newBytes);
+  });
+
+  it('returns value from cache', () => {
+    const bytes = createArrayBuffer(0x0D, 0x01, 0x00, 0x00, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getSfixed32WithDefault(1)).toBe(1);
+    // Make sure the value is cached.
+    bytes[2] = 0x00;
+    expect(accessor.getSfixed32WithDefault(1)).toBe(1);
+  });
+
+  it('throws in setter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().setSfixed32(-1, 1))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setSfixed32(-1, 1);
+      expect(accessor.getSfixed32WithDefault(-1)).toEqual(1);
+    }
+  });
+
+  it('throws in setter for invalid value', () => {
+    if (CHECK_CRITICAL_TYPE) {
+      expect(
+          () => LazyAccessor.createEmpty().setSfixed32(
+              1, /** @type {number} */ (/** @type {*} */ (null))))
+          .toThrow();
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setSfixed32(1, /** @type {number} */ (/** @type {*} */ (null)));
+      expect(accessor.getSfixed32WithDefault(1)).toEqual(null);
+    }
+  });
+});
+
+describe('Sfixed64 access', () => {
+  it('returns default value for empty input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getSfixed64WithDefault(1)).toEqual(Int64.fromInt(0));
+  });
+
+  it('returns the default from parameter', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getSfixed64WithDefault(1, Int64.fromInt(2)))
+        .toEqual(Int64.fromInt(2));
+  });
+
+  it('decodes value from wire', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+    expect(accessor.getSfixed64WithDefault(1)).toEqual(Int64.fromInt(1));
+  });
+
+  it('decodes value from wire with multple values being present', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x02, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+    expect(accessor.getSfixed64WithDefault(1)).toEqual(Int64.fromInt(2));
+  });
+
+  if (CHECK_CRITICAL_STATE) {
+    it('fails when getting value with other wire types', () => {
+      const accessor = LazyAccessor.fromArrayBuffer(
+          createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
+      expect(() => {
+        accessor.getSfixed64WithDefault(1);
+      }).toThrow();
+    });
+  }
+
+  it('throws in getter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(
+          () => LazyAccessor.createEmpty().getSfixed64WithDefault(
+              -1, Int64.fromInt(1)))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      expect(LazyAccessor.createEmpty().getSfixed64WithDefault(
+                 -1, Int64.fromInt(1)))
+          .toEqual(Int64.fromInt(1));
+    }
+  });
+
+  it('returns the value from setter', () => {
+    const bytes =
+        createArrayBuffer(0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    accessor.setSfixed64(1, Int64.fromInt(2));
+    expect(accessor.getSfixed64WithDefault(1)).toEqual(Int64.fromInt(2));
+  });
+
+  it('encode the value from setter', () => {
+    const bytes =
+        createArrayBuffer(0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const newBytes =
+        createArrayBuffer(0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+    accessor.setSfixed64(1, Int64.fromInt(0));
+    expect(accessor.serialize()).toEqual(newBytes);
+  });
+
+  it('returns value from cache', () => {
+    const bytes =
+        createArrayBuffer(0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getSfixed64WithDefault(1)).toEqual(Int64.fromInt(1));
+    // Make sure the value is cached.
+    bytes[2] = 0x00;
+    expect(accessor.getSfixed64WithDefault(1)).toEqual(Int64.fromInt(1));
+  });
+
+  it('throws in setter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().setSfixed64(-1, Int64.fromInt(1)))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setSfixed64(-1, Int64.fromInt(1));
+      expect(accessor.getSfixed64WithDefault(-1)).toEqual(Int64.fromInt(1));
+    }
+  });
+
+  it('throws in setter for invalid value', () => {
+    if (CHECK_CRITICAL_TYPE) {
+      expect(
+          () => LazyAccessor.createEmpty().setSfixed64(
+              1, /** @type {!Int64} */ (/** @type {*} */ (null))))
+          .toThrow();
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setSfixed64(1, /** @type {!Int64} */ (/** @type {*} */ (null)));
+      expect(accessor.getSfixed64WithDefault(1)).toEqual(null);
+    }
+  });
+});
+
+describe('Sint32 access', () => {
+  it('returns default value for empty input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getSint32WithDefault(1)).toEqual(0);
+  });
+
+  it('returns the default from parameter', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getSint32WithDefault(1, 2)).toEqual(2);
+  });
+
+  it('decodes value from wire', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x02));
+    expect(accessor.getSint32WithDefault(1)).toEqual(1);
+  });
+
+  it('decodes value from wire with multple values being present', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x03, 0x08, 0x02));
+    expect(accessor.getSint32WithDefault(1)).toEqual(1);
+  });
+
+  it('fails when getting value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_TYPE) {
+      expect(() => {
+        accessor.getSint32WithDefault(1);
+      }).toThrowError('Expected wire type: 0 but found: 5');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getSint32WithDefault(1)).toEqual(0);
+    }
+  });
+
+  it('throws in getter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().getSint32WithDefault(-1, 1))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      expect(LazyAccessor.createEmpty().getSint32WithDefault(-1, 1)).toEqual(1);
+    }
+  });
+
+  it('returns the value from setter', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    accessor.setSint32(1, 2);
+    expect(accessor.getSint32WithDefault(1)).toEqual(2);
+  });
+
+  it('encode the value from setter', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const newBytes = createArrayBuffer(0x08, 0x00);
+    accessor.setSint32(1, 0);
+    expect(accessor.serialize()).toEqual(newBytes);
+  });
+
+  it('returns value from cache', () => {
+    const bytes = createArrayBuffer(0x08, 0x02);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getSint32WithDefault(1)).toBe(1);
+    // Make sure the value is cached.
+    bytes[2] = 0x00;
+    expect(accessor.getSint32WithDefault(1)).toBe(1);
+  });
+
+  it('throws in setter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().setSint32(-1, 1))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setSint32(-1, 1);
+      expect(accessor.getSint32WithDefault(-1)).toEqual(1);
+    }
+  });
+
+  it('throws in setter for invalid value', () => {
+    if (CHECK_CRITICAL_TYPE) {
+      expect(
+          () => LazyAccessor.createEmpty().setSint32(
+              1, /** @type {number} */ (/** @type {*} */ (null))))
+          .toThrow();
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setSint32(1, /** @type {number} */ (/** @type {*} */ (null)));
+      expect(accessor.getSint32WithDefault(1)).toEqual(null);
+    }
+  });
+});
+
+describe('SInt64 access', () => {
+  it('returns default value for empty input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getSint64WithDefault(1)).toEqual(Int64.fromInt(0));
+  });
+
+  it('returns the default from parameter', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getSint64WithDefault(1, Int64.fromInt(2)))
+        .toEqual(Int64.fromInt(2));
+  });
+
+  it('decodes value from wire', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x02));
+    expect(accessor.getSint64WithDefault(1)).toEqual(Int64.fromInt(1));
+  });
+
+  it('decodes value from wire with multple values being present', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x01, 0x08, 0x02));
+    expect(accessor.getSint64WithDefault(1)).toEqual(Int64.fromInt(1));
+  });
+
+  it('fails when getting value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_TYPE) {
+      expect(() => {
+        accessor.getSint64WithDefault(1);
+      }).toThrowError('Expected wire type: 0 but found: 5');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getSint64WithDefault(1)).toEqual(Int64.fromInt(0));
+    }
+  });
+
+  it('throws in getter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(
+          () => LazyAccessor.createEmpty().getSint64WithDefault(
+              -1, Int64.fromInt(1)))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      expect(
+          LazyAccessor.createEmpty().getSint64WithDefault(-1, Int64.fromInt(1)))
+          .toEqual(Int64.fromInt(1));
+    }
+  });
+
+  it('returns the value from setter', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    accessor.setSint64(1, Int64.fromInt(2));
+    expect(accessor.getSint64WithDefault(1)).toEqual(Int64.fromInt(2));
+  });
+
+  it('encode the value from setter', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const newBytes = createArrayBuffer(0x08, 0x00);
+    accessor.setSint64(1, Int64.fromInt(0));
+    expect(accessor.serialize()).toEqual(newBytes);
+  });
+
+  it('returns value from cache', () => {
+    const bytes = createArrayBuffer(0x08, 0x02);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getSint64WithDefault(1)).toEqual(Int64.fromInt(1));
+    // Make sure the value is cached.
+    bytes[1] = 0x00;
+    expect(accessor.getSint64WithDefault(1)).toEqual(Int64.fromInt(1));
+  });
+
+  it('throws in setter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().setSint64(-1, Int64.fromInt(1)))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setInt64(-1, Int64.fromInt(1));
+      expect(accessor.getSint64WithDefault(-1)).toEqual(Int64.fromInt(1));
+    }
+  });
+
+  it('throws in setter for invalid value', () => {
+    if (CHECK_CRITICAL_TYPE) {
+      expect(
+          () => LazyAccessor.createEmpty().setSint64(
+              1, /** @type {!Int64} */ (/** @type {*} */ (null))))
+          .toThrow();
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setSint64(1, /** @type {!Int64} */ (/** @type {*} */ (null)));
+      expect(accessor.getSint64WithDefault(1)).toEqual(null);
+    }
+  });
+});
+
+describe('String access', () => {
+  it('returns empty string for the empty input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getStringWithDefault(1)).toEqual('');
+  });
+
+  it('returns the default for the empty input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getStringWithDefault(1, 'bar')).toEqual('bar');
+  });
+
+  it('decodes value from wire', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x0A, 0x01, 0x61));
+    expect(accessor.getStringWithDefault(1)).toEqual('a');
+  });
+
+  it('decodes value from wire with multple values being present', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0A, 0x01, 0x60, 0x0A, 0x01, 0x61));
+    expect(accessor.getStringWithDefault(1)).toEqual('a');
+  });
+
+  if (CHECK_CRITICAL_STATE) {
+    it('fails when getting string value with other wire types', () => {
+      const accessor = LazyAccessor.fromArrayBuffer(
+          createArrayBuffer(0x08, 0x02, 0x08, 0x08));
+      expect(() => {
+        accessor.getStringWithDefault(1);
+      }).toThrow();
+    });
+  }
+
+
+  it('throws in getter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().getStringWithDefault(-1, 'a'))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      expect(LazyAccessor.createEmpty().getStringWithDefault(-1, 'a'))
+          .toEqual('a');
+    }
+  });
+
+  it('returns the value from setter', () => {
+    const bytes = createArrayBuffer(0x0A, 0x01, 0x61);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    accessor.setString(1, 'b');
+    expect(accessor.getStringWithDefault(1)).toEqual('b');
+  });
+
+  it('encode the value from setter', () => {
+    const bytes = createArrayBuffer(0x0A, 0x01, 0x61);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const newBytes = createArrayBuffer(0x0A, 0x01, 0x62);
+    accessor.setString(1, 'b');
+    expect(accessor.serialize()).toEqual(newBytes);
+  });
+
+  it('returns string value from cache', () => {
+    const bytes = createArrayBuffer(0x0A, 0x01, 0x61);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getStringWithDefault(1)).toBe('a');
+    // Make sure the value is cached.
+    bytes[2] = 0x00;
+    expect(accessor.getStringWithDefault(1)).toBe('a');
+  });
+
+  it('throws in setter for invalid fieldNumber', () => {
+    if (CHECK_TYPE) {
+      expect(() => LazyAccessor.createEmpty().setString(-1, 'a'))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setString(-1, 'a');
+      expect(accessor.getStringWithDefault(-1)).toEqual('a');
+    }
+  });
+
+  it('throws in setter for invalid value', () => {
+    if (CHECK_CRITICAL_TYPE) {
+      expect(
+          () => LazyAccessor.createEmpty().setString(
+              1, /** @type {string} */ (/** @type {*} */ (null))))
+          .toThrowError('Must be string, but got: null');
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setString(1, /** @type {string} */ (/** @type {*} */ (null)));
+      expect(accessor.getStringWithDefault(1)).toEqual(null);
+    }
+  });
+});
+
+describe('Uint32 access', () => {
+  it('returns default value for empty input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getUint32WithDefault(1)).toEqual(0);
+  });
+
+  it('returns the default from parameter', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getUint32WithDefault(1, 2)).toEqual(2);
+  });
+
+  it('decodes value from wire', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x01));
+    expect(accessor.getUint32WithDefault(1)).toEqual(1);
+  });
+
+  it('decodes value from wire with multple values being present', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x01, 0x08, 0x02));
+    expect(accessor.getUint32WithDefault(1)).toEqual(2);
+  });
+
+  it('fails when getting value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_TYPE) {
+      expect(() => {
+        accessor.getUint32WithDefault(1);
+      }).toThrowError('Expected wire type: 0 but found: 5');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getUint32WithDefault(1)).toEqual(0);
+    }
+  });
+
+  it('throws in getter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().getUint32WithDefault(-1, 1))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      expect(LazyAccessor.createEmpty().getUint32WithDefault(-1, 1)).toEqual(1);
+    }
+  });
+
+  it('returns the value from setter', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    accessor.setUint32(1, 2);
+    expect(accessor.getUint32WithDefault(1)).toEqual(2);
+  });
+
+  it('encode the value from setter', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const newBytes = createArrayBuffer(0x08, 0x00);
+    accessor.setUint32(1, 0);
+    expect(accessor.serialize()).toEqual(newBytes);
+  });
+
+  it('returns value from cache', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getUint32WithDefault(1)).toBe(1);
+    // Make sure the value is cached.
+    bytes[2] = 0x00;
+    expect(accessor.getUint32WithDefault(1)).toBe(1);
+  });
+
+  it('throws in setter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().setInt32(-1, 1))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setUint32(-1, 1);
+      expect(accessor.getUint32WithDefault(-1)).toEqual(1);
+    }
+  });
+
+  it('throws in setter for invalid value', () => {
+    if (CHECK_CRITICAL_TYPE) {
+      expect(
+          () => LazyAccessor.createEmpty().setUint32(
+              1, /** @type {number} */ (/** @type {*} */ (null))))
+          .toThrow();
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setUint32(1, /** @type {number} */ (/** @type {*} */ (null)));
+      expect(accessor.getUint32WithDefault(1)).toEqual(null);
+    }
+  });
+});
+
+describe('Uint64 access', () => {
+  it('returns default value for empty input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getUint64WithDefault(1)).toEqual(Int64.fromInt(0));
+  });
+
+  it('returns the default from parameter', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getUint64WithDefault(1, Int64.fromInt(2)))
+        .toEqual(Int64.fromInt(2));
+  });
+
+  it('decodes value from wire', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x01));
+    expect(accessor.getUint64WithDefault(1)).toEqual(Int64.fromInt(1));
+  });
+
+  it('decodes value from wire with multple values being present', () => {
+    const accessor =
+        LazyAccessor.fromArrayBuffer(createArrayBuffer(0x08, 0x01, 0x08, 0x02));
+    expect(accessor.getUint64WithDefault(1)).toEqual(Int64.fromInt(2));
+  });
+
+  it('fails when getting value with other wire types', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(
+        createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
+    if (CHECK_CRITICAL_TYPE) {
+      expect(() => {
+        accessor.getUint64WithDefault(1);
+      }).toThrowError('Expected wire type: 0 but found: 5');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      expect(accessor.getUint64WithDefault(1)).toEqual(Int64.fromInt(0));
+    }
+  });
+
+  it('throws in getter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(
+          () => LazyAccessor.createEmpty().getUint64WithDefault(
+              -1, Int64.fromInt(1)))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      expect(
+          LazyAccessor.createEmpty().getUint64WithDefault(-1, Int64.fromInt(1)))
+          .toEqual(Int64.fromInt(1));
+    }
+  });
+
+  it('returns the value from setter', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    accessor.setUint64(1, Int64.fromInt(2));
+    expect(accessor.getUint64WithDefault(1)).toEqual(Int64.fromInt(2));
+  });
+
+  it('encode the value from setter', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const newBytes = createArrayBuffer(0x08, 0x00);
+    accessor.setUint64(1, Int64.fromInt(0));
+    expect(accessor.serialize()).toEqual(newBytes);
+  });
+
+  it('returns value from cache', () => {
+    const bytes = createArrayBuffer(0x08, 0x01);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getUint64WithDefault(1)).toEqual(Int64.fromInt(1));
+    // Make sure the value is cached.
+    bytes[2] = 0x00;
+    expect(accessor.getUint64WithDefault(1)).toEqual(Int64.fromInt(1));
+  });
+
+  it('throws in setter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().setUint64(-1, Int64.fromInt(1)))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setUint64(-1, Int64.fromInt(1));
+      expect(accessor.getUint64WithDefault(-1)).toEqual(Int64.fromInt(1));
+    }
+  });
+
+  it('throws in setter for invalid value', () => {
+    if (CHECK_CRITICAL_TYPE) {
+      expect(
+          () => LazyAccessor.createEmpty().setUint64(
+              1, /** @type {!Int64} */ (/** @type {*} */ (null))))
+          .toThrow();
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setUint64(1, /** @type {!Int64} */ (/** @type {*} */ (null)));
+      expect(accessor.getUint64WithDefault(1)).toEqual(null);
+    }
+  });
+});
+
+describe('Double access', () => {
+  it('returns default value for empty input', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getDoubleWithDefault(1)).toEqual(0);
+  });
+
+  it('returns the default from parameter', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer());
+    expect(accessor.getDoubleWithDefault(1, 2)).toEqual(2);
+  });
+
+  it('decodes value from wire', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F));
+    expect(accessor.getDoubleWithDefault(1)).toEqual(1);
+  });
+
+
+  it('decodes value from wire with multple values being present', () => {
+    const accessor = LazyAccessor.fromArrayBuffer(createArrayBuffer(
+        0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x09, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF));
+    expect(accessor.getDoubleWithDefault(1)).toEqual(-1);
+  });
+
+  if (CHECK_CRITICAL_STATE) {
+    it('fails when getting double value with other wire types', () => {
+      const accessor = LazyAccessor.fromArrayBuffer(
+          createArrayBuffer(0x0D, 0x00, 0x00, 0xF0, 0x3F));
+      expect(() => {
+        accessor.getDoubleWithDefault(1);
+      }).toThrow();
+    });
+  }
+
+
+  it('throws in getter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().getDoubleWithDefault(-1, 1))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      expect(LazyAccessor.createEmpty().getDoubleWithDefault(-1, 1)).toEqual(1);
+    }
+  });
+
+  it('returns the value from setter', () => {
+    const bytes =
+        createArrayBuffer(0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    accessor.setDouble(1, 2);
+    expect(accessor.getDoubleWithDefault(1)).toEqual(2);
+  });
+
+  it('encode the value from setter', () => {
+    const bytes =
+        createArrayBuffer(0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    const newBytes =
+        createArrayBuffer(0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+    accessor.setDouble(1, 0);
+    expect(accessor.serialize()).toEqual(newBytes);
+  });
+
+  it('returns string value from cache', () => {
+    const bytes =
+        createArrayBuffer(0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F);
+    const accessor = LazyAccessor.fromArrayBuffer(bytes);
+    expect(accessor.getDoubleWithDefault(1)).toBe(1);
+    // Make sure the value is cached.
+    bytes[2] = 0x00;
+    expect(accessor.getDoubleWithDefault(1)).toBe(1);
+  });
+
+  it('throws in setter for invalid fieldNumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => LazyAccessor.createEmpty().setDouble(-1, 1))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setDouble(-1, 1);
+      expect(accessor.getDoubleWithDefault(-1)).toEqual(1);
+    }
+  });
+
+  it('throws in setter for invalid value', () => {
+    if (CHECK_CRITICAL_TYPE) {
+      expect(
+          () => LazyAccessor.createEmpty().setDouble(
+              1, /** @type {number} */ (/** @type {*} */ (null))))
+          .toThrowError('Must be a number, but got: null');
+    } else {
+      const accessor = LazyAccessor.createEmpty();
+      accessor.setDouble(1, /** @type {number} */ (/** @type {*} */ (null)));
+      expect(accessor.getDoubleWithDefault(1)).toEqual(null);
+    }
+  });
+});

+ 59 - 0
js/experimental/runtime/kernel/packed_bool_test_pairs.js

@@ -0,0 +1,59 @@
+goog.module('protobuf.binary.packedBoolTestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of packed bool values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, boolValues: !Array<boolean>,
+ *                  bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
+ */
+function getPackedBoolPairs() {
+  return [
+    {
+      name: 'empty value',
+      boolValues: [],
+      bufferDecoder: createBufferDecoder(0x00),
+      skip_writer: true,
+    },
+    {
+      name: 'single value',
+      boolValues: [true],
+      bufferDecoder: createBufferDecoder(0x01, 0x01),
+    },
+    {
+      name: 'single multi-bytes value',
+      boolValues: [true],
+      bufferDecoder: createBufferDecoder(0x02, 0x80, 0x01),
+      skip_writer: true,
+    },
+    {
+      name: 'multiple values',
+      boolValues: [true, false],
+      bufferDecoder: createBufferDecoder(0x02, 0x01, 0x00),
+    },
+    {
+      name: 'multiple multi-bytes values',
+      boolValues: [true, false],
+      bufferDecoder: createBufferDecoder(
+          0x0C,  // length
+          0x80,
+          0x80,
+          0x80,
+          0x80,
+          0x80,
+          0x01,  // true
+          0x80,
+          0x80,
+          0x80,
+          0x80,
+          0x80,
+          0x00,  // false
+          ),
+      skip_writer: true,
+    },
+  ];
+}
+
+exports = {getPackedBoolPairs};

+ 52 - 0
js/experimental/runtime/kernel/packed_double_test_pairs.js

@@ -0,0 +1,52 @@
+goog.module('protobuf.binary.packedDoubleTestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of packed double values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, doubleValues: !Array<number>,
+ *                  bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
+ */
+function getPackedDoublePairs() {
+  return [
+    {
+      name: 'empty value',
+      doubleValues: [],
+      bufferDecoder: createBufferDecoder(0x00),
+      skip_writer: true,
+    },
+    {
+      name: 'single value',
+      doubleValues: [1],
+      bufferDecoder: createBufferDecoder(
+          0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F),
+    },
+    {
+      name: 'multiple values',
+      doubleValues: [1, 0],
+      bufferDecoder: createBufferDecoder(
+          0x10,  // length
+          0x00,
+          0x00,
+          0x00,
+          0x00,
+          0x00,
+          0x00,
+          0xF0,
+          0x3F,  // 1
+          0x00,
+          0x00,
+          0x00,
+          0x00,
+          0x00,
+          0x00,
+          0x00,
+          0x00,  // 0
+          ),
+    },
+  ];
+}
+
+exports = {getPackedDoublePairs};

+ 34 - 0
js/experimental/runtime/kernel/packed_fixed32_test_pairs.js

@@ -0,0 +1,34 @@
+goog.module('protobuf.binary.packedFixed32TestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of packed fixed32 values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, fixed32Values: !Array<number>,
+ *                  bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
+ */
+function getPackedFixed32Pairs() {
+  return [
+    {
+      name: 'empty value',
+      fixed32Values: [],
+      bufferDecoder: createBufferDecoder(0x00),
+      skip_writer: true,
+    },
+    {
+      name: 'single value',
+      fixed32Values: [1],
+      bufferDecoder: createBufferDecoder(0x04, 0x01, 0x00, 0x00, 0x00),
+    },
+    {
+      name: 'multiple values',
+      fixed32Values: [1, 0],
+      bufferDecoder: createBufferDecoder(
+          0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
+    },
+  ];
+}
+
+exports = {getPackedFixed32Pairs};

+ 34 - 0
js/experimental/runtime/kernel/packed_float_test_pairs.js

@@ -0,0 +1,34 @@
+goog.module('protobuf.binary.packedFloatTestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of packed float values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, floatValues: !Array<number>,
+ *                  bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
+ */
+function getPackedFloatPairs() {
+  return [
+    {
+      name: 'empty value',
+      floatValues: [],
+      bufferDecoder: createBufferDecoder(0x00),
+      skip_writer: true,
+    },
+    {
+      name: 'single value',
+      floatValues: [1],
+      bufferDecoder: createBufferDecoder(0x04, 0x00, 0x00, 0x80, 0x3F),
+    },
+    {
+      name: 'multiple values',
+      floatValues: [1, 0],
+      bufferDecoder: createBufferDecoder(
+          0x08, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00),
+    },
+  ];
+}
+
+exports = {getPackedFloatPairs};

+ 33 - 0
js/experimental/runtime/kernel/packed_int32_test_pairs.js

@@ -0,0 +1,33 @@
+goog.module('protobuf.binary.packedInt32TestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of packed int32 values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, int32Values: !Array<number>,
+ *                  bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
+ */
+function getPackedInt32Pairs() {
+  return [
+    {
+      name: 'empty value',
+      int32Values: [],
+      bufferDecoder: createBufferDecoder(0x00),
+      skip_writer: true,
+    },
+    {
+      name: 'single value',
+      int32Values: [1],
+      bufferDecoder: createBufferDecoder(0x01, 0x01),
+    },
+    {
+      name: 'multiple values',
+      int32Values: [1, 0],
+      bufferDecoder: createBufferDecoder(0x02, 0x01, 0x00),
+    },
+  ];
+}
+
+exports = {getPackedInt32Pairs};

+ 34 - 0
js/experimental/runtime/kernel/packed_int64_test_pairs.js

@@ -0,0 +1,34 @@
+goog.module('protobuf.binary.packedInt64TestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const Int64 = goog.require('protobuf.Int64');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of packed int64 values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, int64Values: !Array<!Int64>,
+ *                  bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
+ */
+function getPackedInt64Pairs() {
+  return [
+    {
+      name: 'empty value',
+      int64Values: [],
+      bufferDecoder: createBufferDecoder(0x00),
+      skip_writer: true,
+    },
+    {
+      name: 'single value',
+      int64Values: [Int64.fromInt(1)],
+      bufferDecoder: createBufferDecoder(0x01, 0x01),
+    },
+    {
+      name: 'multiple values',
+      int64Values: [Int64.fromInt(1), Int64.fromInt(0)],
+      bufferDecoder: createBufferDecoder(0x02, 0x01, 0x00),
+    },
+  ];
+}
+
+exports = {getPackedInt64Pairs};

+ 34 - 0
js/experimental/runtime/kernel/packed_sfixed32_test_pairs.js

@@ -0,0 +1,34 @@
+goog.module('protobuf.binary.packedSfixed32TestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of packed sfixed32 values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, sfixed32Values: !Array<number>,
+ *                  bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
+ */
+function getPackedSfixed32Pairs() {
+  return [
+    {
+      name: 'empty value',
+      sfixed32Values: [],
+      bufferDecoder: createBufferDecoder(0x00),
+      skip_writer: true,
+    },
+    {
+      name: 'single value',
+      sfixed32Values: [1],
+      bufferDecoder: createBufferDecoder(0x04, 0x01, 0x00, 0x00, 0x00),
+    },
+    {
+      name: 'multiple values',
+      sfixed32Values: [1, 0],
+      bufferDecoder: createBufferDecoder(
+          0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
+    },
+  ];
+}
+
+exports = {getPackedSfixed32Pairs};

+ 53 - 0
js/experimental/runtime/kernel/packed_sfixed64_test_pairs.js

@@ -0,0 +1,53 @@
+goog.module('protobuf.binary.packedSfixed64TestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const Int64 = goog.require('protobuf.Int64');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of packed sfixed64 values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, sfixed64Values: !Array<!Int64>,
+ *                  bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
+ */
+function getPackedSfixed64Pairs() {
+  return [
+    {
+      name: 'empty value',
+      sfixed64Values: [],
+      bufferDecoder: createBufferDecoder(0x00),
+      skip_writer: true,
+    },
+    {
+      name: 'single value',
+      sfixed64Values: [Int64.fromInt(1)],
+      bufferDecoder: createBufferDecoder(
+          0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
+    },
+    {
+      name: 'multiple values',
+      sfixed64Values: [Int64.fromInt(1), Int64.fromInt(0)],
+      bufferDecoder: createBufferDecoder(
+          0x10,  // length
+          0x01,
+          0x00,
+          0x00,
+          0x00,
+          0x00,
+          0x00,
+          0x00,
+          0x00,  // 1
+          0x00,
+          0x00,
+          0x00,
+          0x00,
+          0x00,
+          0x00,
+          0x00,
+          0x00,  // 2
+          ),
+    },
+  ];
+}
+
+exports = {getPackedSfixed64Pairs};

+ 33 - 0
js/experimental/runtime/kernel/packed_sint32_test_pairs.js

@@ -0,0 +1,33 @@
+goog.module('protobuf.binary.packedSint32TestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of packed sint32 values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, sint32Values: !Array<number>,
+ *                  bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
+ */
+function getPackedSint32Pairs() {
+  return [
+    {
+      name: 'empty value',
+      sint32Values: [],
+      bufferDecoder: createBufferDecoder(0x00),
+      skip_writer: true,
+    },
+    {
+      name: 'single value',
+      sint32Values: [-1],
+      bufferDecoder: createBufferDecoder(0x01, 0x01),
+    },
+    {
+      name: 'multiple values',
+      sint32Values: [-1, 0],
+      bufferDecoder: createBufferDecoder(0x02, 0x01, 0x00),
+    },
+  ];
+}
+
+exports = {getPackedSint32Pairs};

+ 34 - 0
js/experimental/runtime/kernel/packed_sint64_test_pairs.js

@@ -0,0 +1,34 @@
+goog.module('protobuf.binary.packedSint64TestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const Int64 = goog.require('protobuf.Int64');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of packed sint64 values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, sint64Values: !Array<number>,
+ *                  bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
+ */
+function getPackedSint64Pairs() {
+  return [
+    {
+      name: 'empty value',
+      sint64Values: [],
+      bufferDecoder: createBufferDecoder(0x00),
+      skip_writer: true,
+    },
+    {
+      name: 'single value',
+      sint64Values: [Int64.fromInt(-1)],
+      bufferDecoder: createBufferDecoder(0x01, 0x01),
+    },
+    {
+      name: 'multiple values',
+      sint64Values: [Int64.fromInt(-1), Int64.fromInt(0)],
+      bufferDecoder: createBufferDecoder(0x02, 0x01, 0x00),
+    },
+  ];
+}
+
+exports = {getPackedSint64Pairs};

+ 33 - 0
js/experimental/runtime/kernel/packed_uint32_test_pairs.js

@@ -0,0 +1,33 @@
+goog.module('protobuf.binary.packedUint32TestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of packed uint32 values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, uint32Values: !Array<number>,
+ *                  bufferDecoder: !BufferDecoder, skip_writer: ?boolean}>}
+ */
+function getPackedUint32Pairs() {
+  return [
+    {
+      name: 'empty value',
+      uint32Values: [],
+      bufferDecoder: createBufferDecoder(0x00),
+      skip_writer: true,
+    },
+    {
+      name: 'single value',
+      uint32Values: [1],
+      bufferDecoder: createBufferDecoder(0x01, 0x01),
+    },
+    {
+      name: 'multiple values',
+      uint32Values: [1, 0],
+      bufferDecoder: createBufferDecoder(0x02, 0x01, 0x00),
+    },
+  ];
+}
+
+exports = {getPackedUint32Pairs};

+ 464 - 0
js/experimental/runtime/kernel/reader.js

@@ -0,0 +1,464 @@
+/**
+ * @fileoverview Helper methods for reading data from the binary wire format.
+ */
+goog.module('protobuf.binary.reader');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const ByteString = goog.require('protobuf.ByteString');
+const Int64 = goog.require('protobuf.Int64');
+
+
+/******************************************************************************
+ *                        OPTIONAL FUNCTIONS
+ ******************************************************************************/
+
+/**
+ * Reads a boolean from the binary bytes.
+ * Also returns the first position after the boolean.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} index Start of the data.
+ * @return {{value: boolean, nextCursor: number}}
+ */
+function readBoolValue(bufferDecoder, index) {
+  const {lowBits, highBits, dataStart} = bufferDecoder.getVarint(index);
+  return {value: lowBits !== 0 || highBits !== 0, nextCursor: dataStart};
+}
+
+/**
+ * Reads a boolean value from the binary bytes.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {boolean}
+ * @package
+ */
+function readBool(bufferDecoder, start) {
+  return readBoolValue(bufferDecoder, start).value;
+}
+
+/**
+ * Reads a double value from the binary bytes.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {!ByteString}
+ * @package
+ */
+function readBytes(bufferDecoder, start) {
+  return readDelimited(bufferDecoder, start).asByteString();
+}
+
+/**
+ * Reads a int32 value from the binary bytes encoded as varint.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} index Start of the data.
+ * @return {{value: number, nextCursor: number}}
+ * @package
+ */
+function readInt32Value(bufferDecoder, index) {
+  const {lowBits, dataStart} = bufferDecoder.getVarint(index);
+  // Negative 32 bit integers are encoded with 64 bit values.
+  // Clients are expected to truncate back to 32 bits.
+  // This is why we are dropping the upper bytes here.
+  return {value: lowBits | 0, nextCursor: dataStart};
+}
+
+/**
+ * Reads a int32 value from the binary bytes encoded as varint.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {number}
+ * @package
+ */
+function readInt32(bufferDecoder, start) {
+  return readInt32Value(bufferDecoder, start).value;
+}
+
+/**
+ * Reads a int32 value from the binary bytes encoded as varint.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} index Start of the data.
+ * @return {{ value: !Int64, nextCursor: number}}
+ * @package
+ */
+function readInt64Value(bufferDecoder, index) {
+  const {lowBits, highBits, dataStart} = bufferDecoder.getVarint(index);
+  return {value: Int64.fromBits(lowBits, highBits), nextCursor: dataStart};
+}
+
+/**
+ * Reads a int32 value from the binary bytes encoded as varint.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {!Int64}
+ * @package
+ */
+function readInt64(bufferDecoder, start) {
+  return readInt64Value(bufferDecoder, start).value;
+}
+
+/**
+ * Reads a fixed int32 value from the binary bytes.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {number}
+ * @package
+ */
+function readFixed32(bufferDecoder, start) {
+  return bufferDecoder.getUint32(start);
+}
+
+/**
+ * Reads a float value from the binary bytes.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {number}
+ * @package
+ */
+function readFloat(bufferDecoder, start) {
+  return bufferDecoder.getFloat32(start);
+}
+
+/**
+ * Reads a fixed int64 value from the binary bytes.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {!Int64}
+ * @package
+ */
+function readSfixed64(bufferDecoder, start) {
+  const lowBits = bufferDecoder.getInt32(start);
+  const highBits = bufferDecoder.getInt32(start + 4);
+  return Int64.fromBits(lowBits, highBits);
+}
+
+/**
+ * Reads a sint32 value from the binary bytes encoded as varint.
+ * Also returns the first position after the boolean.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} index Start of the data.
+ * @return {{value: number, nextCursor: number}}
+ */
+function readSint32Value(bufferDecoder, index) {
+  const {lowBits, dataStart} = bufferDecoder.getVarint(index);
+  // Truncate upper bits and convert from zig zag to signd int
+  return {value: (lowBits >>> 1) ^ -(lowBits & 0x01), nextCursor: dataStart};
+}
+
+/**
+ * Reads a sint32 value from the binary bytes encoded as varint.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {number}
+ * @package
+ */
+function readSint32(bufferDecoder, start) {
+  return readSint32Value(bufferDecoder, start).value;
+}
+
+/**
+ * Reads a sint64 value from the binary bytes encoded as varint.
+ * Also returns the first position after the value.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} index Start of the data.
+ * @return {{value: !Int64, nextCursor: number}}
+ * @package
+ */
+function readSint64Value(bufferDecoder, index) {
+  const {lowBits, highBits, dataStart} = bufferDecoder.getVarint(index);
+  const sign = -(lowBits & 0x01);
+  const decodedLowerBits = ((lowBits >>> 1) | (highBits & 0x01) << 31) ^ sign;
+  const decodedUpperBits = (highBits >>> 1) ^ sign;
+  return {
+    value: Int64.fromBits(decodedLowerBits, decodedUpperBits),
+    nextCursor: dataStart
+  };
+}
+
+/**
+ * Reads a sint64 value from the binary bytes encoded as varint.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {!Int64}
+ * @package
+ */
+function readSint64(bufferDecoder, start) {
+  return readSint64Value(bufferDecoder, start).value;
+}
+
+/**
+ * Read a subarray of bytes representing a length delimited field.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {!BufferDecoder}
+ * @package
+ */
+function readDelimited(bufferDecoder, start) {
+  const {lowBits, dataStart} = bufferDecoder.getVarint(start);
+  const unsignedLength = lowBits >>> 0;
+  return bufferDecoder.subBufferDecoder(dataStart, unsignedLength);
+}
+
+/**
+ * Reads a string value from the binary bytes.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {string}
+ * @package
+ */
+function readString(bufferDecoder, start) {
+  return readDelimited(bufferDecoder, start).asString();
+}
+
+/**
+ * Reads a uint32 value from the binary bytes encoded as varint.
+ * Also returns the first position after the value.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} index Start of the data.
+ * @return {{value: number, nextCursor: number}}
+ */
+function readUint32Value(bufferDecoder, index) {
+  const {lowBits, dataStart} = bufferDecoder.getVarint(index);
+  return {value: lowBits >>> 0, nextCursor: dataStart};
+}
+
+/**
+ * Reads a uint32 value from the binary bytes encoded as varint.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {number}
+ * @package
+ */
+function readUint32(bufferDecoder, start) {
+  return readUint32Value(bufferDecoder, start).value;
+}
+
+/**
+ * Reads a double value from the binary bytes.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {number}
+ * @package
+ */
+function readDouble(bufferDecoder, start) {
+  return bufferDecoder.getFloat64(start);
+}
+
+/**
+ * Reads a fixed int32 value from the binary bytes.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {number}
+ * @package
+ */
+function readSfixed32(bufferDecoder, start) {
+  return bufferDecoder.getInt32(start);
+}
+
+/******************************************************************************
+ *                        REPEATED FUNCTIONS
+ ******************************************************************************/
+
+/**
+ * Reads a packed bool field, which consists of a length header and a list of
+ * unsigned varints.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {!Array<boolean>}
+ * @package
+ */
+function readPackedBool(bufferDecoder, start) {
+  return readPackedVariableLength(bufferDecoder, start, readBoolValue);
+}
+
+/**
+ * Reads a packed double field, which consists of a length header and a list of
+ * fixed64.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {!Array<number>}
+ * @package
+ */
+function readPackedDouble(bufferDecoder, start) {
+  return readPackedFixed(bufferDecoder, start, 8, readDouble);
+}
+
+/**
+ * Reads a packed fixed32 field, which consists of a length header and a list of
+ * fixed32.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {!Array<number>}
+ * @package
+ */
+function readPackedFixed32(bufferDecoder, start) {
+  return readPackedFixed(bufferDecoder, start, 4, readFixed32);
+}
+
+/**
+ * Reads a packed float field, which consists of a length header and a list of
+ * fixed64.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {!Array<number>}
+ * @package
+ */
+function readPackedFloat(bufferDecoder, start) {
+  return readPackedFixed(bufferDecoder, start, 4, readFloat);
+}
+
+/**
+ * Reads a packed int32 field, which consists of a length header and a list of
+ * varint.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {!Array<number>}
+ * @package
+ */
+function readPackedInt32(bufferDecoder, start) {
+  return readPackedVariableLength(bufferDecoder, start, readInt32Value);
+}
+
+/**
+ * Reads a packed int64 field, which consists of a length header and a list
+ * of int64.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {!Array<!Int64>}
+ * @package
+ */
+function readPackedInt64(bufferDecoder, start) {
+  return readPackedVariableLength(bufferDecoder, start, readInt64Value);
+}
+
+/**
+ * Reads a packed sfixed32 field, which consists of a length header and a list
+ * of sfixed32.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {!Array<number>}
+ * @package
+ */
+function readPackedSfixed32(bufferDecoder, start) {
+  return readPackedFixed(bufferDecoder, start, 4, readSfixed32);
+}
+
+/**
+ * Reads a packed sfixed64 field, which consists of a length header and a list
+ * of sfixed64.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {!Array<!Int64>}
+ * @package
+ */
+function readPackedSfixed64(bufferDecoder, start) {
+  return readPackedFixed(bufferDecoder, start, 8, readSfixed64);
+}
+
+/**
+ * Reads a packed sint32 field, which consists of a length header and a list of
+ * varint.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {!Array<number>}
+ * @package
+ */
+function readPackedSint32(bufferDecoder, start) {
+  return readPackedVariableLength(bufferDecoder, start, readSint32Value);
+}
+
+/**
+ * Reads a packed sint64 field, which consists of a length header and a list
+ * of sint64.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {!Array<!Int64>}
+ * @package
+ */
+function readPackedSint64(bufferDecoder, start) {
+  return readPackedVariableLength(bufferDecoder, start, readSint64Value);
+}
+
+/**
+ * Reads a packed uint32 field, which consists of a length header and a list of
+ * varint.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @return {!Array<number>}
+ * @package
+ */
+function readPackedUint32(bufferDecoder, start) {
+  return readPackedVariableLength(bufferDecoder, start, readUint32Value);
+}
+
+/**
+ * Read packed variable length values.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @param {function(!BufferDecoder, number):{value:T, nextCursor: number}}
+ *     valueFunction
+ * @return {!Array<T>}
+ * @package
+ * @template T
+ */
+function readPackedVariableLength(bufferDecoder, start, valueFunction) {
+  const /** !Array<T> */ result = [];
+  const {lowBits, dataStart} = bufferDecoder.getVarint(start);
+  let cursor = dataStart;
+  const unsignedLength = lowBits >>> 0;
+  while (cursor < dataStart + unsignedLength) {
+    const {value, nextCursor} = valueFunction(bufferDecoder, cursor);
+    cursor = nextCursor;
+    result.push(value);
+  }
+  return result;
+}
+
+/**
+ * Read a packed fixed values.
+ * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
+ * @param {number} start Start of the data.
+ * @param {number} size End of the data.
+ * @param {function(!BufferDecoder, number):T} valueFunction
+ * @return {!Array<T>}
+ * @package
+ * @template T
+ */
+function readPackedFixed(bufferDecoder, start, size, valueFunction) {
+  const {lowBits, dataStart} = bufferDecoder.getVarint(start);
+  const unsignedLength = lowBits >>> 0;
+  const noOfEntries = unsignedLength / size;
+  const /** !Array<T> */ result = new Array(noOfEntries);
+  let cursor = dataStart;
+  for (let i = 0; i < noOfEntries; i++) {
+    result[i] = valueFunction(bufferDecoder, cursor);
+    cursor += size;
+  }
+  return result;
+}
+
+exports = {
+  readBool,
+  readBytes,
+  readDelimited,
+  readDouble,
+  readFixed32,
+  readFloat,
+  readInt32,
+  readInt64,
+  readSint32,
+  readSint64,
+  readSfixed32,
+  readSfixed64,
+  readString,
+  readUint32,
+  readPackedBool,
+  readPackedDouble,
+  readPackedFixed32,
+  readPackedFloat,
+  readPackedInt32,
+  readPackedInt64,
+  readPackedSfixed32,
+  readPackedSfixed64,
+  readPackedSint32,
+  readPackedSint64,
+  readPackedUint32,
+};

+ 423 - 0
js/experimental/runtime/kernel/reader_test.js

@@ -0,0 +1,423 @@
+/**
+ * @fileoverview Tests for reader.js.
+ */
+goog.module('protobuf.binary.ReaderTest');
+
+goog.setTestOnly();
+
+// Note to the reader:
+// Since the reader behavior changes with the checking level some of the
+// tests in this file have to know which checking level is enable to make
+// correct assertions.
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const ByteString = goog.require('protobuf.ByteString');
+const reader = goog.require('protobuf.binary.reader');
+const {CHECK_STATE} = goog.require('protobuf.internal.checks');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+const {encode} = goog.require('protobuf.binary.textencoding');
+const {getBoolPairs} = goog.require('protobuf.binary.boolTestPairs');
+const {getDoublePairs} = goog.require('protobuf.binary.doubleTestPairs');
+const {getFixed32Pairs} = goog.require('protobuf.binary.fixed32TestPairs');
+const {getFloatPairs} = goog.require('protobuf.binary.floatTestPairs');
+const {getInt32Pairs} = goog.require('protobuf.binary.int32TestPairs');
+const {getInt64Pairs} = goog.require('protobuf.binary.int64TestPairs');
+const {getPackedBoolPairs} = goog.require('protobuf.binary.packedBoolTestPairs');
+const {getPackedDoublePairs} = goog.require('protobuf.binary.packedDoubleTestPairs');
+const {getPackedFixed32Pairs} = goog.require('protobuf.binary.packedFixed32TestPairs');
+const {getPackedFloatPairs} = goog.require('protobuf.binary.packedFloatTestPairs');
+const {getPackedInt32Pairs} = goog.require('protobuf.binary.packedInt32TestPairs');
+const {getPackedInt64Pairs} = goog.require('protobuf.binary.packedInt64TestPairs');
+const {getPackedSfixed32Pairs} = goog.require('protobuf.binary.packedSfixed32TestPairs');
+const {getPackedSfixed64Pairs} = goog.require('protobuf.binary.packedSfixed64TestPairs');
+const {getPackedSint32Pairs} = goog.require('protobuf.binary.packedSint32TestPairs');
+const {getPackedSint64Pairs} = goog.require('protobuf.binary.packedSint64TestPairs');
+const {getPackedUint32Pairs} = goog.require('protobuf.binary.packedUint32TestPairs');
+const {getSfixed32Pairs} = goog.require('protobuf.binary.sfixed32TestPairs');
+const {getSfixed64Pairs} = goog.require('protobuf.binary.sfixed64TestPairs');
+const {getSint32Pairs} = goog.require('protobuf.binary.sint32TestPairs');
+const {getSint64Pairs} = goog.require('protobuf.binary.sint64TestPairs');
+const {getUint32Pairs} = goog.require('protobuf.binary.uint32TestPairs');
+
+/******************************************************************************
+ *                        Optional FUNCTIONS
+ ******************************************************************************/
+
+describe('Read bool does', () => {
+  for (const pair of getBoolPairs()) {
+    it(`decode ${pair.name}`, () => {
+      if (pair.error && CHECK_STATE) {
+        expect(() => reader.readBool(pair.bufferDecoder, 0)).toThrow();
+      } else {
+        const d = reader.readBool(
+            pair.bufferDecoder, pair.bufferDecoder.startIndex());
+        expect(d).toEqual(pair.boolValue);
+      }
+    });
+  }
+});
+
+describe('readBytes does', () => {
+  it('throw exception if data is too short', () => {
+    const bufferDecoder = createBufferDecoder();
+    expect(() => reader.readBytes(bufferDecoder, 0)).toThrow();
+  });
+
+  it('read bytes by index', () => {
+    const bufferDecoder = createBufferDecoder(3, 1, 2, 3);
+    const byteString = reader.readBytes(bufferDecoder, 0);
+    expect(ByteString.fromArrayBuffer(new Uint8Array([1, 2, 3]).buffer))
+        .toEqual(byteString);
+  });
+});
+
+describe('readDouble does', () => {
+  it('throw exception if data is too short', () => {
+    const bufferDecoder = createBufferDecoder();
+    expect(() => reader.readDouble(bufferDecoder, 0)).toThrow();
+  });
+
+  for (const pair of getDoublePairs()) {
+    it(`decode ${pair.name}`, () => {
+      const d = reader.readDouble(pair.bufferDecoder, 0);
+      expect(d).toEqual(pair.doubleValue);
+    });
+  }
+});
+
+describe('readFixed32 does', () => {
+  it('throw exception if data is too short', () => {
+    const bufferDecoder = createBufferDecoder();
+    expect(() => reader.readFixed32(bufferDecoder, 0)).toThrow();
+  });
+
+  for (const pair of getFixed32Pairs()) {
+    it(`decode ${pair.name}`, () => {
+      const d = reader.readFixed32(pair.bufferDecoder, 0);
+      expect(d).toEqual(pair.intValue);
+    });
+  }
+});
+
+describe('readFloat does', () => {
+  it('throw exception if data is too short', () => {
+    const bufferDecoder = createBufferDecoder();
+    expect(() => reader.readFloat(bufferDecoder, 0)).toThrow();
+  });
+
+  for (const pair of getFloatPairs()) {
+    it(`decode ${pair.name}`, () => {
+      const d = reader.readFloat(pair.bufferDecoder, 0);
+      expect(d).toEqual(Math.fround(pair.floatValue));
+    });
+  }
+});
+
+describe('readInt32 does', () => {
+  it('throw exception if data is too short', () => {
+    const bufferDecoder = createBufferDecoder(0x80);
+    expect(() => reader.readInt32(bufferDecoder, 0)).toThrow();
+  });
+
+  for (const pair of getInt32Pairs()) {
+    it(`decode ${pair.name}`, () => {
+      if (pair.error && CHECK_STATE) {
+        expect(() => reader.readInt32(pair.bufferDecoder, 0)).toThrow();
+      } else {
+        const d = reader.readInt32(pair.bufferDecoder, 0);
+        expect(d).toEqual(pair.intValue);
+      }
+    });
+  }
+});
+
+describe('readSfixed32 does', () => {
+  it('throw exception if data is too short', () => {
+    const bufferDecoder = createBufferDecoder(0x80);
+    expect(() => reader.readSfixed32(bufferDecoder, 0)).toThrow();
+  });
+
+  for (const pair of getSfixed32Pairs()) {
+    it(`decode ${pair.name}`, () => {
+      const d = reader.readSfixed32(pair.bufferDecoder, 0);
+      expect(d).toEqual(pair.intValue);
+    });
+  }
+});
+
+describe('readSfixed64 does', () => {
+  it('throw exception if data is too short', () => {
+    const bufferDecoder = createBufferDecoder(0x80);
+    expect(() => reader.readSfixed64(bufferDecoder, 0)).toThrow();
+  });
+
+  for (const pair of getSfixed64Pairs()) {
+    it(`decode ${pair.name}`, () => {
+      const d = reader.readSfixed64(pair.bufferDecoder, 0);
+      expect(d).toEqual(pair.longValue);
+    });
+  }
+});
+
+describe('readSint32 does', () => {
+  it('throw exception if data is too short', () => {
+    const bufferDecoder = createBufferDecoder(0x80);
+    expect(() => reader.readSint32(bufferDecoder, 0)).toThrow();
+  });
+
+  for (const pair of getSint32Pairs()) {
+    it(`decode ${pair.name}`, () => {
+      if (pair.error && CHECK_STATE) {
+        expect(() => reader.readSint32(pair.bufferDecoder, 0)).toThrow();
+      } else {
+        const d = reader.readSint32(pair.bufferDecoder, 0);
+        expect(d).toEqual(pair.intValue);
+      }
+    });
+  }
+});
+
+describe('readInt64 does', () => {
+  it('throw exception if data is too short', () => {
+    const bufferDecoder = createBufferDecoder(0x80);
+    expect(() => reader.readInt64(bufferDecoder, 0)).toThrow();
+  });
+
+  for (const pair of getInt64Pairs()) {
+    it(`decode ${pair.name}`, () => {
+      if (pair.error && CHECK_STATE) {
+        expect(() => reader.readInt64(pair.bufferDecoder, 0)).toThrow();
+      } else {
+        const d = reader.readInt64(pair.bufferDecoder, 0);
+        expect(d).toEqual(pair.longValue);
+      }
+    });
+  }
+});
+
+describe('readSint64 does', () => {
+  it('throw exception if data is too short', () => {
+    const bufferDecoder = createBufferDecoder(0x80);
+    expect(() => reader.readSint64(bufferDecoder, 0)).toThrow();
+  });
+
+  for (const pair of getSint64Pairs()) {
+    it(`decode ${pair.name}`, () => {
+      if (pair.error && CHECK_STATE) {
+        expect(() => reader.readSint64(pair.bufferDecoder, 0)).toThrow();
+      } else {
+        const d = reader.readSint64(pair.bufferDecoder, 0);
+        expect(d).toEqual(pair.longValue);
+      }
+    });
+  }
+});
+
+describe('readUint32 does', () => {
+  it('throw exception if data is too short', () => {
+    const bufferDecoder = createBufferDecoder(0x80);
+    expect(() => reader.readUint32(bufferDecoder, 0)).toThrow();
+  });
+
+  for (const pair of getUint32Pairs()) {
+    it(`decode ${pair.name}`, () => {
+      if (pair.error && CHECK_STATE) {
+        expect(() => reader.readUint32(pair.bufferDecoder, 0)).toThrow();
+      } else {
+        const d = reader.readUint32(pair.bufferDecoder, 0);
+        expect(d).toEqual(pair.intValue);
+      }
+    });
+  }
+});
+
+/**
+ *
+ * @param {string} s
+ * @return {!Uint8Array}
+ */
+function encodeString(s) {
+  if (typeof TextEncoder !== 'undefined') {
+    const textEncoder = new TextEncoder('utf-8');
+    return textEncoder.encode(s);
+  } else {
+    return encode(s);
+  }
+}
+
+/** @param {string} s */
+function expectEncodedStringToMatch(s) {
+  const array = encodeString(s);
+  const length = array.length;
+  if (length > 127) {
+    throw new Error('Test only works for strings shorter than 128');
+  }
+  const encodedArray = new Uint8Array(length + 1);
+  encodedArray[0] = length;
+  encodedArray.set(array, 1);
+  const bufferDecoder = BufferDecoder.fromArrayBuffer(encodedArray.buffer);
+  expect(reader.readString(bufferDecoder, 0)).toEqual(s);
+}
+
+describe('readString does', () => {
+  it('return empty string for zero length string', () => {
+    const s = reader.readString(createBufferDecoder(0x00), 0);
+    expect(s).toEqual('');
+  });
+
+  it('decode random strings', () => {
+    // 1 byte strings
+    expectEncodedStringToMatch('hello');
+    expectEncodedStringToMatch('HELLO1!');
+
+    // 2 byte String
+    expectEncodedStringToMatch('©');
+
+    // 3 byte string
+    expectEncodedStringToMatch('❄');
+
+    // 4 byte string
+    expectEncodedStringToMatch('😁');
+  });
+
+  it('decode 1 byte strings', () => {
+    for (let i = 0; i < 0x80; i++) {
+      const s = String.fromCharCode(i);
+      expectEncodedStringToMatch(s);
+    }
+  });
+
+  it('decode 2 byte strings', () => {
+    for (let i = 0xC0; i < 0x7FF; i++) {
+      const s = String.fromCharCode(i);
+      expectEncodedStringToMatch(s);
+    }
+  });
+
+  it('decode 3 byte strings', () => {
+    for (let i = 0x7FF; i < 0x8FFF; i++) {
+      const s = String.fromCharCode(i);
+      expectEncodedStringToMatch(s);
+    }
+  });
+
+  it('throw exception on invalid bytes', () => {
+    // This test will only succeed with the native TextDecoder since
+    // our polyfill does not do any validation. IE10 and IE11 don't support
+    // TextDecoder.
+    // TODO: Remove this check once we no longer need to support IE
+    if (typeof TextDecoder !== 'undefined') {
+      expect(
+          () => reader.readString(
+              createBufferDecoder(0x01, /* invalid utf data point*/ 0xFF), 0))
+          .toThrow();
+    }
+  });
+
+  it('throw exception if data is too short', () => {
+    const array = createBufferDecoder(0x02, '?'.charCodeAt(0));
+    expect(() => reader.readString(array, 0)).toThrow();
+  });
+});
+
+/******************************************************************************
+ *                        REPEATED FUNCTIONS
+ ******************************************************************************/
+
+describe('readPackedBool does', () => {
+  for (const pair of getPackedBoolPairs()) {
+    it(`decode ${pair.name}`, () => {
+      const d = reader.readPackedBool(pair.bufferDecoder, 0);
+      expect(d).toEqual(pair.boolValues);
+    });
+  }
+});
+
+describe('readPackedDouble does', () => {
+  for (const pair of getPackedDoublePairs()) {
+    it(`decode ${pair.name}`, () => {
+      const d = reader.readPackedDouble(pair.bufferDecoder, 0);
+      expect(d).toEqual(pair.doubleValues);
+    });
+  }
+});
+
+describe('readPackedFixed32 does', () => {
+  for (const pair of getPackedFixed32Pairs()) {
+    it(`decode ${pair.name}`, () => {
+      const d = reader.readPackedFixed32(pair.bufferDecoder, 0);
+      expect(d).toEqual(pair.fixed32Values);
+    });
+  }
+});
+
+describe('readPackedFloat does', () => {
+  for (const pair of getPackedFloatPairs()) {
+    it(`decode ${pair.name}`, () => {
+      const d = reader.readPackedFloat(pair.bufferDecoder, 0);
+      expect(d).toEqual(pair.floatValues);
+    });
+  }
+});
+
+describe('readPackedInt32 does', () => {
+  for (const pair of getPackedInt32Pairs()) {
+    it(`decode ${pair.name}`, () => {
+      const d = reader.readPackedInt32(pair.bufferDecoder, 0);
+      expect(d).toEqual(pair.int32Values);
+    });
+  }
+});
+
+describe('readPackedInt64 does', () => {
+  for (const pair of getPackedInt64Pairs()) {
+    it(`decode ${pair.name}`, () => {
+      const d = reader.readPackedInt64(pair.bufferDecoder, 0);
+      expect(d).toEqual(pair.int64Values);
+    });
+  }
+});
+
+describe('readPackedSfixed32 does', () => {
+  for (const pair of getPackedSfixed32Pairs()) {
+    it(`decode ${pair.name}`, () => {
+      const d = reader.readPackedSfixed32(pair.bufferDecoder, 0);
+      expect(d).toEqual(pair.sfixed32Values);
+    });
+  }
+});
+
+describe('readPackedSfixed64 does', () => {
+  for (const pair of getPackedSfixed64Pairs()) {
+    it(`decode ${pair.name}`, () => {
+      const d = reader.readPackedSfixed64(pair.bufferDecoder, 0);
+      expect(d).toEqual(pair.sfixed64Values);
+    });
+  }
+});
+
+describe('readPackedSint32 does', () => {
+  for (const pair of getPackedSint32Pairs()) {
+    it(`decode ${pair.name}`, () => {
+      const d = reader.readPackedSint32(pair.bufferDecoder, 0);
+      expect(d).toEqual(pair.sint32Values);
+    });
+  }
+});
+
+describe('readPackedSint64 does', () => {
+  for (const pair of getPackedSint64Pairs()) {
+    it(`decode ${pair.name}`, () => {
+      const d = reader.readPackedSint64(pair.bufferDecoder, 0);
+      expect(d).toEqual(pair.sint64Values);
+    });
+  }
+});
+
+describe('readPackedUint32 does', () => {
+  for (const pair of getPackedUint32Pairs()) {
+    it(`decode ${pair.name}`, () => {
+      const d = reader.readPackedUint32(pair.bufferDecoder, 0);
+      expect(d).toEqual(pair.uint32Values);
+    });
+  }
+});

+ 46 - 0
js/experimental/runtime/kernel/sfixed32_test_pairs.js

@@ -0,0 +1,46 @@
+/**
+ * @fileoverview Test data for sfixed32 encoding and decoding.
+ */
+goog.module('protobuf.binary.sfixed32TestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of int values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, intValue: number, bufferDecoder:
+ *     !BufferDecoder}>}
+ */
+function getSfixed32Pairs() {
+  const sfixed32Pairs = [
+    {
+      name: 'zero',
+      intValue: 0,
+      bufferDecoder: createBufferDecoder(0x00, 0x00, 0x00, 0x00),
+    },
+    {
+      name: 'one',
+      intValue: 1,
+      bufferDecoder: createBufferDecoder(0x01, 0x00, 0x00, 0x00)
+    },
+    {
+      name: 'minus one',
+      intValue: -1,
+      bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF),
+    },
+    {
+      name: 'max int 2^31 -1',
+      intValue: Math.pow(2, 31) - 1,
+      bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0x7F)
+    },
+    {
+      name: 'min int -2^31',
+      intValue: -Math.pow(2, 31),
+      bufferDecoder: createBufferDecoder(0x00, 0x00, 0x00, 0x80)
+    },
+  ];
+  return [...sfixed32Pairs];
+}
+
+exports = {getSfixed32Pairs};

+ 52 - 0
js/experimental/runtime/kernel/sfixed64_test_pairs.js

@@ -0,0 +1,52 @@
+/**
+ * @fileoverview Test data for sfixed32 encoding and decoding.
+ */
+goog.module('protobuf.binary.sfixed64TestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const Int64 = goog.require('protobuf.Int64');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of int values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, longValue: !Int64, bufferDecoder:
+ *     !BufferDecoder}>}
+ */
+function getSfixed64Pairs() {
+  const sfixed64Pairs = [
+    {
+      name: 'zero',
+      longValue: Int64.fromInt(0),
+      bufferDecoder:
+          createBufferDecoder(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
+    },
+    {
+      name: 'one',
+      longValue: Int64.fromInt(1),
+      bufferDecoder:
+          createBufferDecoder(0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
+    },
+    {
+      name: 'minus one',
+      longValue: Int64.fromInt(-1),
+      bufferDecoder:
+          createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF),
+    },
+    {
+      name: 'max int 2^63 -1',
+      longValue: Int64.getMaxValue(),
+      bufferDecoder:
+          createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F)
+    },
+    {
+      name: 'min int -2^63',
+      longValue: Int64.getMinValue(),
+      bufferDecoder:
+          createBufferDecoder(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80)
+    },
+  ];
+  return [...sfixed64Pairs];
+}
+
+exports = {getSfixed64Pairs};

+ 57 - 0
js/experimental/runtime/kernel/sint32_test_pairs.js

@@ -0,0 +1,57 @@
+/**
+ * @fileoverview Test data for int32 encoding and decoding.
+ */
+goog.module('protobuf.binary.sint32TestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of float values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, intValue:number, bufferDecoder:
+ *     !BufferDecoder, error: ?boolean, skip_writer: ?boolean}>}
+ */
+function getSint32Pairs() {
+  const sint32Pairs = [
+    {
+      name: 'zero',
+      intValue: 0,
+      bufferDecoder: createBufferDecoder(0x00),
+    },
+    {
+      name: 'one ',
+      intValue: 1,
+      bufferDecoder: createBufferDecoder(0x02),
+    },
+    {
+      name: 'minus one',
+      intValue: -1,
+      bufferDecoder: createBufferDecoder(0x01),
+    },
+    {
+      name: 'two',
+      intValue: 2,
+      bufferDecoder: createBufferDecoder(0x04),
+    },
+    {
+      name: 'minus two',
+      intValue: -2,
+      bufferDecoder: createBufferDecoder(0x03),
+    },
+    {
+      name: 'int 2^31 - 1',
+      intValue: Math.pow(2, 31) - 1,
+      bufferDecoder: createBufferDecoder(0xFE, 0xFF, 0xFF, 0xFF, 0x0F),
+
+    },
+    {
+      name: '-2^31',
+      intValue: -Math.pow(2, 31),
+      bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0x0F),
+    },
+  ];
+  return [...sint32Pairs];
+}
+
+exports = {getSint32Pairs};

+ 60 - 0
js/experimental/runtime/kernel/sint64_test_pairs.js

@@ -0,0 +1,60 @@
+/**
+ * @fileoverview Test data for sint64 encoding and decoding.
+ */
+goog.module('protobuf.binary.sint64TestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const Int64 = goog.require('protobuf.Int64');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of float values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, longValue: !Int64, bufferDecoder:
+ *     !BufferDecoder, error: ?boolean, skip_writer: ?boolean}>}
+ */
+function getSint64Pairs() {
+  const sint64Pairs = [
+    {
+      name: 'zero',
+      longValue: Int64.fromInt(0),
+      bufferDecoder: createBufferDecoder(0x00),
+    },
+    {
+      name: 'one ',
+      longValue: Int64.fromInt(1),
+      bufferDecoder: createBufferDecoder(0x02),
+    },
+    {
+      name: 'minus one',
+      longValue: Int64.fromInt(-1),
+      bufferDecoder: createBufferDecoder(0x01),
+    },
+    {
+      name: 'two',
+      longValue: Int64.fromInt(2),
+      bufferDecoder: createBufferDecoder(0x04),
+    },
+    {
+      name: 'minus two',
+      longValue: Int64.fromInt(-2),
+      bufferDecoder: createBufferDecoder(0x03),
+    },
+    {
+      name: 'min value',
+      longValue: Int64.getMinValue(),
+      bufferDecoder: createBufferDecoder(
+          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01),
+    },
+
+    {
+      name: 'max value',
+      longValue: Int64.getMaxValue(),
+      bufferDecoder: createBufferDecoder(
+          0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01),
+    },
+  ];
+  return [...sint64Pairs];
+}
+
+exports = {getSint64Pairs};

+ 133 - 0
js/experimental/runtime/kernel/storage.js

@@ -0,0 +1,133 @@
+goog.module('protobuf.binary.Storage');
+
+const {checkDefAndNotNull} = goog.require('protobuf.internal.checks');
+
+/**
+ * 85% of the proto fields have a field number <= 24:
+ * https://plx.corp.google.com/scripts2/script_5d._f02af6_0000_23b1_a15f_001a1139dd02
+ *
+ * @type {number}
+ */
+// LINT.IfChange
+const DEFAULT_PIVOT = 24;
+// LINT.ThenChange(//depot/google3/third_party/protobuf/javascript/runtime/kernel/storage_test.js,
+// //depot/google3/net/proto2/contrib/js_proto/internal/kernel_message_generator.cc)
+
+/**
+ * Class storing all the fields of a protobuf message.
+ *
+ * @package
+ * @template FieldType
+ */
+class Storage {
+  /**
+   * @param {number=} pivot
+   */
+  constructor(pivot = DEFAULT_PIVOT) {
+    /**
+     * Fields having a field number no greater than the pivot value are stored
+     * into an array for fast access. A field with field number X is stored into
+     * the array position X - 1.
+     *
+     * @private @const {!Array<!FieldType|undefined>}
+     */
+    this.array_ = new Array(pivot);
+
+    /**
+     * Fields having a field number higher than the pivot value are stored into
+     * the map. We create the map only when it's needed, since even an empty map
+     * takes up a significant amount of memory.
+     *
+     * @private {?Map<number, !FieldType>}
+     */
+    this.map_ = null;
+  }
+
+  /**
+   * Fields having a field number no greater than the pivot value are stored
+   * into an array for fast access. A field with field number X is stored into
+   * the array position X - 1.
+   * @return {number}
+   */
+  getPivot() {
+    return this.array_.length;
+  }
+
+  /**
+   * Sets a field in the specified field number.
+   *
+   * @param {number} fieldNumber
+   * @param {!FieldType} field
+   */
+  set(fieldNumber, field) {
+    if (fieldNumber <= this.getPivot()) {
+      this.array_[fieldNumber - 1] = field;
+    } else {
+      if (this.map_) {
+        this.map_.set(fieldNumber, field);
+      } else {
+        this.map_ = new Map([[fieldNumber, field]]);
+      }
+    }
+  }
+
+  /**
+   * Returns a field at the specified field number.
+   *
+   * @param {number} fieldNumber
+   * @return {!FieldType|undefined}
+   */
+  get(fieldNumber) {
+    if (fieldNumber <= this.getPivot()) {
+      return this.array_[fieldNumber - 1];
+    } else {
+      return this.map_ ? this.map_.get(fieldNumber) : undefined;
+    }
+  }
+
+  /**
+   * Deletes a field from the specified field number.
+   *
+   * @param {number} fieldNumber
+   */
+  delete(fieldNumber) {
+    if (fieldNumber <= this.getPivot()) {
+      delete this.array_[fieldNumber - 1];
+    } else {
+      if (this.map_) {
+        this.map_.delete(fieldNumber);
+      }
+    }
+  }
+
+  /**
+   * Executes the provided function once for each array element.
+   *
+   * @param {function(!FieldType, number): void} callback
+   */
+  forEach(callback) {
+    this.array_.forEach((field, fieldNumber) => {
+      if (field) {
+        callback(checkDefAndNotNull(field), fieldNumber + 1);
+      }
+    });
+    if (this.map_) {
+      this.map_.forEach(callback);
+    }
+  }
+
+  /**
+   * Creates a shallow copy of the storage.
+   *
+   * @return {!Storage}
+   */
+  shallowCopy() {
+    const copy = new Storage(this.getPivot());
+    this.forEach(
+        (field, fieldNumber) =>
+            void copy.set(fieldNumber, field.shallowCopy()));
+    return copy;
+  }
+}
+
+exports = Storage;

+ 165 - 0
js/experimental/runtime/kernel/storage_test.js

@@ -0,0 +1,165 @@
+/**
+ * @fileoverview Tests for storage.js.
+ */
+goog.module('protobuf.binary.StorageTest');
+
+goog.setTestOnly();
+
+const Storage = goog.require('protobuf.binary.Storage');
+const {Field} = goog.require('protobuf.binary.field');
+
+/**
+ * @type {number}
+ */
+const DEFAULT_PIVOT = 24;
+
+const /** !Field */ field1 =
+    Field.fromDecodedValue(/* decodedValue= */ 1, /* encoder= */ () => {});
+const /** !Field */ field2 =
+    Field.fromDecodedValue(/* decodedValue= */ 2, /* encoder= */ () => {});
+const /** !Field */ field3 =
+    Field.fromDecodedValue(/* decodedValue= */ 3, /* encoder= */ () => {});
+const /** !Field */ field4 =
+    Field.fromDecodedValue(/* decodedValue= */ 4, /* encoder= */ () => {});
+
+/**
+ * Returns the number of fields stored.
+ *
+ * @param {!Storage} storage
+ * @return {number}
+ */
+function getStorageSize(storage) {
+  let size = 0;
+  storage.forEach(() => void size++);
+  return size;
+}
+
+describe('Storage', () => {
+  it('sets and gets a field not greater than the pivot', () => {
+    const storage = new Storage(DEFAULT_PIVOT);
+
+    storage.set(1, field1);
+    storage.set(DEFAULT_PIVOT, field2);
+
+    expect(storage.getPivot()).toBe(DEFAULT_PIVOT);
+    expect(storage.get(1)).toBe(field1);
+    expect(storage.get(DEFAULT_PIVOT)).toBe(field2);
+  });
+
+  it('sets and gets a field greater than the pivot', () => {
+    const storage = new Storage(DEFAULT_PIVOT);
+
+    storage.set(DEFAULT_PIVOT + 1, field1);
+    storage.set(100000, field2);
+
+    expect(storage.get(DEFAULT_PIVOT + 1)).toBe(field1);
+    expect(storage.get(100000)).toBe(field2);
+  });
+
+  it('sets and gets a field when pivot is zero', () => {
+    const storage = new Storage(0);
+
+    storage.set(0, field1);
+    storage.set(100000, field2);
+
+    expect(storage.getPivot()).toBe(0);
+    expect(storage.get(0)).toBe(field1);
+    expect(storage.get(100000)).toBe(field2);
+  });
+
+  it('sets and gets a field when pivot is undefined', () => {
+    const storage = new Storage();
+
+    storage.set(0, field1);
+    storage.set(DEFAULT_PIVOT, field2);
+    storage.set(DEFAULT_PIVOT + 1, field3);
+
+    expect(storage.getPivot()).toBe(DEFAULT_PIVOT);
+    expect(storage.get(0)).toBe(field1);
+    expect(storage.get(DEFAULT_PIVOT)).toBe(field2);
+    expect(storage.get(DEFAULT_PIVOT + 1)).toBe(field3);
+  });
+
+  it('returns undefined for nonexistent fields', () => {
+    const storage = new Storage(DEFAULT_PIVOT);
+
+    expect(storage.get(1)).toBeUndefined();
+    expect(storage.get(DEFAULT_PIVOT)).toBeUndefined();
+    expect(storage.get(DEFAULT_PIVOT + 1)).toBeUndefined();
+    expect(storage.get(100000)).toBeUndefined();
+  });
+
+  it('returns undefined for nonexistent fields after map initialization',
+     () => {
+       const storage = new Storage(DEFAULT_PIVOT);
+       storage.set(100001, field1);
+
+       expect(storage.get(1)).toBeUndefined();
+       expect(storage.get(DEFAULT_PIVOT)).toBeUndefined();
+       expect(storage.get(DEFAULT_PIVOT + 1)).toBeUndefined();
+       expect(storage.get(100000)).toBeUndefined();
+     });
+
+  it('deletes a field in delete() when values are only in array', () => {
+    const storage = new Storage(DEFAULT_PIVOT);
+    storage.set(1, field1);
+
+    storage.delete(1);
+
+    expect(storage.get(1)).toBeUndefined();
+  });
+
+  it('deletes a field in delete() when values are both in array and map',
+     () => {
+       const storage = new Storage(DEFAULT_PIVOT);
+       storage.set(DEFAULT_PIVOT, field2);
+       storage.set(DEFAULT_PIVOT + 1, field3);
+
+       storage.delete(DEFAULT_PIVOT);
+       storage.delete(DEFAULT_PIVOT + 1);
+
+       expect(storage.get(DEFAULT_PIVOT)).toBeUndefined();
+       expect(storage.get(DEFAULT_PIVOT + 1)).toBeUndefined();
+     });
+
+  it('deletes a field in delete() when values are only in map', () => {
+    const storage = new Storage(DEFAULT_PIVOT);
+    storage.set(100000, field4);
+
+    storage.delete(100000);
+
+    expect(storage.get(100000)).toBeUndefined();
+  });
+
+  it('loops over all the elements in forEach()', () => {
+    const storage = new Storage(DEFAULT_PIVOT);
+    storage.set(1, field1);
+    storage.set(DEFAULT_PIVOT, field2);
+    storage.set(DEFAULT_PIVOT + 1, field3);
+    storage.set(100000, field4);
+
+    const fields = new Map();
+    storage.forEach(
+        (field, fieldNumber) => void fields.set(fieldNumber, field));
+
+    expect(fields.size).toEqual(4);
+    expect(fields.get(1)).toBe(field1);
+    expect(storage.get(DEFAULT_PIVOT)).toBe(field2);
+    expect(storage.get(DEFAULT_PIVOT + 1)).toBe(field3);
+    expect(fields.get(100000)).toBe(field4);
+  });
+
+  it('creates a shallow copy of the storage in shallowCopy()', () => {
+    const storage = new Storage(DEFAULT_PIVOT);
+    storage.set(1, field1);
+    storage.set(100000, field2);
+
+    const copy = storage.shallowCopy();
+
+    expect(getStorageSize(copy)).toEqual(2);
+    expect(copy.get(1)).not.toBe(field1);
+    expect(copy.get(1).getDecodedValue()).toEqual(1);
+    expect(copy.get(100000)).not.toBe(field1);
+    expect(copy.get(100000).getDecodedValue()).toEqual(2);
+  });
+});

+ 116 - 0
js/experimental/runtime/kernel/textencoding.js

@@ -0,0 +1,116 @@
+/**
+ * @fileoverview A UTF8 decoder.
+ */
+goog.module('protobuf.binary.textencoding');
+
+const {checkElementIndex} = goog.require('protobuf.internal.checks');
+
+/**
+ * Combines an array of codePoints into a string.
+ * @param {!Array<number>} codePoints
+ * @return {string}
+ */
+function codePointsToString(codePoints) {
+  // Performance: http://jsperf.com/string-fromcharcode-test/13
+  let s = '', i = 0;
+  const length = codePoints.length;
+  const BATCH_SIZE = 10000;
+  while (i < length) {
+    const end = Math.min(i + BATCH_SIZE, length);
+    s += String.fromCharCode.apply(null, codePoints.slice(i, end));
+    i = end;
+  }
+  return s;
+}
+
+/**
+ * Decodes raw bytes into a string.
+ * Supports codepoints from U+0000 up to U+10FFFF.
+ * (http://en.wikipedia.org/wiki/UTF-8).
+ * @param {!DataView} bytes
+ * @return {string}
+ */
+function decode(bytes) {
+  let cursor = 0;
+  const codePoints = [];
+
+  while (cursor < bytes.byteLength) {
+    const c = bytes.getUint8(cursor++);
+    if (c < 0x80) {  // Regular 7-bit ASCII.
+      codePoints.push(c);
+    } else if (c < 0xC0) {
+      // UTF-8 continuation mark. We are out of sync. This
+      // might happen if we attempted to read a character
+      // with more than four bytes.
+      continue;
+    } else if (c < 0xE0) {  // UTF-8 with two bytes.
+      checkElementIndex(cursor, bytes.byteLength);
+      const c2 = bytes.getUint8(cursor++);
+      codePoints.push(((c & 0x1F) << 6) | (c2 & 0x3F));
+    } else if (c < 0xF0) {  // UTF-8 with three bytes.
+      checkElementIndex(cursor + 1, bytes.byteLength);
+      const c2 = bytes.getUint8(cursor++);
+      const c3 = bytes.getUint8(cursor++);
+      codePoints.push(((c & 0xF) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F));
+    } else if (c < 0xF8) {  // UTF-8 with 4 bytes.
+      checkElementIndex(cursor + 2, bytes.byteLength);
+      const c2 = bytes.getUint8(cursor++);
+      const c3 = bytes.getUint8(cursor++);
+      const c4 = bytes.getUint8(cursor++);
+      // Characters written on 4 bytes have 21 bits for a codepoint.
+      // We can't fit that on 16bit characters, so we use surrogates.
+      let codepoint = ((c & 0x07) << 18) | ((c2 & 0x3F) << 12) |
+          ((c3 & 0x3F) << 6) | (c4 & 0x3F);
+      // Surrogates formula from wikipedia.
+      // 1. Subtract 0x10000 from codepoint
+      codepoint -= 0x10000;
+      // 2. Split this into the high 10-bit value and the low 10-bit value
+      // 3. Add 0xD800 to the high value to form the high surrogate
+      // 4. Add 0xDC00 to the low value to form the low surrogate:
+      const low = (codepoint & 0x3FF) + 0xDC00;
+      const high = ((codepoint >> 10) & 0x3FF) + 0xD800;
+      codePoints.push(high, low);
+    }
+  }
+  return codePointsToString(codePoints);
+}
+
+/**
+ * Writes a UTF16 JavaScript string to the buffer encoded as UTF8.
+ * @param {string} value The string to write.
+ * @return {!Uint8Array} An array containing the encoded bytes.
+ */
+function encode(value) {
+  const buffer = [];
+
+  for (let i = 0; i < value.length; i++) {
+    const c1 = value.charCodeAt(i);
+
+    if (c1 < 0x80) {
+      buffer.push(c1);
+    } else if (c1 < 0x800) {
+      buffer.push((c1 >> 6) | 0xC0);
+      buffer.push((c1 & 0x3F) | 0x80);
+    } else if (c1 < 0xD800 || c1 >= 0xE000) {
+      buffer.push((c1 >> 12) | 0xE0);
+      buffer.push(((c1 >> 6) & 0x3F) | 0x80);
+      buffer.push((c1 & 0x3F) | 0x80);
+    } else {
+      // surrogate pair
+      i++;
+      checkElementIndex(i, value.length);
+      const c2 = value.charCodeAt(i);
+      const paired = 0x10000 + (((c1 & 0x3FF) << 10) | (c2 & 0x3FF));
+      buffer.push((paired >> 18) | 0xF0);
+      buffer.push(((paired >> 12) & 0x3F) | 0x80);
+      buffer.push(((paired >> 6) & 0x3F) | 0x80);
+      buffer.push((paired & 0x3F) | 0x80);
+    }
+  }
+  return new Uint8Array(buffer);
+}
+
+exports = {
+  decode,
+  encode,
+};

+ 113 - 0
js/experimental/runtime/kernel/textencoding_test.js

@@ -0,0 +1,113 @@
+/**
+ * @fileoverview Tests for textdecoder.js.
+ */
+goog.module('protobuf.binary.TextDecoderTest');
+
+goog.setTestOnly();
+
+const {decode, encode} = goog.require('protobuf.binary.textencoding');
+
+describe('Decode does', () => {
+  it('return empty string for empty array', () => {
+    expect(decode(new DataView(new ArrayBuffer(0)))).toEqual('');
+  });
+
+  it('throw on null being passed', () => {
+    expect(() => decode(/** @type {!DataView} */ (/** @type {*} */ (null))))
+        .toThrow();
+  });
+});
+
+describe('Encode does', () => {
+  it('return empty array for empty string', () => {
+    expect(encode('')).toEqual(new Uint8Array(0));
+  });
+
+  it('throw on null being passed', () => {
+    expect(() => encode(/** @type {string} */ (/** @type {*} */ (null))))
+        .toThrow();
+  });
+});
+
+/** @const {!TextEncoder} */
+const textEncoder = new TextEncoder('utf-8');
+
+/**
+ * A Pair of string and Uint8Array representing the same data.
+ * Each pair has the string value and its utf-8 bytes.
+ */
+class Pair {
+  /**
+   * Constructs a pair from a given string.
+   * @param {string} s
+   * @return {!Pair}
+   */
+  static fromString(s) {
+    return new Pair(s, textEncoder.encode(s).buffer);
+  }
+  /**
+   * Constructs a pair from a given charCode.
+   * @param {number} charCode
+   * @return {!Pair}
+   */
+  static fromCharCode(charCode) {
+    return Pair.fromString(String.fromCharCode(charCode));
+  }
+
+  /**
+   * @param {string} stringValue
+   * @param {!ArrayBuffer} bytes
+   * @private
+   */
+  constructor(stringValue, bytes) {
+    /** @const @private {string} */
+    this.stringValue_ = stringValue;
+    /** @const @private {!ArrayBuffer} */
+    this.bytes_ = bytes;
+  }
+
+  /** Ensures that a given pair encodes and decodes round trip*/
+  expectPairToMatch() {
+    expect(decode(new DataView(this.bytes_))).toEqual(this.stringValue_);
+    expect(encode(this.stringValue_)).toEqual(new Uint8Array(this.bytes_));
+  }
+}
+
+describe('textencoding does', () => {
+  it('works for empty string', () => {
+    Pair.fromString('').expectPairToMatch();
+  });
+
+  it('decode and encode random strings', () => {
+    // 1 byte strings
+    Pair.fromString('hello').expectPairToMatch();
+    Pair.fromString('HELLO1!');
+
+    // 2 byte String
+    Pair.fromString('©').expectPairToMatch();
+
+    // 3 byte string
+    Pair.fromString('❄').expectPairToMatch();
+
+    // 4 byte string
+    Pair.fromString('😁').expectPairToMatch();
+  });
+
+  it('decode and encode 1 byte strings', () => {
+    for (let i = 0; i < 0x80; i++) {
+      Pair.fromCharCode(i).expectPairToMatch();
+    }
+  });
+
+  it('decode and encode 2 byte strings', () => {
+    for (let i = 0xC0; i < 0x7FF; i++) {
+      Pair.fromCharCode(i).expectPairToMatch();
+    }
+  });
+
+  it('decode and encode 3 byte strings', () => {
+    for (let i = 0x7FF; i < 0x8FFF; i++) {
+      Pair.fromCharCode(i).expectPairToMatch();
+    }
+  });
+});

+ 116 - 0
js/experimental/runtime/kernel/typed_arrays.js

@@ -0,0 +1,116 @@
+/**
+ * @fileoverview Helper methods for typed arrays.
+ */
+goog.module('protobuf.binary.typedArrays');
+
+const {assert} = goog.require('goog.asserts');
+
+/**
+ * @param {!ArrayBuffer} buffer1
+ * @param {!ArrayBuffer} buffer2
+ * @return {boolean}
+ */
+function arrayBufferEqual(buffer1, buffer2) {
+  if (!buffer1 || !buffer2) {
+    throw new Error('Buffer shouldn\'t be empty');
+  }
+
+  const array1 = new Uint8Array(buffer1);
+  const array2 = new Uint8Array(buffer2);
+
+  return uint8ArrayEqual(array1, array2);
+}
+
+/**
+ * @param {!Uint8Array} array1
+ * @param {!Uint8Array} array2
+ * @return {boolean}
+ */
+function uint8ArrayEqual(array1, array2) {
+  if (array1 === array2) {
+    return true;
+  }
+
+  if (array1.byteLength !== array2.byteLength) {
+    return false;
+  }
+
+  for (let i = 0; i < array1.byteLength; i++) {
+    if (array1[i] !== array2[i]) {
+      return false;
+    }
+  }
+  return true;
+}
+
+/**
+ * ArrayBuffer.prototype.slice, but fallback to manual copy if missing.
+ * @param {!ArrayBuffer} buffer
+ * @param {number} start
+ * @param {number=} end
+ * @return {!ArrayBuffer} New array buffer with given the contents of `buffer`.
+ */
+function arrayBufferSlice(buffer, start, end = undefined) {
+  // The fallback isn't fully compatible with ArrayBuffer.slice, enforce
+  // strict requirements on start/end.
+  // Spec:
+  // https://www.ecma-international.org/ecma-262/6.0/#sec-arraybuffer.prototype.slice
+  assert(start >= 0);
+  assert(end === undefined || (end >= 0 && end <= buffer.byteLength));
+  assert((end === undefined ? buffer.byteLength : end) >= start);
+  if (buffer.slice) {
+    const slicedBuffer = buffer.slice(start, end);
+    // The ArrayBuffer.prototype.slice function was flawed before iOS 12.2. This
+    // causes 0-length results when passing undefined or a number greater
+    // than 32 bits for the optional end value. In these cases, we fall back to
+    // using our own slice implementation.
+    // More details: https://bugs.webkit.org/show_bug.cgi?id=185127
+    if (slicedBuffer.byteLength !== 0 || (start === end)) {
+      return slicedBuffer;
+    }
+  }
+  const realEnd = end == null ? buffer.byteLength : end;
+  const length = realEnd - start;
+  assert(length >= 0);
+  const view = new Uint8Array(buffer, start, length);
+  // A TypedArray constructed from another Typed array copies the data.
+  const clone = new Uint8Array(view);
+
+  return clone.buffer;
+}
+
+/**
+ * Returns a new Uint8Array with the size and contents of the given
+ * ArrayBufferView. ArrayBufferView is an interface implemented by DataView,
+ * Uint8Array and all typed arrays.
+ * @param {!ArrayBufferView} view
+ * @return {!Uint8Array}
+ */
+function cloneArrayBufferView(view) {
+  return new Uint8Array(arrayBufferSlice(
+      view.buffer, view.byteOffset, view.byteOffset + view.byteLength));
+}
+
+/**
+ * Returns a 32 bit number for the corresponding Uint8Array.
+ * @param {!Uint8Array} array
+ * @return {number}
+ */
+function hashUint8Array(array) {
+  const prime = 31;
+  let result = 17;
+
+  for (let i = 0; i < array.length; i++) {
+    // '| 0' ensures signed 32 bits
+    result = (result * prime + array[i]) | 0;
+  }
+  return result;
+}
+
+exports = {
+  arrayBufferEqual,
+  uint8ArrayEqual,
+  arrayBufferSlice,
+  cloneArrayBufferView,
+  hashUint8Array,
+};

+ 191 - 0
js/experimental/runtime/kernel/typed_arrays_test.js

@@ -0,0 +1,191 @@
+/**
+ * @fileoverview Tests for typed_arrays.js.
+ */
+goog.module('protobuf.binary.typedArraysTest');
+
+const {arrayBufferEqual, arrayBufferSlice, cloneArrayBufferView, hashUint8Array, uint8ArrayEqual} = goog.require('protobuf.binary.typedArrays');
+
+describe('arrayBufferEqual', () => {
+  it('returns true for empty buffers', () => {
+    const buffer1 = new ArrayBuffer(0);
+    const buffer2 = new ArrayBuffer(0);
+    expect(arrayBufferEqual(buffer1, buffer2)).toBe(true);
+  });
+
+  it('throws for first null buffers', () => {
+    const buffer = new ArrayBuffer(0);
+    expect(
+        () => arrayBufferEqual(
+            /** @type {!ArrayBuffer} */ (/** @type {*} */ (null)), buffer))
+        .toThrow();
+  });
+
+  it('throws for second null buffers', () => {
+    const buffer = new ArrayBuffer(0);
+    expect(
+        () => arrayBufferEqual(
+            buffer, /** @type {!ArrayBuffer} */ (/** @type {*} */ (null))))
+        .toThrow();
+  });
+
+  it('returns true for arrays with same values', () => {
+    const array1 = new Uint8Array(4);
+    array1[0] = 1;
+    const array2 = new Uint8Array(4);
+    array2[0] = 1;
+    expect(arrayBufferEqual(array1.buffer, array2.buffer)).toBe(true);
+  });
+
+  it('returns false for arrays with different values', () => {
+    const array1 = new Uint8Array(4);
+    array1[0] = 1;
+    const array2 = new Uint8Array(4);
+    array2[0] = 2;
+    expect(arrayBufferEqual(array1.buffer, array2.buffer)).toBe(false);
+  });
+
+  it('returns true same instance', () => {
+    const array1 = new Uint8Array(4);
+    array1[0] = 1;
+    expect(arrayBufferEqual(array1.buffer, array1.buffer)).toBe(true);
+  });
+});
+
+describe('uint8ArrayEqual', () => {
+  it('returns true for empty arrays', () => {
+    const array1 = new Uint8Array(0);
+    const array2 = new Uint8Array(0);
+    expect(uint8ArrayEqual(array1, array2)).toBe(true);
+  });
+
+  it('throws for first Uint8Array array', () => {
+    const array = new Uint8Array(0);
+    expect(
+        () => uint8ArrayEqual(
+            /** @type {!Uint8Array} */ (/** @type {*} */ (null)), array))
+        .toThrow();
+  });
+
+  it('throws for second null array', () => {
+    const array = new Uint8Array(0);
+    expect(
+        () => uint8ArrayEqual(
+            array, /** @type {!Uint8Array} */ (/** @type {*} */ (null))))
+        .toThrow();
+  });
+
+  it('returns true for arrays with same values', () => {
+    const buffer1 = new Uint8Array([0, 1, 2, 3]).buffer;
+    const buffer2 = new Uint8Array([1, 2, 3, 4]).buffer;
+    const array1 = new Uint8Array(buffer1, 1, 3);
+    const array2 = new Uint8Array(buffer2, 0, 3);
+    expect(uint8ArrayEqual(array1, array2)).toBe(true);
+  });
+
+  it('returns false for arrays with different values', () => {
+    const array1 = new Uint8Array(4);
+    array1[0] = 1;
+    const array2 = new Uint8Array(4);
+    array2[0] = 2;
+    expect(uint8ArrayEqual(array1, array2)).toBe(false);
+  });
+
+  it('returns true same instance', () => {
+    const array1 = new Uint8Array(4);
+    array1[0] = 1;
+    expect(uint8ArrayEqual(array1, array1)).toBe(true);
+  });
+});
+
+describe('arrayBufferSlice', () => {
+  it('Returns a new instance.', () => {
+    const buffer1 = new ArrayBuffer(0);
+    const buffer2 = arrayBufferSlice(buffer1, 0);
+    expect(buffer2).not.toBe(buffer1);
+    expect(arrayBufferEqual(buffer1, buffer2)).toBe(true);
+  });
+
+  it('Copies data with positive start/end.', () => {
+    const buffer1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).buffer;
+    expect(buffer1.byteLength).toEqual(10);
+
+    const buffer2 = arrayBufferSlice(buffer1, 2, 6);
+    expect(buffer2.byteLength).toEqual(4);
+    expect(buffer2).not.toBe(buffer1);
+    const expected = new Uint8Array([2, 3, 4, 5]).buffer;
+    expect(arrayBufferEqual(expected, buffer2)).toBe(true);
+  });
+
+  it('Copies all data without end.', () => {
+    const buffer1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).buffer;
+    expect(buffer1.byteLength).toEqual(10);
+
+    const buffer2 = arrayBufferSlice(buffer1, 0);
+    expect(buffer2.byteLength).toEqual(10);
+    expect(arrayBufferEqual(buffer1, buffer2)).toBe(true);
+  });
+
+  if (goog.DEBUG) {
+    it('Fails with negative end.', () => {
+      const buffer1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).buffer;
+      expect(() => void arrayBufferSlice(buffer1, 2, -1)).toThrow();
+    });
+
+    it('Fails with negative start.', () => {
+      const buffer1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).buffer;
+      expect(() => void arrayBufferSlice(buffer1, 2, -1)).toThrow();
+    });
+
+    it('Fails when start > end.', () => {
+      const buffer1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).buffer;
+      expect(() => void arrayBufferSlice(buffer1, 2, 1)).toThrow();
+    });
+  }
+});
+
+describe('cloneArrayBufferView', () => {
+  it('Returns a new instance.', () => {
+    const array1 = new Uint8Array(0);
+    const array2 = cloneArrayBufferView(new Uint8Array(array1));
+    expect(array2).not.toBe(array1);
+    expect(uint8ArrayEqual(array1, array2)).toBe(true);
+  });
+
+  it('Returns an array of the exact size.', () => {
+    const array1 =
+        new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).subarray(2, 5);
+    expect(array1.length).toEqual(3);
+    expect(array1.buffer.byteLength).toEqual(10);
+    const array2 = cloneArrayBufferView(array1);
+    expect(array2.byteLength).toEqual(3);
+    expect(array2).toEqual(new Uint8Array([2, 3, 4]));
+  });
+});
+
+describe('hashUint8Array', () => {
+  it('returns same hashcode for empty Uint8Arrays', () => {
+    const array1 = new Uint8Array(0);
+    const array2 = new Uint8Array(0);
+    expect(hashUint8Array(array1)).toBe(hashUint8Array(array2));
+  });
+
+  it('returns same hashcode for Uint8Arrays with same values', () => {
+    const array1 = new Uint8Array(4);
+    array1[0] = 1;
+    const array2 = new Uint8Array(4);
+    array2[0] = 1;
+    expect(hashUint8Array(array1)).toBe(hashUint8Array(array2));
+  });
+
+  it('returns different hashcode for Uint8Arrays with different values', () => {
+    // This test might fail in the future if the hashing algorithm is updated
+    // and we end up with a collision here.
+    // We still need this test to make sure that we are not just returning
+    // the same number for all buffers.
+    const array1 = new Uint8Array(4);
+    array1[0] = 1;
+    const array2 = new Uint8Array(4);
+    array2[0] = 2;
+    expect(hashUint8Array(array1)).not.toBe(hashUint8Array(array2));
+  });
+});

+ 61 - 0
js/experimental/runtime/kernel/uint32_test_pairs.js

@@ -0,0 +1,61 @@
+/**
+ * @fileoverview Test data for int32 encoding and decoding.
+ */
+goog.module('protobuf.binary.uint32TestPairs');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
+
+/**
+ * An array of Pairs of float values and their bit representation.
+ * This is used to test encoding and decoding from/to the protobuf wire format.
+ * @return {!Array<{name: string, intValue:number, bufferDecoder:
+ *     !BufferDecoder, error: ?boolean, skip_writer: ?boolean}>}
+ */
+function getUint32Pairs() {
+  const uint32Pairs = [
+    {
+      name: 'zero',
+      intValue: 0,
+      bufferDecoder: createBufferDecoder(0x00),
+    },
+    {
+      name: 'one ',
+      intValue: 1,
+      bufferDecoder: createBufferDecoder(0x01),
+    },
+    {
+      name: 'max signed int 2^31 - 1',
+      intValue: Math.pow(2, 31) - 1,
+      bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0x07),
+    },
+    {
+      name: 'max unsigned int 2^32 - 1',
+      intValue: Math.pow(2, 32) - 1,
+      bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0x0F),
+    },
+    {
+      name: 'truncates more than 32 bits',
+      intValue: Math.pow(2, 32) - 1,
+      bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01),
+      skip_writer: true,
+    },
+    {
+      name: 'truncates more than 32 bits (bit 33 set)',
+      intValue: Math.pow(2, 28) - 1,
+      bufferDecoder: createBufferDecoder(0xFF, 0xFF, 0xFF, 0xFF, 0x10),
+      skip_writer: true,
+    },
+    {
+      name: 'errors out for 11 bytes',
+      intValue: Math.pow(2, 32) - 1,
+      bufferDecoder: createBufferDecoder(
+          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF),
+      error: true,
+      skip_writer: true,
+    },
+  ];
+  return [...uint32Pairs];
+}
+
+exports = {getUint32Pairs};

+ 28 - 0
js/experimental/runtime/kernel/uint8arrays.js

@@ -0,0 +1,28 @@
+/**
+ * @fileoverview Helper methods for Uint8Arrays.
+ */
+goog.module('protobuf.binary.uint8arrays');
+
+/**
+ * Combines multiple bytes arrays (either Uint8Array or number array whose
+ * values are bytes) into a single Uint8Array.
+ * @param {!Array<!Uint8Array>|!Array<!Array<number>>} arrays
+ * @return {!Uint8Array}
+ */
+function concatenateByteArrays(arrays) {
+  let totalLength = 0;
+  for (const array of arrays) {
+    totalLength += array.length;
+  }
+  const result = new Uint8Array(totalLength);
+  let offset = 0;
+  for (const array of arrays) {
+    result.set(array, offset);
+    offset += array.length;
+  }
+  return result;
+}
+
+exports = {
+  concatenateByteArrays,
+};

+ 47 - 0
js/experimental/runtime/kernel/uint8arrays_test.js

@@ -0,0 +1,47 @@
+/**
+ * @fileoverview Tests for uint8arrays.js.
+ */
+goog.module('protobuf.binary.Uint8ArraysTest');
+
+goog.setTestOnly();
+
+const {concatenateByteArrays} = goog.require('protobuf.binary.uint8arrays');
+
+describe('concatenateByteArrays does', () => {
+  it('concatenate empty array', () => {
+    const byteArrays = [];
+    expect(concatenateByteArrays(byteArrays)).toEqual(new Uint8Array(0));
+  });
+
+  it('concatenate Uint8Arrays', () => {
+    const byteArrays = [new Uint8Array([0x01]), new Uint8Array([0x02])];
+    expect(concatenateByteArrays(byteArrays)).toEqual(new Uint8Array([
+      0x01, 0x02
+    ]));
+  });
+
+  it('concatenate array of bytes', () => {
+    const byteArrays = [[0x01], [0x02]];
+    expect(concatenateByteArrays(byteArrays)).toEqual(new Uint8Array([
+      0x01, 0x02
+    ]));
+  });
+
+  it('concatenate array of non-bytes', () => {
+    // Note in unchecked mode we produce invalid output for invalid inputs.
+    // This test just documents our behavior in those cases.
+    // These values might change at any point and are not considered
+    // what the implementation should be doing here.
+    const byteArrays = [[40.0], [256]];
+    expect(concatenateByteArrays(byteArrays)).toEqual(new Uint8Array([
+      0x28, 0x00
+    ]));
+  });
+
+  it('throw for null array', () => {
+    expect(
+        () => concatenateByteArrays(
+            /** @type {!Array<!Uint8Array>} */ (/** @type {*} */ (null))))
+        .toThrow();
+  });
+});

+ 17 - 0
js/experimental/runtime/kernel/wire_type.js

@@ -0,0 +1,17 @@
+goog.module('protobuf.binary.WireType');
+
+/**
+ * Wire-format type codes, taken from proto2/public/wire_format_lite.h.
+ * @enum {number}
+ */
+const WireType = {
+  VARINT: 0,
+  FIXED64: 1,
+  DELIMITED: 2,
+  START_GROUP: 3,
+  END_GROUP: 4,
+  FIXED32: 5,
+  INVALID: 6
+};
+
+exports = WireType;

+ 772 - 0
js/experimental/runtime/kernel/writer.js

@@ -0,0 +1,772 @@
+/**
+ * @fileoverview Implements Writer for writing data as the binary wire format
+ * bytes array.
+ */
+goog.module('protobuf.binary.Writer');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const ByteString = goog.require('protobuf.ByteString');
+const Int64 = goog.require('protobuf.Int64');
+const WireType = goog.require('protobuf.binary.WireType');
+const {POLYFILL_TEXT_ENCODING, checkFieldNumber, checkTypeUnsignedInt32, checkWireType} = goog.require('protobuf.internal.checks');
+const {concatenateByteArrays} = goog.require('protobuf.binary.uint8arrays');
+const {encode} = goog.require('protobuf.binary.textencoding');
+
+/**
+ * Returns a valid utf-8 encoder function based on TextEncoder if available or
+ * a polyfill.
+ * Some of the environments we run in do not have TextEncoder defined.
+ * TextEncoder is faster than our polyfill so we prefer it over the polyfill.
+ * @return {function(string):!Uint8Array}
+ */
+function getEncoderFunction() {
+  if (goog.global['TextEncoder']) {
+    const textEncoder = new goog.global['TextEncoder']('utf-8');
+    return s => s.length === 0 ? new Uint8Array(0) : textEncoder.encode(s);
+  }
+  if (POLYFILL_TEXT_ENCODING) {
+    return encode;
+  } else {
+    throw new Error(
+        'TextEncoder is missing. ' +
+        'Enable protobuf.defines.POLYFILL_TEXT_ENCODING');
+  }
+}
+
+/** @const {function(string): !Uint8Array} */
+const encoderFunction = getEncoderFunction();
+
+/**
+ * Writer provides methods for encoding all protobuf supported type into a
+ * binary format bytes array.
+ * Check https://developers.google.com/protocol-buffers/docs/encoding for binary
+ * format definition.
+ * @final
+ * @package
+ */
+class Writer {
+  constructor() {
+    /**
+     * Blocks of data that needs to be serialized. After writing all the data,
+     * the blocks are concatenated into a single Uint8Array.
+     * @private {!Array<!Uint8Array>}
+     */
+    this.blocks_ = [];
+
+    /**
+     * A buffer for writing varint data (tag number + field number for each
+     * field, int32, uint32 etc.). Before writing a non-varint data block
+     * (string, fixed32 etc.), the buffer is appended to the block array as a
+     * new block, and a new buffer is started.
+     *
+     * We could've written each varint as a new block instead of writing
+     * multiple varints in this buffer. But this will increase the number of
+     * blocks, and concatenating many small blocks is slower than concatenating
+     * few large blocks.
+     *
+     * TODO: Experiment with writing data in a fixed-length
+     * Uint8Array instead of using a growing buffer.
+     *
+     * @private {!Array<number>}
+     */
+    this.currentBuffer_ = [];
+  }
+
+  /**
+   * Converts the encoded data into a Uint8Array.
+   * The writer is also reset.
+   * @return {!ArrayBuffer}
+   */
+  getAndResetResultBuffer() {
+    this.closeAndStartNewBuffer_();
+    const result = concatenateByteArrays(this.blocks_);
+    this.blocks_ = [];
+    return result.buffer;
+  }
+
+  /**
+   * Encodes a (field number, wire type) tuple into a wire-format field header.
+   * @param {number} fieldNumber
+   * @param {!WireType} wireType
+   */
+  writeTag(fieldNumber, wireType) {
+    checkFieldNumber(fieldNumber);
+    checkWireType(wireType);
+    const tag = fieldNumber << 3 | wireType;
+    this.writeUnsignedVarint32_(tag);
+  }
+
+  /**
+   * Appends the current buffer into the blocks array and starts a new buffer.
+   * @private
+   */
+  closeAndStartNewBuffer_() {
+    this.blocks_.push(new Uint8Array(this.currentBuffer_));
+    this.currentBuffer_ = [];
+  }
+
+  /**
+   * Encodes a 32-bit integer into its wire-format varint representation and
+   * stores it in the buffer.
+   * @param {number} value
+   * @private
+   */
+  writeUnsignedVarint32_(value) {
+    checkTypeUnsignedInt32(value);
+    while (value > 0x7f) {
+      this.currentBuffer_.push((value & 0x7f) | 0x80);
+      value = value >>> 7;
+    }
+    this.currentBuffer_.push(value);
+  }
+
+  /****************************************************************************
+   *                        OPTIONAL METHODS
+   ****************************************************************************/
+
+  /**
+   * Writes a boolean value field to the buffer as a varint.
+   * @param {boolean} value
+   * @private
+   */
+  writeBoolValue_(value) {
+    this.currentBuffer_.push(value ? 1 : 0);
+  }
+
+  /**
+   * Writes a boolean value field to the buffer as a varint.
+   * @param {number} fieldNumber
+   * @param {boolean} value
+   */
+  writeBool(fieldNumber, value) {
+    this.writeTag(fieldNumber, WireType.VARINT);
+    this.writeBoolValue_(value);
+  }
+
+  /**
+   * Writes a bytes value field to the buffer as a length delimited field.
+   * @param {number} fieldNumber
+   * @param {!ByteString} value
+   */
+  writeBytes(fieldNumber, value) {
+    this.writeTag(fieldNumber, WireType.DELIMITED);
+    const buffer = value.toArrayBuffer();
+    this.writeUnsignedVarint32_(buffer.byteLength);
+    this.writeRaw_(buffer);
+  }
+
+  /**
+   * Writes a double value field to the buffer without tag.
+   * @param {number} value
+   * @private
+   */
+  writeDoubleValue_(value) {
+    const buffer = new ArrayBuffer(8);
+    const view = new DataView(buffer);
+    view.setFloat64(0, value, true);
+    this.writeRaw_(buffer);
+  }
+
+  /**
+   * Writes a double value field to the buffer.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  writeDouble(fieldNumber, value) {
+    this.writeTag(fieldNumber, WireType.FIXED64);
+    this.writeDoubleValue_(value);
+  }
+
+  /**
+   * Writes a fixed32 value field to the buffer without tag.
+   * @param {number} value
+   * @private
+   */
+  writeFixed32Value_(value) {
+    const buffer = new ArrayBuffer(4);
+    const view = new DataView(buffer);
+    view.setUint32(0, value, true);
+    this.writeRaw_(buffer);
+  }
+
+  /**
+   * Writes a fixed32 value field to the buffer.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  writeFixed32(fieldNumber, value) {
+    this.writeTag(fieldNumber, WireType.FIXED32);
+    this.writeFixed32Value_(value);
+  }
+
+  /**
+   * Writes a float value field to the buffer without tag.
+   * @param {number} value
+   * @private
+   */
+  writeFloatValue_(value) {
+    const buffer = new ArrayBuffer(4);
+    const view = new DataView(buffer);
+    view.setFloat32(0, value, true);
+    this.writeRaw_(buffer);
+  }
+
+  /**
+   * Writes a float value field to the buffer.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  writeFloat(fieldNumber, value) {
+    this.writeTag(fieldNumber, WireType.FIXED32);
+    this.writeFloatValue_(value);
+  }
+
+  /**
+   * Writes a int32 value field to the buffer as a varint without tag.
+   * @param {number} value
+   * @private
+   */
+  writeInt32Value_(value) {
+    if (value >= 0) {
+      this.writeVarint64_(0, value);
+    } else {
+      this.writeVarint64_(0xFFFFFFFF, value);
+    }
+  }
+
+  /**
+   * Writes a int32 value field to the buffer as a varint.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  writeInt32(fieldNumber, value) {
+    this.writeTag(fieldNumber, WireType.VARINT);
+    this.writeInt32Value_(value);
+  }
+
+  /**
+   * Writes a int64 value field to the buffer as a varint.
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  writeInt64(fieldNumber, value) {
+    this.writeTag(fieldNumber, WireType.VARINT);
+    this.writeVarint64_(value.getHighBits(), value.getLowBits());
+  }
+
+  /**
+   * Writes a sfixed32 value field to the buffer.
+   * @param {number} value
+   * @private
+   */
+  writeSfixed32Value_(value) {
+    const buffer = new ArrayBuffer(4);
+    const view = new DataView(buffer);
+    view.setInt32(0, value, true);
+    this.writeRaw_(buffer);
+  }
+
+  /**
+   * Writes a sfixed32 value field to the buffer.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  writeSfixed32(fieldNumber, value) {
+    this.writeTag(fieldNumber, WireType.FIXED32);
+    this.writeSfixed32Value_(value);
+  }
+
+  /**
+   * Writes a sfixed64 value field to the buffer without tag.
+   * @param {!Int64} value
+   * @private
+   */
+  writeSfixed64Value_(value) {
+    const buffer = new ArrayBuffer(8);
+    const view = new DataView(buffer);
+    view.setInt32(0, value.getLowBits(), true);
+    view.setInt32(4, value.getHighBits(), true);
+    this.writeRaw_(buffer);
+  }
+
+  /**
+   * Writes a sfixed64 value field to the buffer.
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  writeSfixed64(fieldNumber, value) {
+    this.writeTag(fieldNumber, WireType.FIXED64);
+    this.writeSfixed64Value_(value);
+  }
+
+  /**
+   * Writes a uint32 value field to the buffer as a varint without tag.
+   * @param {number} value
+   * @private
+   */
+  writeUint32Value_(value) {
+    this.writeVarint64_(0, value);
+  }
+
+  /**
+   * Writes a uint32 value field to the buffer as a varint.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  writeUint32(fieldNumber, value) {
+    this.writeTag(fieldNumber, WireType.VARINT);
+    this.writeUint32Value_(value);
+  }
+
+  /**
+   * Writes the bits of a 64 bit number to the buffer as a varint.
+   * @param {number} highBits
+   * @param {number} lowBits
+   * @private
+   */
+  writeVarint64_(highBits, lowBits) {
+    for (let i = 0; i < 28; i = i + 7) {
+      const shift = lowBits >>> i;
+      const hasNext = !((shift >>> 7) === 0 && highBits === 0);
+      const byte = (hasNext ? shift | 0x80 : shift) & 0xFF;
+      this.currentBuffer_.push(byte);
+      if (!hasNext) {
+        return;
+      }
+    }
+
+    const splitBits = ((lowBits >>> 28) & 0x0F) | ((highBits & 0x07) << 4);
+    const hasMoreBits = !((highBits >> 3) === 0);
+    this.currentBuffer_.push(
+        (hasMoreBits ? splitBits | 0x80 : splitBits) & 0xFF);
+
+    if (!hasMoreBits) {
+      return;
+    }
+
+    for (let i = 3; i < 31; i = i + 7) {
+      const shift = highBits >>> i;
+      const hasNext = !((shift >>> 7) === 0);
+      const byte = (hasNext ? shift | 0x80 : shift) & 0xFF;
+      this.currentBuffer_.push(byte);
+      if (!hasNext) {
+        return;
+      }
+    }
+
+    this.currentBuffer_.push((highBits >>> 31) & 0x01);
+  }
+
+  /**
+   * Writes a sint32 value field to the buffer as a varint without tag.
+   * @param {number} value
+   * @private
+   */
+  writeSint32Value_(value) {
+    value = (value << 1) ^ (value >> 31);
+    this.writeVarint64_(0, value);
+  }
+
+  /**
+   * Writes a sint32 value field to the buffer as a varint.
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  writeSint32(fieldNumber, value) {
+    this.writeTag(fieldNumber, WireType.VARINT);
+    this.writeSint32Value_(value);
+  }
+
+  /**
+   * Writes a sint64 value field to the buffer as a varint without tag.
+   * @param {!Int64} value
+   * @private
+   */
+  writeSint64Value_(value) {
+    const highBits = value.getHighBits();
+    const lowBits = value.getLowBits();
+
+    const sign = highBits >> 31;
+    const encodedLowBits = (lowBits << 1) ^ sign;
+    const encodedHighBits = ((highBits << 1) | (lowBits >>> 31)) ^ sign;
+    this.writeVarint64_(encodedHighBits, encodedLowBits);
+  }
+
+  /**
+   * Writes a sint64 value field to the buffer as a varint.
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  writeSint64(fieldNumber, value) {
+    this.writeTag(fieldNumber, WireType.VARINT);
+    this.writeSint64Value_(value);
+  }
+
+  /**
+   * Writes a string value field to the buffer as a varint.
+   * @param {number} fieldNumber
+   * @param {string} value
+   */
+  writeString(fieldNumber, value) {
+    this.writeTag(fieldNumber, WireType.DELIMITED);
+    const array = encoderFunction(value);
+    this.writeUnsignedVarint32_(array.length);
+    this.writeRaw_(array.buffer);
+  }
+
+  /**
+   * Writes raw bytes to the buffer.
+   * @param {!ArrayBuffer} arrayBuffer
+   * @private
+   */
+  writeRaw_(arrayBuffer) {
+    this.closeAndStartNewBuffer_();
+    this.blocks_.push(new Uint8Array(arrayBuffer));
+  }
+
+  /**
+   * Writes raw bytes to the buffer.
+   * @param {!BufferDecoder} bufferDecoder
+   * @param {number} start
+   * @param {!WireType} wireType
+   * @package
+   */
+  writeBufferDecoder(bufferDecoder, start, wireType) {
+    this.closeAndStartNewBuffer_();
+    const dataLength = this.getLength_(bufferDecoder, start, wireType);
+    this.blocks_.push(
+        bufferDecoder.subBufferDecoder(start, dataLength).asUint8Array());
+  }
+
+  /**
+   * Returns the length of the data to serialize. Returns -1 when a STOP GROUP
+   * is found.
+   * @param {!BufferDecoder} bufferDecoder
+   * @param {number} start
+   * @param {!WireType} wireType
+   * @return {number}
+   * @private
+   */
+  getLength_(bufferDecoder, start, wireType) {
+    switch (wireType) {
+      case WireType.VARINT:
+        return bufferDecoder.skipVarint(start) - start;
+      case WireType.FIXED64:
+        return 8;
+      case WireType.DELIMITED:
+        const {lowBits: dataLength, dataStart} = bufferDecoder.getVarint(start);
+        return dataLength + dataStart - start;
+      case WireType.START_GROUP:
+        return this.getGroupLength_(bufferDecoder, start);
+      case WireType.FIXED32:
+        return 4;
+      default:
+        throw new Error(`Invalid wire type: ${wireType}`);
+    }
+  }
+
+  /**
+   * Skips over fields until it finds the end of a given group.
+   * @param {!BufferDecoder} bufferDecoder
+   * @param {number} start
+   * @return {number}
+   * @private
+   */
+  getGroupLength_(bufferDecoder, start) {
+    // On a start group we need to keep skipping fields until we find a
+    // corresponding stop group
+    let cursor = start;
+    while (cursor < bufferDecoder.endIndex()) {
+      const {lowBits: tag, dataStart} = bufferDecoder.getVarint(cursor);
+      const wireType = /** @type {!WireType} */ (tag & 0x07);
+      if (wireType === WireType.END_GROUP) {
+        return dataStart - start;
+      }
+      cursor = dataStart + this.getLength_(bufferDecoder, dataStart, wireType);
+    }
+    throw new Error('No end group found');
+  }
+
+  /**
+   * Write the whole bytes as a length delimited field.
+   * @param {number} fieldNumber
+   * @param {!ArrayBuffer} arrayBuffer
+   */
+  writeDelimited(fieldNumber, arrayBuffer) {
+    this.writeTag(fieldNumber, WireType.DELIMITED);
+    this.writeUnsignedVarint32_(arrayBuffer.byteLength);
+    this.writeRaw_(arrayBuffer);
+  }
+
+  /****************************************************************************
+   *                        REPEATED METHODS
+   ****************************************************************************/
+
+  /**
+   * Writes repeated boolean values to the buffer as unpacked varints.
+   * @param {number} fieldNumber
+   * @param {!Array<boolean>} values
+   */
+  writeRepeatedBool(fieldNumber, values) {
+    values.forEach(val => this.writeBool(fieldNumber, val));
+  }
+
+  /**
+   * Writes repeated boolean values to the buffer as packed varints.
+   * @param {number} fieldNumber
+   * @param {!Array<boolean>} values
+   */
+  writePackedBool(fieldNumber, values) {
+    this.writeFixedPacked_(
+        fieldNumber, values, val => this.writeBoolValue_(val), 1);
+  }
+
+  /**
+   * Writes repeated double values to the buffer as unpacked fixed64.
+   * @param {number} fieldNumber
+   * @param {!Array<number>} values
+   */
+  writeRepeatedDouble(fieldNumber, values) {
+    values.forEach(val => this.writeDouble(fieldNumber, val));
+  }
+
+  /**
+   * Writes repeated double values to the buffer as packed fixed64.
+   * @param {number} fieldNumber
+   * @param {!Array<number>} values
+   */
+  writePackedDouble(fieldNumber, values) {
+    this.writeFixedPacked_(
+        fieldNumber, values, val => this.writeDoubleValue_(val), 8);
+  }
+
+  /**
+   * Writes repeated fixed32 values to the buffer as unpacked fixed32.
+   * @param {number} fieldNumber
+   * @param {!Array<number>} values
+   */
+  writeRepeatedFixed32(fieldNumber, values) {
+    values.forEach(val => this.writeFixed32(fieldNumber, val));
+  }
+
+  /**
+   * Writes repeated fixed32 values to the buffer as packed fixed32.
+   * @param {number} fieldNumber
+   * @param {!Array<number>} values
+   */
+  writePackedFixed32(fieldNumber, values) {
+    this.writeFixedPacked_(
+        fieldNumber, values, val => this.writeFixed32Value_(val), 4);
+  }
+
+  /**
+   * Writes repeated float values to the buffer as unpacked fixed64.
+   * @param {number} fieldNumber
+   * @param {!Array<number>} values
+   */
+  writeRepeatedFloat(fieldNumber, values) {
+    values.forEach(val => this.writeFloat(fieldNumber, val));
+  }
+
+  /**
+   * Writes repeated float values to the buffer as packed fixed64.
+   * @param {number} fieldNumber
+   * @param {!Array<number>} values
+   */
+  writePackedFloat(fieldNumber, values) {
+    this.writeFixedPacked_(
+        fieldNumber, values, val => this.writeFloatValue_(val), 4);
+  }
+
+  /**
+   * Writes repeated int32 values to the buffer as unpacked int32.
+   * @param {number} fieldNumber
+   * @param {!Array<number>} values
+   */
+  writeRepeatedInt32(fieldNumber, values) {
+    values.forEach(val => this.writeInt32(fieldNumber, val));
+  }
+
+  /**
+   * Writes repeated int32 values to the buffer as packed int32.
+   * @param {number} fieldNumber
+   * @param {!Array<number>} values
+   */
+  writePackedInt32(fieldNumber, values) {
+    this.writeVariablePacked_(
+        fieldNumber, values, (writer, val) => writer.writeInt32Value_(val));
+  }
+
+  /**
+   * Writes repeated int64 values to the buffer as unpacked varint.
+   * @param {number} fieldNumber
+   * @param {!Array<!Int64>} values
+   */
+  writeRepeatedInt64(fieldNumber, values) {
+    values.forEach(val => this.writeInt64(fieldNumber, val));
+  }
+
+  /**
+   * Writes repeated int64 values to the buffer as packed varint.
+   * @param {number} fieldNumber
+   * @param {!Array<!Int64>} values
+   */
+  writePackedInt64(fieldNumber, values) {
+    this.writeVariablePacked_(
+        fieldNumber, values,
+        (writer, val) =>
+            writer.writeVarint64_(val.getHighBits(), val.getLowBits()));
+  }
+
+  /**
+   * Writes repeated sfixed32 values to the buffer as unpacked fixed32.
+   * @param {number} fieldNumber
+   * @param {!Array<number>} values
+   */
+  writeRepeatedSfixed32(fieldNumber, values) {
+    values.forEach(val => this.writeSfixed32(fieldNumber, val));
+  }
+
+  /**
+   * Writes repeated sfixed32 values to the buffer as packed fixed32.
+   * @param {number} fieldNumber
+   * @param {!Array<number>} values
+   */
+  writePackedSfixed32(fieldNumber, values) {
+    this.writeFixedPacked_(
+        fieldNumber, values, val => this.writeSfixed32Value_(val), 4);
+  }
+
+  /**
+   * Writes repeated sfixed64 values to the buffer as unpacked fixed64.
+   * @param {number} fieldNumber
+   * @param {!Array<!Int64>} values
+   */
+  writeRepeatedSfixed64(fieldNumber, values) {
+    values.forEach(val => this.writeSfixed64(fieldNumber, val));
+  }
+
+  /**
+   * Writes repeated sfixed64 values to the buffer as packed fixed64.
+   * @param {number} fieldNumber
+   * @param {!Array<!Int64>} values
+   */
+  writePackedSfixed64(fieldNumber, values) {
+    this.writeFixedPacked_(
+        fieldNumber, values, val => this.writeSfixed64Value_(val), 8);
+  }
+
+  /**
+   * Writes repeated sint32 values to the buffer as unpacked sint32.
+   * @param {number} fieldNumber
+   * @param {!Array<number>} values
+   */
+  writeRepeatedSint32(fieldNumber, values) {
+    values.forEach(val => this.writeSint32(fieldNumber, val));
+  }
+
+  /**
+   * Writes repeated sint32 values to the buffer as packed sint32.
+   * @param {number} fieldNumber
+   * @param {!Array<number>} values
+   */
+  writePackedSint32(fieldNumber, values) {
+    this.writeVariablePacked_(
+        fieldNumber, values, (writer, val) => writer.writeSint32Value_(val));
+  }
+
+  /**
+   * Writes repeated sint64 values to the buffer as unpacked varint.
+   * @param {number} fieldNumber
+   * @param {!Array<!Int64>} values
+   */
+  writeRepeatedSint64(fieldNumber, values) {
+    values.forEach(val => this.writeSint64(fieldNumber, val));
+  }
+
+  /**
+   * Writes repeated sint64 values to the buffer as packed varint.
+   * @param {number} fieldNumber
+   * @param {!Array<!Int64>} values
+   */
+  writePackedSint64(fieldNumber, values) {
+    this.writeVariablePacked_(
+        fieldNumber, values, (writer, val) => writer.writeSint64Value_(val));
+  }
+
+  /**
+   * Writes repeated uint32 values to the buffer as unpacked uint32.
+   * @param {number} fieldNumber
+   * @param {!Array<number>} values
+   */
+  writeRepeatedUint32(fieldNumber, values) {
+    values.forEach(val => this.writeUint32(fieldNumber, val));
+  }
+
+  /**
+   * Writes repeated uint32 values to the buffer as packed uint32.
+   * @param {number} fieldNumber
+   * @param {!Array<number>} values
+   */
+  writePackedUint32(fieldNumber, values) {
+    this.writeVariablePacked_(
+        fieldNumber, values, (writer, val) => writer.writeUint32Value_(val));
+  }
+
+  /**
+   * Writes repeated bytes values to the buffer.
+   * @param {number} fieldNumber
+   * @param {!Array<!ByteString>} values
+   */
+  writeRepeatedBytes(fieldNumber, values) {
+    values.forEach(val => this.writeBytes(fieldNumber, val));
+  }
+
+  /**
+   * Writes packed fields with fixed length.
+   * @param {number} fieldNumber
+   * @param {!Array<T>} values
+   * @param {function(T)} valueWriter
+   * @param {number} entitySize
+   * @template T
+   * @private
+   */
+  writeFixedPacked_(fieldNumber, values, valueWriter, entitySize) {
+    if (values.length === 0) {
+      return;
+    }
+    this.writeTag(fieldNumber, WireType.DELIMITED);
+    this.writeUnsignedVarint32_(values.length * entitySize);
+    this.closeAndStartNewBuffer_();
+    values.forEach(value => valueWriter(value));
+  }
+
+  /**
+   * Writes packed fields with variable length.
+   * @param {number} fieldNumber
+   * @param {!Array<T>} values
+   * @param {function(!Writer, T)} valueWriter
+   * @template T
+   * @private
+   */
+  writeVariablePacked_(fieldNumber, values, valueWriter) {
+    if (values.length === 0) {
+      return;
+    }
+    const writer = new Writer();
+    values.forEach(val => valueWriter(writer, val));
+    const bytes = writer.getAndResetResultBuffer();
+    this.writeDelimited(fieldNumber, bytes);
+  }
+
+  /**
+   * Writes repeated string values to the buffer.
+   * @param {number} fieldNumber
+   * @param {!Array<string>} values
+   */
+  writeRepeatedString(fieldNumber, values) {
+    values.forEach(val => this.writeString(fieldNumber, val));
+  }
+}
+
+exports = Writer;

+ 910 - 0
js/experimental/runtime/kernel/writer_test.js

@@ -0,0 +1,910 @@
+/**
+ * @fileoverview Tests for writer.js.
+ */
+goog.module('protobuf.binary.WriterTest');
+
+goog.setTestOnly();
+
+// Note to the reader:
+// Since the writer behavior changes with the checking level some of the tests
+// in this file have to know which checking level is enable to make correct
+// assertions.
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const ByteString = goog.require('protobuf.ByteString');
+const WireType = goog.require('protobuf.binary.WireType');
+const Writer = goog.require('protobuf.binary.Writer');
+const {CHECK_BOUNDS, CHECK_TYPE, MAX_FIELD_NUMBER} = goog.require('protobuf.internal.checks');
+const {arrayBufferSlice} = goog.require('protobuf.binary.typedArrays');
+const {getDoublePairs} = goog.require('protobuf.binary.doubleTestPairs');
+const {getFixed32Pairs} = goog.require('protobuf.binary.fixed32TestPairs');
+const {getFloatPairs} = goog.require('protobuf.binary.floatTestPairs');
+const {getInt32Pairs} = goog.require('protobuf.binary.int32TestPairs');
+const {getInt64Pairs} = goog.require('protobuf.binary.int64TestPairs');
+const {getPackedBoolPairs} = goog.require('protobuf.binary.packedBoolTestPairs');
+const {getPackedDoublePairs} = goog.require('protobuf.binary.packedDoubleTestPairs');
+const {getPackedFixed32Pairs} = goog.require('protobuf.binary.packedFixed32TestPairs');
+const {getPackedFloatPairs} = goog.require('protobuf.binary.packedFloatTestPairs');
+const {getPackedInt32Pairs} = goog.require('protobuf.binary.packedInt32TestPairs');
+const {getPackedInt64Pairs} = goog.require('protobuf.binary.packedInt64TestPairs');
+const {getPackedSfixed32Pairs} = goog.require('protobuf.binary.packedSfixed32TestPairs');
+const {getPackedSfixed64Pairs} = goog.require('protobuf.binary.packedSfixed64TestPairs');
+const {getPackedSint32Pairs} = goog.require('protobuf.binary.packedSint32TestPairs');
+const {getPackedSint64Pairs} = goog.require('protobuf.binary.packedSint64TestPairs');
+const {getPackedUint32Pairs} = goog.require('protobuf.binary.packedUint32TestPairs');
+const {getSfixed32Pairs} = goog.require('protobuf.binary.sfixed32TestPairs');
+const {getSfixed64Pairs} = goog.require('protobuf.binary.sfixed64TestPairs');
+const {getSint32Pairs} = goog.require('protobuf.binary.sint32TestPairs');
+const {getSint64Pairs} = goog.require('protobuf.binary.sint64TestPairs');
+const {getUint32Pairs} = goog.require('protobuf.binary.uint32TestPairs');
+
+
+/**
+ * @param {...number} bytes
+ * @return {!ArrayBuffer}
+ */
+function createArrayBuffer(...bytes) {
+  return new Uint8Array(bytes).buffer;
+}
+
+/******************************************************************************
+ *                        OPTIONAL FUNCTIONS
+ ******************************************************************************/
+
+describe('Writer does', () => {
+  it('return an empty ArrayBuffer when nothing is encoded', () => {
+    const writer = new Writer();
+    expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
+  });
+
+  it('encode tag', () => {
+    const writer = new Writer();
+    writer.writeTag(1, WireType.VARINT);
+    expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer(0x08));
+  });
+
+  it('reset after calling getAndResetResultBuffer', () => {
+    const writer = new Writer();
+    writer.writeTag(1, WireType.VARINT);
+    writer.getAndResetResultBuffer();
+    expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
+  });
+
+  it('fail when field number is too large for writeTag', () => {
+    const writer = new Writer();
+    if (CHECK_TYPE) {
+      expect(() => writer.writeTag(MAX_FIELD_NUMBER + 1, WireType.VARINT))
+          .toThrowError('Field number is out of range: 536870912');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      writer.writeTag(MAX_FIELD_NUMBER + 1, WireType.VARINT);
+      expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer(0));
+    }
+  });
+
+  it('fail when field number is negative for writeTag', () => {
+    const writer = new Writer();
+    if (CHECK_TYPE) {
+      expect(() => writer.writeTag(-1, WireType.VARINT))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      writer.writeTag(-1, WireType.VARINT);
+      expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer(0xF8));
+    }
+  });
+
+  it('fail when wire type is invalid for writeTag', () => {
+    const writer = new Writer();
+    if (CHECK_TYPE) {
+      expect(() => writer.writeTag(1, /** @type {!WireType} */ (0x08)))
+          .toThrowError('Invalid wire type: 8');
+    } else {
+      // Note in unchecked mode we produce invalid output for invalid inputs.
+      // This test just documents our behavior in those cases.
+      // These values might change at any point and are not considered
+      // what the implementation should be doing here.
+      writer.writeTag(1, /** @type {!WireType} */ (0x08));
+      expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer(0x08));
+    }
+  });
+
+  it('encode singular boolean value', () => {
+    const writer = new Writer();
+    writer.writeBool(1, true);
+    expect(writer.getAndResetResultBuffer())
+        .toEqual(createArrayBuffer(0x08, 0x01));
+  });
+
+  it('encode length delimited', () => {
+    const writer = new Writer();
+    writer.writeDelimited(1, createArrayBuffer(0x01, 0x02));
+    expect(writer.getAndResetResultBuffer())
+        .toEqual(createArrayBuffer(0x0A, 0x02, 0x01, 0x02));
+  });
+});
+
+describe('Writer.writeBufferDecoder does', () => {
+  it('encode BufferDecoder containing a varint value', () => {
+    const writer = new Writer();
+    const expected = createArrayBuffer(
+        0x08, /* varint start= */ 0xFF, /* varint end= */ 0x01, 0x08, 0x01);
+    writer.writeBufferDecoder(
+        BufferDecoder.fromArrayBuffer(expected), 1, WireType.VARINT);
+    const result = writer.getAndResetResultBuffer();
+    expect(result).toEqual(arrayBufferSlice(expected, 1, 3));
+  });
+
+  it('encode BufferDecoder containing a fixed64 value', () => {
+    const writer = new Writer();
+    const expected = createArrayBuffer(
+        0x09, /* fixed64 start= */ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+        /* fixed64 end= */ 0x08, 0x08, 0x01);
+    writer.writeBufferDecoder(
+        BufferDecoder.fromArrayBuffer(expected), 1, WireType.FIXED64);
+    const result = writer.getAndResetResultBuffer();
+    expect(result).toEqual(arrayBufferSlice(expected, 1, 9));
+  });
+
+  it('encode BufferDecoder containing a length delimited value', () => {
+    const writer = new Writer();
+    const expected = createArrayBuffer(
+        0xA, /* length= */ 0x03, /* data start= */ 0x01, 0x02,
+        /* data end= */ 0x03, 0x08, 0x01);
+    writer.writeBufferDecoder(
+        BufferDecoder.fromArrayBuffer(expected), 1, WireType.DELIMITED);
+    const result = writer.getAndResetResultBuffer();
+    expect(result).toEqual(arrayBufferSlice(expected, 1, 5));
+  });
+
+  it('encode BufferDecoder containing a group', () => {
+    const writer = new Writer();
+    const expected = createArrayBuffer(
+        0xB, /* group start= */ 0x08, 0x01, /* nested group start= */ 0x0B,
+        /* nested group end= */ 0x0C, /* group end= */ 0x0C, 0x08, 0x01);
+    writer.writeBufferDecoder(
+        BufferDecoder.fromArrayBuffer(expected), 1, WireType.START_GROUP);
+    const result = writer.getAndResetResultBuffer();
+    expect(result).toEqual(arrayBufferSlice(expected, 1, 6));
+  });
+
+  it('encode BufferDecoder containing a fixed32 value', () => {
+    const writer = new Writer();
+    const expected = createArrayBuffer(
+        0x09, /* fixed64 start= */ 0x01, 0x02, 0x03, /* fixed64 end= */ 0x04,
+        0x08, 0x01);
+    writer.writeBufferDecoder(
+        BufferDecoder.fromArrayBuffer(expected), 1, WireType.FIXED32);
+    const result = writer.getAndResetResultBuffer();
+    expect(result).toEqual(arrayBufferSlice(expected, 1, 5));
+  });
+
+  it('fail when encoding out of bound data', () => {
+    const writer = new Writer();
+    const buffer = createArrayBuffer(0x4, 0x0, 0x1, 0x2, 0x3);
+    const subBuffer = arrayBufferSlice(buffer, 0, 2);
+    expect(
+        () => writer.writeBufferDecoder(
+            BufferDecoder.fromArrayBuffer(subBuffer), 0, WireType.DELIMITED))
+        .toThrow();
+  });
+});
+
+describe('Writer.writeBytes does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  it('encodes empty ByteString', () => {
+    writer.writeBytes(1, ByteString.EMPTY);
+    const buffer = writer.getAndResetResultBuffer();
+    expect(buffer.byteLength).toBe(2);
+  });
+
+  it('encodes empty array', () => {
+    writer.writeBytes(1, ByteString.fromArrayBuffer(new ArrayBuffer(0)));
+    expect(writer.getAndResetResultBuffer())
+        .toEqual(createArrayBuffer(
+            1 << 3 | 0x02,  // tag (fieldnumber << 3 | (length delimited))
+            0,              // length of the bytes
+            ));
+  });
+
+  it('encodes ByteString', () => {
+    const array = createArrayBuffer(1, 2, 3);
+    writer.writeBytes(1, ByteString.fromArrayBuffer(array));
+    expect(writer.getAndResetResultBuffer())
+        .toEqual(createArrayBuffer(
+            1 << 3 | 0x02,  // tag (fieldnumber << 3 | (length delimited))
+            3,              // length of the bytes
+            1,
+            2,
+            3,
+            ));
+  });
+});
+
+describe('Writer.writeDouble does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  for (const pair of getDoublePairs()) {
+    it(`encode ${pair.name}`, () => {
+        writer.writeDouble(1, pair.doubleValue);
+        const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+        expect(buffer.length).toBe(9);
+        // ensure we have a correct tag
+        expect(buffer[0]).toEqual(0x09);
+        // Encoded values are stored right after the tag
+        expect(buffer.subarray(1, 9))
+            .toEqual(pair.bufferDecoder.asUint8Array());
+    });
+  }
+
+  /**
+   * NaN may have different value in different browsers. Thus, we need to make
+   * the test lenient.
+   */
+  it('encode NaN', () => {
+    writer.writeDouble(1, NaN);
+    const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+    expect(buffer.length).toBe(9);
+    // ensure we have a correct tag
+    expect(buffer[0]).toEqual(0x09);
+    // Encoded values are stored right after the tag
+    const float64 = new DataView(buffer.buffer);
+    expect(float64.getFloat64(1, true)).toBeNaN();
+  });
+});
+
+describe('Writer.writeFixed32 does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  for (const pair of getFixed32Pairs()) {
+    it(`encode ${pair.name}`, () => {
+      writer.writeFixed32(1, pair.intValue);
+      const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+      expect(buffer.length).toBe(5);
+      // ensure we have a correct tag
+      expect(buffer[0]).toEqual(0x0D);
+      // Encoded values are stored right after the tag
+      expect(buffer.subarray(1, 5)).toEqual(pair.bufferDecoder.asUint8Array());
+    });
+  }
+});
+
+describe('Writer.writeFloat does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  for (const pair of getFloatPairs()) {
+    it(`encode ${pair.name}`, () => {
+      writer.writeFloat(1, pair.floatValue);
+      const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+      expect(buffer.length).toBe(5);
+      // ensure we have a correct tag
+      expect(buffer[0]).toEqual(0x0D);
+      // Encoded values are stored right after the tag
+      expect(buffer.subarray(1, 5)).toEqual(pair.bufferDecoder.asUint8Array());
+    });
+  }
+
+  /**
+   * NaN may have different value in different browsers. Thus, we need to make
+   * the test lenient.
+   */
+  it('encode NaN', () => {
+    writer.writeFloat(1, NaN);
+    const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+    expect(buffer.length).toBe(5);
+    // ensure we have a correct tag
+    expect(buffer[0]).toEqual(0x0D);
+    // Encoded values are stored right after the tag
+    const float32 = new DataView(buffer.buffer);
+    expect(float32.getFloat32(1, true)).toBeNaN();
+  });
+});
+
+describe('Writer.writeInt32 does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  for (const pair of getInt32Pairs()) {
+    if (!pair.skip_writer) {
+      it(`encode ${pair.name}`, () => {
+        writer.writeInt32(1, pair.intValue);
+        const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+        // ensure we have a correct tag
+        expect(buffer[0]).toEqual(0x08);
+        // Encoded values are stored right after the tag
+        expect(buffer.subarray(1, buffer.length))
+            .toEqual(pair.bufferDecoder.asUint8Array());
+      });
+    }
+  }
+});
+
+describe('Writer.writeSfixed32 does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  it('encode empty array', () => {
+    writer.writePackedSfixed32(1, []);
+    expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
+  });
+
+  for (const pair of getSfixed32Pairs()) {
+    it(`encode ${pair.name}`, () => {
+      writer.writeSfixed32(1, pair.intValue);
+      const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+      expect(buffer.length).toBe(5);
+      // ensure we have a correct tag
+      expect(buffer[0]).toEqual(0x0D);
+      // Encoded values are stored right after the tag
+      expect(buffer.subarray(1, 5)).toEqual(pair.bufferDecoder.asUint8Array());
+    });
+  }
+});
+
+describe('Writer.writeSfixed64 does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  for (const pair of getSfixed64Pairs()) {
+    it(`encode ${pair.name}`, () => {
+      writer.writeSfixed64(1, pair.longValue);
+      const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+      expect(buffer.length).toBe(9);
+      // ensure we have a correct tag
+      expect(buffer[0]).toEqual(0x09);
+      // Encoded values are stored right after the tag
+      expect(buffer.subarray(1, 9)).toEqual(pair.bufferDecoder.asUint8Array());
+    });
+  }
+});
+
+describe('Writer.writeSint32 does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  for (const pair of getSint32Pairs()) {
+    if (!pair.skip_writer) {
+      it(`encode ${pair.name}`, () => {
+        writer.writeSint32(1, pair.intValue);
+        const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+        // ensure we have a correct tag
+        expect(buffer[0]).toEqual(0x08);
+        // Encoded values are stored right after the tag
+        expect(buffer.subarray(1, buffer.length))
+            .toEqual(pair.bufferDecoder.asUint8Array());
+      });
+    }
+  }
+});
+
+describe('Writer.writeSint64 does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  for (const pair of getSint64Pairs()) {
+    if (!pair.skip_writer) {
+      it(`encode ${pair.name}`, () => {
+        writer.writeSint64(1, pair.longValue);
+        const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+        // ensure we have a correct tag
+        expect(buffer[0]).toEqual(0x08);
+        // Encoded values are stored right after the tag
+        expect(buffer.subarray(1, buffer.length))
+            .toEqual(pair.bufferDecoder.asUint8Array());
+      });
+    }
+  }
+});
+
+describe('Writer.writeInt64 does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  for (const pair of getInt64Pairs()) {
+    if (!pair.skip_writer) {
+      it(`encode ${pair.name}`, () => {
+        writer.writeInt64(1, pair.longValue);
+        const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+        // ensure we have a correct tag
+        expect(buffer[0]).toEqual(0x08);
+        // Encoded values are stored right after the tag
+        expect(buffer.subarray(1, buffer.length))
+            .toEqual(pair.bufferDecoder.asUint8Array());
+      });
+    }
+  }
+});
+
+describe('Writer.writeUint32 does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  for (const pair of getUint32Pairs()) {
+    if (!pair.skip_writer) {
+      it(`encode ${pair.name}`, () => {
+        writer.writeUint32(1, pair.intValue);
+        const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+        // ensure we have a correct tag
+        expect(buffer[0]).toEqual(0x08);
+        // Encoded values are stored right after the tag
+        expect(buffer.subarray(1, buffer.length))
+            .toEqual(pair.bufferDecoder.asUint8Array());
+      });
+    }
+  }
+});
+
+describe('Writer.writeString does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  it('encode empty string', () => {
+    writer.writeString(1, '');
+    expect(writer.getAndResetResultBuffer())
+        .toEqual(createArrayBuffer(
+            1 << 3 | 0x02,  // tag (fieldnumber << 3 | (length delimited))
+            0,              // length of the string
+            ));
+  });
+
+  it('encode simple string', () => {
+    writer.writeString(1, 'hello');
+    expect(writer.getAndResetResultBuffer())
+        .toEqual(createArrayBuffer(
+            1 << 3 | 0x02,  // tag (fieldnumber << 3 | (length delimited))
+            5,              // length of the string
+            'h'.charCodeAt(0),
+            'e'.charCodeAt(0),
+            'l'.charCodeAt(0),
+            'l'.charCodeAt(0),
+            'o'.charCodeAt(0),
+            ));
+  });
+
+  it('throw for invalid fieldnumber', () => {
+    if (CHECK_BOUNDS) {
+      expect(() => writer.writeString(-1, 'a'))
+          .toThrowError('Field number is out of range: -1');
+    } else {
+      writer.writeString(-1, 'a');
+      expect(new Uint8Array(writer.getAndResetResultBuffer()))
+          .toEqual(new Uint8Array(createArrayBuffer(
+              -6,  // invalid tag
+              1,   // string length
+              'a'.charCodeAt(0),
+              )));
+    }
+  });
+
+  it('throw for null string value', () => {
+    expect(
+        () => writer.writeString(
+            1, /** @type {string} */ (/** @type {*} */ (null))))
+        .toThrow();
+  });
+});
+
+
+/******************************************************************************
+ *                        REPEATED FUNCTIONS
+ ******************************************************************************/
+
+describe('Writer.writePackedBool does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  it('encode empty array', () => {
+    writer.writePackedBool(1, []);
+    expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
+  });
+
+  for (const pair of getPackedBoolPairs()) {
+    if (!pair.skip_writer) {
+      it(`encode ${pair.name}`, () => {
+        writer.writePackedBool(1, pair.boolValues);
+        const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+        // ensure we have a correct tag
+        expect(buffer[0]).toEqual(0x0A);
+        // Encoded values are stored right after the tag
+        expect(buffer.subarray(1, buffer.length))
+            .toEqual(pair.bufferDecoder.asUint8Array());
+      });
+    }
+  }
+});
+
+describe('Writer.writeRepeatedBool does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  it('encode empty array', () => {
+    writer.writeRepeatedBool(1, []);
+    expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
+  });
+
+  it('encode repeated unpacked boolean values', () => {
+    const writer = new Writer();
+    writer.writeRepeatedBool(1, [true, false]);
+    expect(writer.getAndResetResultBuffer())
+        .toEqual(createArrayBuffer(
+            1 << 3 | 0x00,  // tag (fieldnumber << 3 | (varint))
+            0x01,           // value[0]
+            1 << 3 | 0x00,  // tag (fieldnumber << 3 | (varint))
+            0x00,           // value[1]
+            ));
+  });
+});
+
+describe('Writer.writePackedDouble does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  it('encode empty array', () => {
+    writer.writePackedDouble(1, []);
+    expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
+  });
+
+  for (const pair of getPackedDoublePairs()) {
+    if (!pair.skip_writer) {
+      it(`encode ${pair.name}`, () => {
+        writer.writePackedDouble(1, pair.doubleValues);
+        const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+        // ensure we have a correct tag
+        expect(buffer[0]).toEqual(0x0A);
+        // Encoded values are stored right after the tag
+        expect(buffer.subarray(1, buffer.length))
+            .toEqual(pair.bufferDecoder.asUint8Array());
+      });
+    }
+  }
+});
+
+describe('Writer.writePackedFixed32 does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  it('encode empty array', () => {
+    writer.writePackedFixed32(1, []);
+    expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
+  });
+
+  for (const pair of getPackedFixed32Pairs()) {
+    if (!pair.skip_writer) {
+      it(`encode ${pair.name}`, () => {
+        writer.writePackedFixed32(1, pair.fixed32Values);
+        const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+        // ensure we have a correct tag
+        expect(buffer[0]).toEqual(0x0A);
+        // Encoded values are stored right after the tag
+        expect(buffer.subarray(1, buffer.length))
+            .toEqual(pair.bufferDecoder.asUint8Array());
+      });
+    }
+  }
+});
+
+describe('Writer.writePackedFloat does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  it('encode empty array', () => {
+    writer.writePackedFloat(1, []);
+    expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
+  });
+
+  for (const pair of getPackedFloatPairs()) {
+    if (!pair.skip_writer) {
+      it(`encode ${pair.name}`, () => {
+        writer.writePackedFloat(1, pair.floatValues);
+        const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+        // ensure we have a correct tag
+        expect(buffer[0]).toEqual(0x0A);
+        // Encoded values are stored right after the tag
+        expect(buffer.subarray(1, buffer.length))
+            .toEqual(pair.bufferDecoder.asUint8Array());
+      });
+    }
+  }
+});
+
+describe('Writer.writePackedInt32 does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  it('encode empty array', () => {
+    writer.writePackedInt32(1, []);
+    expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
+  });
+
+  for (const pair of getPackedInt32Pairs()) {
+    if (!pair.skip_writer) {
+      it(`encode ${pair.name}`, () => {
+        writer.writePackedInt32(1, pair.int32Values);
+        const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+        // ensure we have a correct tag
+        expect(buffer[0]).toEqual(0x0A);
+        // Encoded values are stored right after the tag
+        expect(buffer.subarray(1, buffer.length))
+            .toEqual(pair.bufferDecoder.asUint8Array());
+      });
+    }
+  }
+});
+
+describe('Writer.writePackedInt64 does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  it('encode empty array', () => {
+    writer.writePackedInt64(1, []);
+    expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
+  });
+
+  for (const pair of getPackedInt64Pairs()) {
+    if (!pair.skip_writer) {
+      it(`encode ${pair.name}`, () => {
+        writer.writePackedInt64(1, pair.int64Values);
+        const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+        // ensure we have a correct tag
+        expect(buffer[0]).toEqual(0x0A);
+        // Encoded values are stored right after the tag
+        expect(buffer.subarray(1, buffer.length))
+            .toEqual(pair.bufferDecoder.asUint8Array());
+      });
+    }
+  }
+});
+
+describe('Writer.writePackedSfixed32 does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  it('encode empty array', () => {
+    writer.writePackedSfixed32(1, []);
+    expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
+  });
+
+  for (const pair of getPackedSfixed32Pairs()) {
+    if (!pair.skip_writer) {
+      it(`encode ${pair.name}`, () => {
+        writer.writePackedSfixed32(1, pair.sfixed32Values);
+        const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+        // ensure we have a correct tag
+        expect(buffer[0]).toEqual(0x0A);
+        // Encoded values are stored right after the tag
+        expect(buffer.subarray(1, buffer.length))
+            .toEqual(pair.bufferDecoder.asUint8Array());
+      });
+    }
+  }
+});
+
+describe('Writer.writePackedSfixed64 does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  it('encode empty array', () => {
+    writer.writePackedSfixed64(1, []);
+    expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
+  });
+
+  for (const pair of getPackedSfixed64Pairs()) {
+    if (!pair.skip_writer) {
+      it(`encode ${pair.name}`, () => {
+        writer.writePackedSfixed64(1, pair.sfixed64Values);
+        const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+        // ensure we have a correct tag
+        expect(buffer[0]).toEqual(0x0A);
+        // Encoded values are stored right after the tag
+        expect(buffer.subarray(1, buffer.length))
+            .toEqual(pair.bufferDecoder.asUint8Array());
+      });
+    }
+  }
+});
+
+describe('Writer.writePackedSint32 does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  it('encode empty array', () => {
+    writer.writePackedSint32(1, []);
+    expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
+  });
+
+  for (const pair of getPackedSint32Pairs()) {
+    if (!pair.skip_writer) {
+      it(`encode ${pair.name}`, () => {
+        writer.writePackedSint32(1, pair.sint32Values);
+        const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+        // ensure we have a correct tag
+        expect(buffer[0]).toEqual(0x0A);
+        // Encoded values are stored right after the tag
+        expect(buffer.subarray(1, buffer.length))
+            .toEqual(pair.bufferDecoder.asUint8Array());
+      });
+    }
+  }
+});
+
+describe('Writer.writePackedSint64 does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  it('encode empty array', () => {
+    writer.writePackedSint64(1, []);
+    expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
+  });
+
+  for (const pair of getPackedSint64Pairs()) {
+    if (!pair.skip_writer) {
+      it(`encode ${pair.name}`, () => {
+        writer.writePackedSint64(1, pair.sint64Values);
+        const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+        // ensure we have a correct tag
+        expect(buffer[0]).toEqual(0x0A);
+        // Encoded values are stored right after the tag
+        expect(buffer.subarray(1, buffer.length))
+            .toEqual(pair.bufferDecoder.asUint8Array());
+      });
+    }
+  }
+});
+
+describe('Writer.writePackedUint32 does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  it('encode empty array', () => {
+    writer.writePackedUint32(1, []);
+    expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
+  });
+
+  for (const pair of getPackedUint32Pairs()) {
+    if (!pair.skip_writer) {
+      it(`encode ${pair.name}`, () => {
+        writer.writePackedUint32(1, pair.uint32Values);
+        const buffer = new Uint8Array(writer.getAndResetResultBuffer());
+        // ensure we have a correct tag
+        expect(buffer[0]).toEqual(0x0A);
+        // Encoded values are stored right after the tag
+        expect(buffer.subarray(1, buffer.length))
+            .toEqual(pair.bufferDecoder.asUint8Array());
+      });
+    }
+  }
+});
+
+describe('Writer.writeRepeatedBytes does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  it('encode empty array', () => {
+    writer.writeRepeatedBytes(1, []);
+    expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
+  });
+
+  it('encode single value', () => {
+    const value = createArrayBuffer(0x61);
+    writer.writeRepeatedBytes(1, [ByteString.fromArrayBuffer(value)]);
+    expect(writer.getAndResetResultBuffer())
+        .toEqual(createArrayBuffer(
+            0x0A,
+            0x01,
+            0x61,  // a
+            ));
+  });
+
+  it('encode multiple values', () => {
+    const value1 = createArrayBuffer(0x61);
+    const value2 = createArrayBuffer(0x62);
+    writer.writeRepeatedBytes(1, [
+      ByteString.fromArrayBuffer(value1),
+      ByteString.fromArrayBuffer(value2),
+    ]);
+    expect(writer.getAndResetResultBuffer())
+        .toEqual(createArrayBuffer(
+            0x0A,
+            0x01,
+            0x61,  // a
+            0x0A,
+            0x01,
+            0x62,  // b
+            ));
+  });
+});
+
+describe('Writer.writeRepeatedString does', () => {
+  let writer;
+  beforeEach(() => {
+    writer = new Writer();
+  });
+
+  it('encode empty array', () => {
+    writer.writeRepeatedString(1, []);
+    expect(writer.getAndResetResultBuffer()).toEqual(createArrayBuffer());
+  });
+
+  it('encode single value', () => {
+    writer.writeRepeatedString(1, ['a']);
+    expect(writer.getAndResetResultBuffer())
+        .toEqual(createArrayBuffer(
+            0x0A,
+            0x01,
+            0x61,  // a
+            ));
+  });
+
+  it('encode multiple values', () => {
+    writer.writeRepeatedString(1, ['a', 'b']);
+    expect(writer.getAndResetResultBuffer())
+        .toEqual(createArrayBuffer(
+            0x0A,
+            0x01,
+            0x61,  // a
+            0x0A,
+            0x01,
+            0x62,  // b
+            ));
+  });
+});

+ 1763 - 0
js/experimental/runtime/testing/binary/test_message.js

@@ -0,0 +1,1763 @@
+/**
+ * @fileoverview LazyAccessor wrapper message.
+ */
+goog.module('protobuf.testing.binary.TestMessage');
+
+const ByteString = goog.require('protobuf.ByteString');
+const Int64 = goog.require('protobuf.Int64');
+const InternalMessage = goog.require('protobuf.binary.InternalMessage');
+const LazyAccessor = goog.require('protobuf.binary.LazyAccessor');
+
+/**
+ * A protobuf message implemented as a LazyAccessor wrapper.
+ * @implements {InternalMessage}
+ */
+class TestMessage {
+  /**
+   * @param {!LazyAccessor} kernel
+   * @return {!TestMessage}
+   */
+  static instanceCreator(kernel) {
+    return new TestMessage(kernel);
+  }
+
+  /**
+   * @param {!LazyAccessor} kernel
+   */
+  constructor(kernel) {
+    /** @private @const {!LazyAccessor} */
+    this.kernel_ = kernel;
+  }
+
+  /**
+   * @override
+   * @package
+   * @return {!LazyAccessor}
+   */
+  internalGetKernel() {
+    return this.kernel_;
+  }
+
+  /**
+   * @return {!ArrayBuffer}
+   */
+  serialize() {
+    return this.kernel_.serialize();
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {boolean=} defaultValue
+   * @return {boolean}
+   */
+  getBoolWithDefault(fieldNumber, defaultValue = false) {
+    return this.kernel_.getBoolWithDefault(fieldNumber, defaultValue);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!ByteString=} defaultValue
+   * @return {!ByteString}
+   */
+  getBytesWithDefault(fieldNumber, defaultValue = ByteString.EMPTY) {
+    return this.kernel_.getBytesWithDefault(fieldNumber, defaultValue);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number=} defaultValue
+   * @return {number}
+   */
+  getDoubleWithDefault(fieldNumber, defaultValue = 0) {
+    return this.kernel_.getDoubleWithDefault(fieldNumber, defaultValue);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number=} defaultValue
+   * @return {number}
+   */
+  getFixed32WithDefault(fieldNumber, defaultValue = 0) {
+    return this.kernel_.getFixed32WithDefault(fieldNumber, defaultValue);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Int64=} defaultValue
+   * @return {!Int64}
+   */
+  getFixed64WithDefault(fieldNumber, defaultValue = Int64.getZero()) {
+    return this.kernel_.getFixed64WithDefault(fieldNumber, defaultValue);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number=} defaultValue
+   * @return {number}
+   */
+  getFloatWithDefault(fieldNumber, defaultValue = 0) {
+    return this.kernel_.getFloatWithDefault(fieldNumber, defaultValue);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number=} defaultValue
+   * @return {number}
+   */
+  getInt32WithDefault(fieldNumber, defaultValue = 0) {
+    return this.kernel_.getInt32WithDefault(fieldNumber, defaultValue);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Int64=} defaultValue
+   * @return {!Int64}
+   */
+  getInt64WithDefault(fieldNumber, defaultValue = Int64.getZero()) {
+    return this.kernel_.getInt64WithDefault(fieldNumber, defaultValue);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number=} defaultValue
+   * @return {number}
+   */
+  getSfixed32WithDefault(fieldNumber, defaultValue = 0) {
+    return this.kernel_.getSfixed32WithDefault(fieldNumber, defaultValue);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Int64=} defaultValue
+   * @return {!Int64}
+   */
+  getSfixed64WithDefault(fieldNumber, defaultValue = Int64.getZero()) {
+    return this.kernel_.getSfixed64WithDefault(fieldNumber, defaultValue);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number=} defaultValue
+   * @return {number}
+   */
+  getSint32WithDefault(fieldNumber, defaultValue = 0) {
+    return this.kernel_.getSint32WithDefault(fieldNumber, defaultValue);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Int64=} defaultValue
+   * @return {!Int64}
+   */
+  getSint64WithDefault(fieldNumber, defaultValue = Int64.getZero()) {
+    return this.kernel_.getSint64WithDefault(fieldNumber, defaultValue);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {string=} defaultValue
+   * @return {string}
+   */
+  getStringWithDefault(fieldNumber, defaultValue = '') {
+    return this.kernel_.getStringWithDefault(fieldNumber, defaultValue);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number=} defaultValue
+   * @return {number}
+   */
+  getUint32WithDefault(fieldNumber, defaultValue = 0) {
+    return this.kernel_.getUint32WithDefault(fieldNumber, defaultValue);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Int64=} defaultValue
+   * @return {!Int64}
+   */
+  getUint64WithDefault(fieldNumber, defaultValue = Int64.getZero()) {
+    return this.kernel_.getUint64WithDefault(fieldNumber, defaultValue);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {function(!LazyAccessor):T} instanceCreator
+   * @return {?T}
+   * @template T
+   */
+  getMessageOrNull(fieldNumber, instanceCreator) {
+    return this.kernel_.getMessageOrNull(fieldNumber, instanceCreator);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {function(!LazyAccessor):T} instanceCreator
+   * @return {T}
+   * @template T
+   */
+  getMessageAttach(fieldNumber, instanceCreator) {
+    return this.kernel_.getMessageAttach(fieldNumber, instanceCreator);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {function(!LazyAccessor):T} instanceCreator
+   * @return {T}
+   * @template T
+   */
+  getMessage(fieldNumber, instanceCreator) {
+    return this.kernel_.getMessage(fieldNumber, instanceCreator);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {?LazyAccessor}
+   * @template T
+   */
+  getMessageAccessorOrNull(fieldNumber) {
+    return this.kernel_.getMessageAccessorOrNull(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {boolean}
+   */
+  getRepeatedBoolElement(fieldNumber, index) {
+    return this.kernel_.getRepeatedBoolElement(fieldNumber, index);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {!Iterable<boolean>}
+   */
+  getRepeatedBoolIterable(fieldNumber) {
+    return this.kernel_.getRepeatedBoolIterable(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedBoolSize(fieldNumber) {
+    return this.kernel_.getRepeatedBoolSize(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {number}
+   */
+  getRepeatedDoubleElement(fieldNumber, index) {
+    return this.kernel_.getRepeatedDoubleElement(fieldNumber, index);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {!Iterable<number>}
+   */
+  getRepeatedDoubleIterable(fieldNumber) {
+    return this.kernel_.getRepeatedDoubleIterable(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedDoubleSize(fieldNumber) {
+    return this.kernel_.getRepeatedDoubleSize(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {number}
+   */
+  getRepeatedFixed32Element(fieldNumber, index) {
+    return this.kernel_.getRepeatedFixed32Element(fieldNumber, index);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {!Iterable<number>}
+   */
+  getRepeatedFixed32Iterable(fieldNumber) {
+    return this.kernel_.getRepeatedFixed32Iterable(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedFixed32Size(fieldNumber) {
+    return this.kernel_.getRepeatedFixed32Size(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {!Int64}
+   */
+  getRepeatedFixed64Element(fieldNumber, index) {
+    return this.kernel_.getRepeatedFixed64Element(fieldNumber, index);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {!Iterable<!Int64>}
+   */
+  getRepeatedFixed64Iterable(fieldNumber) {
+    return this.kernel_.getRepeatedFixed64Iterable(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedFixed64Size(fieldNumber) {
+    return this.kernel_.getRepeatedFixed64Size(fieldNumber);
+  }
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {number}
+   */
+  getRepeatedFloatElement(fieldNumber, index) {
+    return this.kernel_.getRepeatedFloatElement(fieldNumber, index);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {!Iterable<number>}
+   */
+  getRepeatedFloatIterable(fieldNumber) {
+    return this.kernel_.getRepeatedFloatIterable(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedFloatSize(fieldNumber) {
+    return this.kernel_.getRepeatedFloatSize(fieldNumber);
+  }
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {number}
+   */
+  getRepeatedInt32Element(fieldNumber, index) {
+    return this.kernel_.getRepeatedInt32Element(fieldNumber, index);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {!Iterable<number>}
+   */
+  getRepeatedInt32Iterable(fieldNumber) {
+    return this.kernel_.getRepeatedInt32Iterable(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedInt32Size(fieldNumber) {
+    return this.kernel_.getRepeatedInt32Size(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {!Int64}
+   */
+  getRepeatedInt64Element(fieldNumber, index) {
+    return this.kernel_.getRepeatedInt64Element(fieldNumber, index);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {!Iterable<!Int64>}
+   */
+  getRepeatedInt64Iterable(fieldNumber) {
+    return this.kernel_.getRepeatedInt64Iterable(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedInt64Size(fieldNumber) {
+    return this.kernel_.getRepeatedInt64Size(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {number}
+   */
+  getRepeatedSfixed32Element(fieldNumber, index) {
+    return this.kernel_.getRepeatedSfixed32Element(fieldNumber, index);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {!Iterable<number>}
+   */
+  getRepeatedSfixed32Iterable(fieldNumber) {
+    return this.kernel_.getRepeatedSfixed32Iterable(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedSfixed32Size(fieldNumber) {
+    return this.kernel_.getRepeatedSfixed32Size(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {!Int64}
+   */
+  getRepeatedSfixed64Element(fieldNumber, index) {
+    return this.kernel_.getRepeatedSfixed64Element(fieldNumber, index);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {!Iterable<!Int64>}
+   */
+  getRepeatedSfixed64Iterable(fieldNumber) {
+    return this.kernel_.getRepeatedSfixed64Iterable(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedSfixed64Size(fieldNumber) {
+    return this.kernel_.getRepeatedSfixed64Size(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {number}
+   */
+  getRepeatedSint32Element(fieldNumber, index) {
+    return this.kernel_.getRepeatedSint32Element(fieldNumber, index);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {!Iterable<number>}
+   */
+  getRepeatedSint32Iterable(fieldNumber) {
+    return this.kernel_.getRepeatedSint32Iterable(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedSint32Size(fieldNumber) {
+    return this.kernel_.getRepeatedSint32Size(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {!Int64}
+   */
+  getRepeatedSint64Element(fieldNumber, index) {
+    return this.kernel_.getRepeatedSint64Element(fieldNumber, index);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {!Iterable<!Int64>}
+   */
+  getRepeatedSint64Iterable(fieldNumber) {
+    return this.kernel_.getRepeatedSint64Iterable(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedSint64Size(fieldNumber) {
+    return this.kernel_.getRepeatedSint64Size(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {number}
+   */
+  getRepeatedUint32Element(fieldNumber, index) {
+    return this.kernel_.getRepeatedUint32Element(fieldNumber, index);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {!Iterable<number>}
+   */
+  getRepeatedUint32Iterable(fieldNumber) {
+    return this.kernel_.getRepeatedUint32Iterable(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedUint32Size(fieldNumber) {
+    return this.kernel_.getRepeatedUint32Size(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {!Int64}
+   */
+  getRepeatedUint64Element(fieldNumber, index) {
+    return this.kernel_.getRepeatedUint64Element(fieldNumber, index);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {!Iterable<!Int64>}
+   */
+  getRepeatedUint64Iterable(fieldNumber) {
+    return this.kernel_.getRepeatedUint64Iterable(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedUint64Size(fieldNumber) {
+    return this.kernel_.getRepeatedUint64Size(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {!ByteString}
+   */
+  getRepeatedBytesElement(fieldNumber, index) {
+    return this.kernel_.getRepeatedBytesElement(fieldNumber, index);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {!Iterable<!ByteString>}
+   */
+  getRepeatedBytesIterable(fieldNumber) {
+    return this.kernel_.getRepeatedBytesIterable(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedBytesSize(fieldNumber) {
+    return this.kernel_.getRepeatedBytesSize(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @return {string}
+   */
+  getRepeatedStringElement(fieldNumber, index) {
+    return this.kernel_.getRepeatedStringElement(fieldNumber, index);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {!Iterable<string>}
+   */
+  getRepeatedStringIterable(fieldNumber) {
+    return this.kernel_.getRepeatedStringIterable(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {number}
+   */
+  getRepeatedStringSize(fieldNumber) {
+    return this.kernel_.getRepeatedStringSize(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {function(!LazyAccessor):T} instanceCreator
+   * @param {number} index
+   * @return {T}
+   * @template T
+   */
+  getRepeatedMessageElement(fieldNumber, instanceCreator, index) {
+    return this.kernel_.getRepeatedMessageElement(
+        fieldNumber, instanceCreator, index);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {function(!LazyAccessor):T} instanceCreator
+   * @return {!Iterable<T>}
+   * @template T
+   */
+  getRepeatedMessageIterable(fieldNumber, instanceCreator) {
+    return this.kernel_.getRepeatedMessageIterable(
+        fieldNumber, instanceCreator);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @return {!Iterable<!LazyAccessor>}
+   * @template T
+   */
+  getRepeatedMessageAccessorIterable(fieldNumber) {
+    return this.kernel_.getRepeatedMessageAccessorIterable(fieldNumber);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {function(!LazyAccessor):T} instanceCreator
+   * @return {number}
+   * @template T
+   */
+  getRepeatedMessageSize(fieldNumber, instanceCreator) {
+    return this.kernel_.getRepeatedMessageSize(fieldNumber, instanceCreator);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {boolean} value
+   */
+  setBool(fieldNumber, value) {
+    this.kernel_.setBool(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!ByteString} value
+   */
+  setBytes(fieldNumber, value) {
+    this.kernel_.setBytes(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  setDouble(fieldNumber, value) {
+    this.kernel_.setDouble(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  setFixed32(fieldNumber, value) {
+    this.kernel_.setFixed32(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  setFixed64(fieldNumber, value) {
+    this.kernel_.setFixed64(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  setFloat(fieldNumber, value) {
+    this.kernel_.setFloat(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  setInt32(fieldNumber, value) {
+    this.kernel_.setInt32(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  setInt64(fieldNumber, value) {
+    this.kernel_.setInt64(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  setSfixed32(fieldNumber, value) {
+    this.kernel_.setSfixed32(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  setSfixed64(fieldNumber, value) {
+    this.kernel_.setSfixed64(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  setSint32(fieldNumber, value) {
+    this.kernel_.setSint32(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  setSint64(fieldNumber, value) {
+    this.kernel_.setSint64(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {string} value
+   */
+  setString(fieldNumber, value) {
+    this.kernel_.setString(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  setUint32(fieldNumber, value) {
+    this.kernel_.setUint32(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  setUint64(fieldNumber, value) {
+    this.kernel_.setUint64(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {T} value
+   * @template T
+   */
+  setMessage(fieldNumber, value) {
+    this.kernel_.setMessage(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {boolean} value
+   */
+  addPackedBoolElement(fieldNumber, value) {
+    this.kernel_.addPackedBoolElement(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<boolean>} values
+   */
+  addPackedBoolIterable(fieldNumber, values) {
+    this.kernel_.addPackedBoolIterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {boolean} value
+   */
+  addUnpackedBoolElement(fieldNumber, value) {
+    this.kernel_.addUnpackedBoolElement(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<boolean>} values
+   */
+  addUnpackedBoolIterable(fieldNumber, values) {
+    this.kernel_.addUnpackedBoolIterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {boolean} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedBoolElement(fieldNumber, index, value) {
+    this.kernel_.setPackedBoolElement(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<boolean>} values
+   */
+  setPackedBoolIterable(fieldNumber, values) {
+    this.kernel_.setPackedBoolIterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {boolean} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedBoolElement(fieldNumber, index, value) {
+    this.kernel_.setUnpackedBoolElement(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<boolean>} values
+   */
+  setUnpackedBoolIterable(fieldNumber, values) {
+    this.kernel_.setUnpackedBoolIterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addPackedDoubleElement(fieldNumber, value) {
+    this.kernel_.addPackedDoubleElement(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addPackedDoubleIterable(fieldNumber, values) {
+    this.kernel_.addPackedDoubleIterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addUnpackedDoubleElement(fieldNumber, value) {
+    this.kernel_.addUnpackedDoubleElement(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addUnpackedDoubleIterable(fieldNumber, values) {
+    this.kernel_.addUnpackedDoubleIterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedDoubleElement(fieldNumber, index, value) {
+    this.kernel_.setPackedDoubleElement(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setPackedDoubleIterable(fieldNumber, values) {
+    this.kernel_.setPackedDoubleIterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedDoubleElement(fieldNumber, index, value) {
+    this.kernel_.setUnpackedDoubleElement(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setUnpackedDoubleIterable(fieldNumber, values) {
+    this.kernel_.setUnpackedDoubleIterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addPackedFixed32Element(fieldNumber, value) {
+    this.kernel_.addPackedFixed32Element(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addPackedFixed32Iterable(fieldNumber, values) {
+    this.kernel_.addPackedFixed32Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addUnpackedFixed32Element(fieldNumber, value) {
+    this.kernel_.addUnpackedFixed32Element(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addUnpackedFixed32Iterable(fieldNumber, values) {
+    this.kernel_.addUnpackedFixed32Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedFixed32Element(fieldNumber, index, value) {
+    this.kernel_.setPackedFixed32Element(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setPackedFixed32Iterable(fieldNumber, values) {
+    this.kernel_.setPackedFixed32Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedFixed32Element(fieldNumber, index, value) {
+    this.kernel_.setUnpackedFixed32Element(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setUnpackedFixed32Iterable(fieldNumber, values) {
+    this.kernel_.setUnpackedFixed32Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  addPackedFixed64Element(fieldNumber, value) {
+    this.kernel_.addPackedFixed64Element(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  addPackedFixed64Iterable(fieldNumber, values) {
+    this.kernel_.addPackedFixed64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  addUnpackedFixed64Element(fieldNumber, value) {
+    this.kernel_.addUnpackedFixed64Element(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  addUnpackedFixed64Iterable(fieldNumber, values) {
+    this.kernel_.addUnpackedFixed64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedFixed64Element(fieldNumber, index, value) {
+    this.kernel_.setPackedFixed64Element(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  setPackedFixed64Iterable(fieldNumber, values) {
+    this.kernel_.setPackedFixed64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedFixed64Element(fieldNumber, index, value) {
+    this.kernel_.setUnpackedFixed64Element(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  setUnpackedFixed64Iterable(fieldNumber, values) {
+    this.kernel_.setUnpackedFixed64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addPackedFloatElement(fieldNumber, value) {
+    this.kernel_.addPackedFloatElement(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addPackedFloatIterable(fieldNumber, values) {
+    this.kernel_.addPackedFloatIterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addUnpackedFloatElement(fieldNumber, value) {
+    this.kernel_.addUnpackedFloatElement(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addUnpackedFloatIterable(fieldNumber, values) {
+    this.kernel_.addUnpackedFloatIterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedFloatElement(fieldNumber, index, value) {
+    this.kernel_.setPackedFloatElement(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setPackedFloatIterable(fieldNumber, values) {
+    this.kernel_.setPackedFloatIterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedFloatElement(fieldNumber, index, value) {
+    this.kernel_.setUnpackedFloatElement(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setUnpackedFloatIterable(fieldNumber, values) {
+    this.kernel_.setUnpackedFloatIterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addPackedInt32Element(fieldNumber, value) {
+    this.kernel_.addPackedInt32Element(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addPackedInt32Iterable(fieldNumber, values) {
+    this.kernel_.addPackedInt32Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addUnpackedInt32Element(fieldNumber, value) {
+    this.kernel_.addUnpackedInt32Element(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addUnpackedInt32Iterable(fieldNumber, values) {
+    this.kernel_.addUnpackedInt32Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedInt32Element(fieldNumber, index, value) {
+    this.kernel_.setPackedInt32Element(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setPackedInt32Iterable(fieldNumber, values) {
+    this.kernel_.setPackedInt32Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedInt32Element(fieldNumber, index, value) {
+    this.kernel_.setUnpackedInt32Element(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setUnpackedInt32Iterable(fieldNumber, values) {
+    this.kernel_.setUnpackedInt32Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  addPackedInt64Element(fieldNumber, value) {
+    this.kernel_.addPackedInt64Element(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  addPackedInt64Iterable(fieldNumber, values) {
+    this.kernel_.addPackedInt64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  addUnpackedInt64Element(fieldNumber, value) {
+    this.kernel_.addUnpackedInt64Element(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  addUnpackedInt64Iterable(fieldNumber, values) {
+    this.kernel_.addUnpackedInt64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedInt64Element(fieldNumber, index, value) {
+    this.kernel_.setPackedInt64Element(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  setPackedInt64Iterable(fieldNumber, values) {
+    this.kernel_.setPackedInt64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedInt64Element(fieldNumber, index, value) {
+    this.kernel_.setUnpackedInt64Element(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  setUnpackedInt64Iterable(fieldNumber, values) {
+    this.kernel_.setUnpackedInt64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addPackedSfixed32Element(fieldNumber, value) {
+    this.kernel_.addPackedSfixed32Element(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addPackedSfixed32Iterable(fieldNumber, values) {
+    this.kernel_.addPackedSfixed32Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addUnpackedSfixed32Element(fieldNumber, value) {
+    this.kernel_.addUnpackedSfixed32Element(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addUnpackedSfixed32Iterable(fieldNumber, values) {
+    this.kernel_.addUnpackedSfixed32Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedSfixed32Element(fieldNumber, index, value) {
+    this.kernel_.setPackedSfixed32Element(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setPackedSfixed32Iterable(fieldNumber, values) {
+    this.kernel_.setPackedSfixed32Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedSfixed32Element(fieldNumber, index, value) {
+    this.kernel_.setUnpackedSfixed32Element(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setUnpackedSfixed32Iterable(fieldNumber, values) {
+    this.kernel_.setUnpackedSfixed32Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  addPackedSfixed64Element(fieldNumber, value) {
+    this.kernel_.addPackedSfixed64Element(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  addPackedSfixed64Iterable(fieldNumber, values) {
+    this.kernel_.addPackedSfixed64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  addUnpackedSfixed64Element(fieldNumber, value) {
+    this.kernel_.addUnpackedSfixed64Element(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  addUnpackedSfixed64Iterable(fieldNumber, values) {
+    this.kernel_.addUnpackedSfixed64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedSfixed64Element(fieldNumber, index, value) {
+    this.kernel_.setPackedSfixed64Element(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  setPackedSfixed64Iterable(fieldNumber, values) {
+    this.kernel_.setPackedSfixed64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedSfixed64Element(fieldNumber, index, value) {
+    this.kernel_.setUnpackedSfixed64Element(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  setUnpackedSfixed64Iterable(fieldNumber, values) {
+    this.kernel_.setUnpackedSfixed64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addPackedSint32Element(fieldNumber, value) {
+    this.kernel_.addPackedSint32Element(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addPackedSint32Iterable(fieldNumber, values) {
+    this.kernel_.addPackedSint32Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addUnpackedSint32Element(fieldNumber, value) {
+    this.kernel_.addUnpackedSint32Element(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addUnpackedSint32Iterable(fieldNumber, values) {
+    this.kernel_.addUnpackedSint32Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedSint32Element(fieldNumber, index, value) {
+    this.kernel_.setPackedSint32Element(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setPackedSint32Iterable(fieldNumber, values) {
+    this.kernel_.setPackedSint32Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedSint32Element(fieldNumber, index, value) {
+    this.kernel_.setUnpackedSint32Element(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setUnpackedSint32Iterable(fieldNumber, values) {
+    this.kernel_.setUnpackedSint32Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  addPackedSint64Element(fieldNumber, value) {
+    this.kernel_.addPackedSint64Element(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  addPackedSint64Iterable(fieldNumber, values) {
+    this.kernel_.addPackedSint64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  addUnpackedSint64Element(fieldNumber, value) {
+    this.kernel_.addUnpackedSint64Element(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  addUnpackedSint64Iterable(fieldNumber, values) {
+    this.kernel_.addUnpackedSint64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedSint64Element(fieldNumber, index, value) {
+    this.kernel_.setPackedSint64Element(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  setPackedSint64Iterable(fieldNumber, values) {
+    this.kernel_.setPackedSint64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedSint64Element(fieldNumber, index, value) {
+    this.kernel_.setUnpackedSint64Element(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  setUnpackedSint64Iterable(fieldNumber, values) {
+    this.kernel_.setUnpackedSint64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addPackedUint32Element(fieldNumber, value) {
+    this.kernel_.addPackedUint32Element(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addPackedUint32Iterable(fieldNumber, values) {
+    this.kernel_.addPackedUint32Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} value
+   */
+  addUnpackedUint32Element(fieldNumber, value) {
+    this.kernel_.addUnpackedUint32Element(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  addUnpackedUint32Iterable(fieldNumber, values) {
+    this.kernel_.addUnpackedUint32Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedUint32Element(fieldNumber, index, value) {
+    this.kernel_.setPackedUint32Element(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setPackedUint32Iterable(fieldNumber, values) {
+    this.kernel_.setPackedUint32Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {number} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedUint32Element(fieldNumber, index, value) {
+    this.kernel_.setUnpackedUint32Element(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<number>} values
+   */
+  setUnpackedUint32Iterable(fieldNumber, values) {
+    this.kernel_.setUnpackedUint32Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  addPackedUint64Element(fieldNumber, value) {
+    this.kernel_.addPackedUint64Element(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  addPackedUint64Iterable(fieldNumber, values) {
+    this.kernel_.addPackedUint64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Int64} value
+   */
+  addUnpackedUint64Element(fieldNumber, value) {
+    this.kernel_.addUnpackedUint64Element(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  addUnpackedUint64Iterable(fieldNumber, values) {
+    this.kernel_.addUnpackedUint64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setPackedUint64Element(fieldNumber, index, value) {
+    this.kernel_.setPackedUint64Element(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  setPackedUint64Iterable(fieldNumber, values) {
+    this.kernel_.setPackedUint64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!Int64} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setUnpackedUint64Element(fieldNumber, index, value) {
+    this.kernel_.setUnpackedUint64Element(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!Int64>} values
+   */
+  setUnpackedUint64Iterable(fieldNumber, values) {
+    this.kernel_.setUnpackedUint64Iterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!ByteString>} values
+   */
+  setRepeatedBytesIterable(fieldNumber, values) {
+    this.kernel_.setRepeatedBytesIterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<!ByteString>} values
+   */
+  addRepeatedBytesIterable(fieldNumber, values) {
+    this.kernel_.addRepeatedBytesIterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {!ByteString} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setRepeatedBytesElement(fieldNumber, index, value) {
+    this.kernel_.setRepeatedBytesElement(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!ByteString} value
+   */
+  addRepeatedBytesElement(fieldNumber, value) {
+    this.kernel_.addRepeatedBytesElement(fieldNumber, value);
+  }
+
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<string>} values
+   */
+  setRepeatedStringIterable(fieldNumber, values) {
+    this.kernel_.setRepeatedStringIterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<string>} values
+   */
+  addRepeatedStringIterable(fieldNumber, values) {
+    this.kernel_.addRepeatedStringIterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {number} index
+   * @param {string} value
+   * @throws {!Error} if index is out of range when check mode is critical
+   */
+  setRepeatedStringElement(fieldNumber, index, value) {
+    this.kernel_.setRepeatedStringElement(fieldNumber, index, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {string} value
+   */
+  addRepeatedStringElement(fieldNumber, value) {
+    this.kernel_.addRepeatedStringElement(fieldNumber, value);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<T>} values
+   * @template T
+   */
+  setRepeatedMessageIterable(fieldNumber, values) {
+    this.kernel_.setRepeatedMessageIterable(fieldNumber, values);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {!Iterable<T>} values
+   * @param {function(!LazyAccessor):T} instanceCreator
+   * @template T
+   */
+  addRepeatedMessageIterable(fieldNumber, values, instanceCreator) {
+    this.kernel_.addRepeatedMessageIterable(
+        fieldNumber, values, instanceCreator);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {T} value
+   * @param {function(!LazyAccessor):T} instanceCreator
+   * @param {number} index
+   * @throws {!Error} if index is out of range when check mode is critical
+   * @template T
+   */
+  setRepeatedMessageElement(fieldNumber, value, instanceCreator, index) {
+    this.kernel_.setRepeatedMessageElement(
+        fieldNumber, value, instanceCreator, index);
+  }
+
+  /**
+   * @param {number} fieldNumber
+   * @param {T} value
+   * @param {function(!LazyAccessor):T} instanceCreator
+   * @template T
+   */
+  addRepeatedMessageElement(fieldNumber, value, instanceCreator) {
+    this.kernel_.addRepeatedMessageElement(fieldNumber, value, instanceCreator);
+  }
+}
+
+exports = TestMessage;

+ 44 - 0
js/experimental/runtime/testing/ensure_custom_equality_test.js

@@ -0,0 +1,44 @@
+/**
+ * @fileoverview Tests in this file will fail if our custom equality have not
+ * been installed.
+ * see b/131864652
+ */
+
+goog.module('protobuf.testing.ensureCustomEqualityTest');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const ByteString = goog.require('protobuf.ByteString');
+
+describe('Custom equality', () => {
+  it('ensure that custom equality for ArrayBuffer is installed', () => {
+    const buffer1 = new ArrayBuffer(4);
+    const buffer2 = new ArrayBuffer(4);
+    const array = new Uint8Array(buffer1);
+    array[0] = 1;
+    expect(buffer1).not.toEqual(buffer2);
+  });
+
+  it('ensure that custom equality for ByteString is installed', () => {
+    const HALLO_IN_BASE64 = 'aGFsbG8=';
+    const BYTES_WITH_HALLO = new Uint8Array([
+      'h'.charCodeAt(0),
+      'a'.charCodeAt(0),
+      'l'.charCodeAt(0),
+      'l'.charCodeAt(0),
+      'o'.charCodeAt(0),
+    ]);
+
+    const byteString1 = ByteString.fromBase64String(HALLO_IN_BASE64);
+    const byteString2 = ByteString.fromArrayBufferView(BYTES_WITH_HALLO);
+    expect(byteString1).toEqual(byteString2);
+  });
+
+  it('ensure that custom equality for BufferDecoder is installed', () => {
+    const arrayBuffer1 = new Uint8Array([0, 1, 2]).buffer;
+    const arrayBuffer2 = new Uint8Array([0, 1, 2]).buffer;
+
+    const bufferDecoder1 = BufferDecoder.fromArrayBuffer(arrayBuffer1);
+    const bufferDecoder2 = BufferDecoder.fromArrayBuffer(arrayBuffer2);
+    expect(bufferDecoder1).toEqual(bufferDecoder2);
+  });
+});

+ 88 - 0
js/experimental/runtime/testing/jasmine_protobuf.js

@@ -0,0 +1,88 @@
+/**
+ * @fileoverview Installs our custom equality matchers in Jasmine.
+ */
+goog.module('protobuf.testing.jasmineProtoBuf');
+
+const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
+const ByteString = goog.require('protobuf.ByteString');
+const {arrayBufferEqual} = goog.require('protobuf.binary.typedArrays');
+
+/**
+ * A function that ensures custom equality for ByteStrings.
+ * Since Jasmine compare structure by default Bytestrings might be equal that
+ * are not equal since ArrayBuffers still compare content in g3.
+ * (Jasmine fix upstream: https://github.com/jasmine/jasmine/issues/1687)
+ * Also ByteStrings that are equal might compare non equal in jasmine of the
+ * base64 string has been initialized.
+ * @param {*} first
+ * @param {*} second
+ * @return {boolean|undefined}
+ */
+const byteStringEquality = (first, second) => {
+  if (second instanceof ByteString) {
+    return second.equals(first);
+  }
+
+  // Intentionally not returning anything, this signals to jasmine that we
+  // did not perform any equality on the given objects.
+};
+
+/**
+ * A function that ensures custom equality for ArrayBuffers.
+ * By default Jasmine does not compare the content of an ArrayBuffer and thus
+ * will return true for buffers with the same length but different content.
+ * @param {*} first
+ * @param {*} second
+ * @return {boolean|undefined}
+ */
+const arrayBufferCustomEquality = (first, second) => {
+  if (first instanceof ArrayBuffer && second instanceof ArrayBuffer) {
+    return arrayBufferEqual(first, second);
+  }
+  // Intentionally not returning anything, this signals to jasmine that we
+  // did not perform any equality on the given objects.
+};
+
+/**
+ * A function that ensures custom equality for ArrayBuffers.
+ * By default Jasmine does not compare the content of an ArrayBuffer and thus
+ * will return true for buffers with the same length but different content.
+ * @param {*} first
+ * @param {*} second
+ * @return {boolean|undefined}
+ */
+const bufferDecoderCustomEquality = (first, second) => {
+  if (first instanceof BufferDecoder && second instanceof BufferDecoder) {
+    return first.asByteString().equals(second.asByteString());
+  }
+  // Intentionally not returning anything, this signals to jasmine that we
+  // did not perform any equality on the given objects.
+};
+
+/**
+ * Overrides the default ArrayBuffer toString method ([object ArrayBuffer]) with
+ * a more readable representation.
+ */
+function overrideArrayBufferToString() {
+  /**
+   * Returns the hex values of the underlying bytes of the ArrayBuffer.
+   *
+   * @override
+   * @return {string}
+   */
+  ArrayBuffer.prototype.toString = function() {
+    const arr = Array.from(new Uint8Array(this));
+    return 'ArrayBuffer[' +
+        arr.map((b) => '0x' + (b & 0xFF).toString(16).toUpperCase())
+            .join(', ') +
+        ']';
+  };
+}
+
+beforeEach(() => {
+  jasmine.addCustomEqualityTester(arrayBufferCustomEquality);
+  jasmine.addCustomEqualityTester(bufferDecoderCustomEquality);
+  jasmine.addCustomEqualityTester(byteStringEquality);
+
+  overrideArrayBufferToString();
+});