Project import generated by Copybara

PiperOrigin-RevId: 293911829
diff --git a/js/experimental/runtime/kernel/buffer_decoder.js b/js/experimental/runtime/kernel/buffer_decoder.js
index 614659a..2b4157e 100644
--- a/js/experimental/runtime/kernel/buffer_decoder.js
+++ b/js/experimental/runtime/kernel/buffer_decoder.js
@@ -6,7 +6,7 @@
 
 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 {POLYFILL_TEXT_ENCODING, checkCriticalPositionIndex, checkCriticalState, 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');
@@ -72,7 +72,9 @@
     /** @private @const {number} */
     this.startIndex_ = startIndex;
     /** @private @const {number} */
-    this.endIndex_ = this.startIndex_ + length;
+    this.endIndex_ = startIndex + length;
+    /** @private {number} */
+    this.cursor_ = startIndex;
   }
 
   /**
@@ -100,11 +102,37 @@
   }
 
   /**
+   * Returns the start position of the next data, i.e. end position of the last
+   * read data + 1.
+   * @return {number}
+   */
+  cursor() {
+    return this.cursor_;
+  }
+
+  /**
+   * Sets the cursor to the specified position.
+   * @param {number} position
+   */
+  setCursor(position) {
+    this.cursor_ = position;
+  }
+
+  /**
+   * Returns if there is more data to read after the current cursor position.
+   * @return {boolean}
+   */
+  hasNext() {
+    return this.cursor_ < this.endIndex_;
+  }
+
+  /**
    * Returns a float32 from a given index
    * @param {number} index
    * @return {number}
    */
   getFloat32(index) {
+    this.cursor_ = index + 4;
     return this.dataView_.getFloat32(index, true);
   }
 
@@ -114,6 +142,7 @@
    * @return {number}
    */
   getFloat64(index) {
+    this.cursor_ = index + 8;
     return this.dataView_.getFloat64(index, true);
   }
 
@@ -123,45 +152,40 @@
    * @return {number}
    */
   getInt32(index) {
+    this.cursor_ = index + 4;
     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) {
+    this.cursor_ = index + 4;
     return this.dataView_.getUint32(index, true);
   }
 
   /**
-   * Returns two JS numbers each representing 32 bits of a 64 bit number.
+   * Returns two JS numbers each representing 32 bits of a 64 bit number. Also
+   * sets the cursor to the start of the next block of data.
    * @param {number} index
-   * @return {{lowBits: number, highBits: number, dataStart: number}}
+   * @return {{lowBits: number, highBits: number}}
    */
   getVarint(index) {
-    let start = index;
+    this.cursor_ = index;
     let lowBits = 0;
     let highBits = 0;
 
     for (let shift = 0; shift < 28; shift += 7) {
-      const b = this.dataView_.getUint8(start++);
+      const b = this.dataView_.getUint8(this.cursor_++);
       lowBits |= (b & 0x7F) << shift;
       if ((b & 0x80) === 0) {
-        return {lowBits, highBits, dataStart: start};
+        return {lowBits, highBits};
       }
     }
 
-    const middleByte = this.dataView_.getUint8(start++);
+    const middleByte = this.dataView_.getUint8(this.cursor_++);
 
     // last four bits of the first 32 bit number
     lowBits |= (middleByte & 0x0F) << 28;
@@ -170,36 +194,100 @@
     highBits = (middleByte & 0x70) >> 4;
 
     if ((middleByte & 0x80) === 0) {
-      return {lowBits, highBits, dataStart: start};
+      return {lowBits, highBits};
     }
 
 
     for (let shift = 3; shift <= 31; shift += 7) {
-      const b = this.dataView_.getUint8(start++);
+      const b = this.dataView_.getUint8(this.cursor_++);
       highBits |= (b & 0x7F) << shift;
       if ((b & 0x80) === 0) {
-        return {lowBits, highBits, dataStart: start};
+        return {lowBits, highBits};
       }
     }
 
-    checkState(false, 'Data is longer than 10 bytes');
-    return {lowBits, highBits, dataStart: start};
+    checkCriticalState(false, 'Data is longer than 10 bytes');
+
+    return {lowBits, highBits};
   }
 
   /**
-   * Skips over a varint at a given index and returns the next position.
+   * Returns an unsigned int32 number at the current cursor position. The upper
+   * bits are discarded if the varint is longer than 32 bits. Also sets the
+   * cursor to the start of the next block of data.
+   * @return {number}
+   */
+  getUnsignedVarint32() {
+    let b = this.dataView_.getUint8(this.cursor_++);
+    let result = b & 0x7F;
+    if ((b & 0x80) === 0) {
+      return result;
+    }
+
+    b = this.dataView_.getUint8(this.cursor_++);
+    result |= (b & 0x7F) << 7;
+    if ((b & 0x80) === 0) {
+      return result;
+    }
+
+    b = this.dataView_.getUint8(this.cursor_++);
+    result |= (b & 0x7F) << 14;
+    if ((b & 0x80) === 0) {
+      return result;
+    }
+
+    b = this.dataView_.getUint8(this.cursor_++);
+    result |= (b & 0x7F) << 21;
+    if ((b & 0x80) === 0) {
+      return result;
+    }
+
+    // Extract only last 4 bits
+    b = this.dataView_.getUint8(this.cursor_++);
+    result |= (b & 0x0F) << 28;
+
+    for (let readBytes = 5; ((b & 0x80) !== 0) && readBytes < 10; readBytes++) {
+      b = this.dataView_.getUint8(this.cursor_++);
+    }
+
+    checkCriticalState((b & 0x80) === 0, 'Data is longer than 10 bytes');
+
+    // Result can be have 32 bits, convert it to unsigned
+    return result >>> 0;
+  }
+
+  /**
+   * Returns an unsigned int32 number at the specified index. The upper bits are
+   * discarded if the varint is longer than 32 bits. Also sets the cursor to the
+   * start of the next block of data.
+   * @param {number} index
+   * @return {number}
+   */
+  getUnsignedVarint32At(index) {
+    this.cursor_ = index;
+    return this.getUnsignedVarint32();
+  }
+
+  /**
+   * Seeks forward by the given amount.
+   * @param {number} skipAmount
+   * @package
+   */
+  skip(skipAmount) {
+    this.cursor_ += skipAmount;
+    checkCriticalPositionIndex(this.cursor_, this.endIndex_);
+  }
+
+  /**
+   * Skips over a varint at a given index.
    * @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());
+    this.cursor_ = index;
+    while (this.dataView_.getUint8(this.cursor_++) & 0x80) {
     }
-    checkCriticalPositionIndex(cursor, index + 10);
-    return cursor;
+    checkCriticalPositionIndex(this.cursor_, index + 10);
   }
 
   /**
diff --git a/js/experimental/runtime/kernel/buffer_decoder_test.js b/js/experimental/runtime/kernel/buffer_decoder_test.js
index aed045a..7c549f8 100644
--- a/js/experimental/runtime/kernel/buffer_decoder_test.js
+++ b/js/experimental/runtime/kernel/buffer_decoder_test.js
@@ -5,7 +5,7 @@
 goog.module('protobuf.binary.varintsTest');
 
 const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
-const {CHECK_CRITICAL_BOUNDS, CHECK_STATE} = goog.require('protobuf.internal.checks');
+const {CHECK_CRITICAL_STATE, CHECK_STATE} = goog.require('protobuf.internal.checks');
 
 goog.setTestOnly();
 
@@ -17,25 +17,47 @@
   return new Uint8Array(bytes).buffer;
 }
 
+describe('setCursor does', () => {
+  it('set the cursor at the position specified', () => {
+    const bufferDecoder =
+        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x0, 0x1));
+    expect(bufferDecoder.cursor()).toBe(0);
+    bufferDecoder.setCursor(1);
+    expect(bufferDecoder.cursor()).toBe(1);
+  });
+});
+
+describe('skip does', () => {
+  it('advance the cursor', () => {
+    const bufferDecoder =
+        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x0, 0x1, 0x2));
+    bufferDecoder.setCursor(1);
+    bufferDecoder.skip(1);
+    expect(bufferDecoder.cursor()).toBe(2);
+  });
+});
+
 describe('Skip varint does', () => {
   it('skip a varint', () => {
     const bufferDecoder =
         BufferDecoder.fromArrayBuffer(createArrayBuffer(0x01));
-    expect(bufferDecoder.skipVarint(0)).toBe(1);
+    bufferDecoder.skipVarint(0);
+    expect(bufferDecoder.cursor()).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) {
+    if (CHECK_CRITICAL_STATE) {
       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);
+      bufferDecoder.skipVarint(0);
+      expect(bufferDecoder.cursor()).toBe(11);
     }
   });
 
@@ -50,28 +72,144 @@
   it('read zero', () => {
     const bufferDecoder =
         BufferDecoder.fromArrayBuffer(createArrayBuffer(0x00));
-    const {dataStart, lowBits, highBits} = bufferDecoder.getVarint(0);
-    expect(dataStart).toBe(1);
+    const {lowBits, highBits} = bufferDecoder.getVarint(0);
     expect(lowBits).toBe(0);
     expect(highBits).toBe(0);
+    expect(bufferDecoder.cursor()).toBe(1);
   });
 
   it('read one', () => {
     const bufferDecoder =
         BufferDecoder.fromArrayBuffer(createArrayBuffer(0x01));
-    const {dataStart, lowBits, highBits} = bufferDecoder.getVarint(0);
-    expect(dataStart).toBe(1);
+    const {lowBits, highBits} = bufferDecoder.getVarint(0);
     expect(lowBits).toBe(1);
     expect(highBits).toBe(0);
+    expect(bufferDecoder.cursor()).toBe(1);
   });
 
   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);
+    const {lowBits, highBits} = bufferDecoder.getVarint(0);
     expect(lowBits).toBe(-1);
     expect(highBits).toBe(-1);
+    expect(bufferDecoder.cursor()).toBe(10);
+  });
+});
+
+describe('readUnsignedVarint32 does', () => {
+  it('read zero', () => {
+    const bufferDecoder =
+        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x00));
+    const result = bufferDecoder.getUnsignedVarint32();
+    expect(result).toBe(0);
+    expect(bufferDecoder.cursor()).toBe(1);
+  });
+
+  it('read one', () => {
+    const bufferDecoder =
+        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x01));
+    const result = bufferDecoder.getUnsignedVarint32();
+    expect(result).toBe(1);
+    expect(bufferDecoder.cursor()).toBe(1);
+  });
+
+  it('read max int32', () => {
+    const bufferDecoder = BufferDecoder.fromArrayBuffer(
+        createArrayBuffer(0xFF, 0xFF, 0xFF, 0xFF, 0x0F));
+    const result = bufferDecoder.getUnsignedVarint32();
+    expect(result).toBe(4294967295);
+    expect(bufferDecoder.cursor()).toBe(5);
+  });
+
+  it('read max value', () => {
+    const bufferDecoder = BufferDecoder.fromArrayBuffer(createArrayBuffer(
+        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01));
+    const result = bufferDecoder.getUnsignedVarint32();
+    expect(result).toBe(4294967295);
+    expect(bufferDecoder.cursor()).toBe(10);
+  });
+
+  it('fail if data is longer than 10 bytes', () => {
+    const bufferDecoder = BufferDecoder.fromArrayBuffer(createArrayBuffer(
+        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01));
+    if (CHECK_CRITICAL_STATE) {
+      expect(() => bufferDecoder.getUnsignedVarint32()).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 result = bufferDecoder.getUnsignedVarint32();
+      expect(result).toBe(4294967295);
+      expect(bufferDecoder.cursor()).toBe(10);
+    }
+  });
+});
+
+describe('readUnsignedVarint32At does', () => {
+  it('reads from a specific index', () => {
+    const bufferDecoder =
+        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x1, 0x2));
+    const result = bufferDecoder.getUnsignedVarint32At(1);
+    expect(result).toBe(2);
+    expect(bufferDecoder.cursor()).toBe(2);
+  });
+});
+
+describe('getFloat32 does', () => {
+  it('read one', () => {
+    const bufferDecoder = BufferDecoder.fromArrayBuffer(
+        createArrayBuffer(0x00, 0x00, 0x80, 0x3F));
+    const result = bufferDecoder.getFloat32(0);
+    expect(result).toBe(1);
+    expect(bufferDecoder.cursor()).toBe(4);
+  });
+});
+
+describe('getFloat64 does', () => {
+  it('read one', () => {
+    const bufferDecoder = BufferDecoder.fromArrayBuffer(
+        createArrayBuffer(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F));
+    const result = bufferDecoder.getFloat64(0);
+    expect(result).toBe(1);
+    expect(bufferDecoder.cursor()).toBe(8);
+  });
+});
+
+describe('getInt32 does', () => {
+  it('read one', () => {
+    const bufferDecoder = BufferDecoder.fromArrayBuffer(
+        createArrayBuffer(0x01, 0x00, 0x00, 0x00));
+    const result = bufferDecoder.getInt32(0);
+    expect(result).toBe(1);
+    expect(bufferDecoder.cursor()).toBe(4);
+  });
+
+  it('read minus one', () => {
+    const bufferDecoder = BufferDecoder.fromArrayBuffer(
+        createArrayBuffer(0xFF, 0xFF, 0xFF, 0xFF));
+    const result = bufferDecoder.getInt32(0);
+    expect(result).toBe(-1);
+    expect(bufferDecoder.cursor()).toBe(4);
+  });
+});
+
+describe('getUint32 does', () => {
+  it('read one', () => {
+    const bufferDecoder =
+        BufferDecoder.fromArrayBuffer(createArrayBuffer(0x01, 0x00, 0x00, 0x0));
+    const result = bufferDecoder.getUint32(0);
+    expect(result).toBe(1);
+    expect(bufferDecoder.cursor()).toBe(4);
+  });
+
+  it('read max uint32', () => {
+    const bufferDecoder = BufferDecoder.fromArrayBuffer(
+        createArrayBuffer(0xFF, 0xFF, 0xFF, 0xFF));
+    const result = bufferDecoder.getUint32(0);
+    expect(result).toBe(4294967295);
+    expect(bufferDecoder.cursor()).toBe(4);
   });
 });
 
diff --git a/js/experimental/runtime/kernel/indexer.js b/js/experimental/runtime/kernel/indexer.js
index c247c2f..e281994 100644
--- a/js/experimental/runtime/kernel/indexer.js
+++ b/js/experimental/runtime/kernel/indexer.js
@@ -8,7 +8,7 @@
 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');
+const {checkCriticalElementIndex, checkCriticalState} = goog.require('protobuf.internal.checks');
 
 /**
  * Appends a new entry in the index array for the given field number.
@@ -57,8 +57,6 @@
   constructor(bufferDecoder) {
     /** @private @const {!BufferDecoder} */
     this.bufferDecoder_ = bufferDecoder;
-    /** @private {number} */
-    this.cursor_ = bufferDecoder.startIndex();
   }
 
   /**
@@ -66,15 +64,18 @@
    * @return {!Storage<!Field>}
    */
   index(pivot) {
+    this.bufferDecoder_.setCursor(this.bufferDecoder_.startIndex());
+
     const storage = new Storage(pivot);
-    while (this.hasNextByte_()) {
-      const tag = this.readVarInt32_();
+    while (this.bufferDecoder_.hasNext()) {
+      const tag = this.bufferDecoder_.getUnsignedVarint32();
       const wireType = tagToWireType(tag);
       const fieldNumber = tagToFieldNumber(tag);
       checkCriticalState(
           fieldNumber > 0, `Invalid field number ${fieldNumber}`);
 
-      addIndexEntry(storage, fieldNumber, wireType, this.cursor_);
+      addIndexEntry(
+          storage, fieldNumber, wireType, this.bufferDecoder_.cursor());
 
       checkCriticalState(
           !this.skipField_(wireType, fieldNumber),
@@ -93,14 +94,18 @@
   skipField_(wireType, fieldNumber) {
     switch (wireType) {
       case WireType.VARINT:
-        this.cursor_ = this.bufferDecoder_.skipVarint(this.cursor_);
+        checkCriticalElementIndex(
+            this.bufferDecoder_.cursor(), this.bufferDecoder_.endIndex());
+        this.bufferDecoder_.skipVarint(this.bufferDecoder_.cursor());
         return false;
       case WireType.FIXED64:
-        this.skip_(8);
+        this.bufferDecoder_.skip(8);
         return false;
       case WireType.DELIMITED:
-        const length = this.readVarInt32_();
-        this.skip_(length);
+        checkCriticalElementIndex(
+            this.bufferDecoder_.cursor(), this.bufferDecoder_.endIndex());
+        const length = this.bufferDecoder_.getUnsignedVarint32();
+        this.bufferDecoder_.skip(length);
         return false;
       case WireType.START_GROUP:
         checkCriticalState(this.skipGroup_(fieldNumber), 'No end group found.');
@@ -109,7 +114,7 @@
         // Signal that we found a stop group to the caller
         return true;
       case WireType.FIXED32:
-        this.skip_(4);
+        this.bufferDecoder_.skip(4);
         return false;
       default:
         throw new Error(`Invalid wire type: ${wireType}`);
@@ -117,16 +122,6 @@
   }
 
   /**
-   * 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.
@@ -138,8 +133,8 @@
     // 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_();
+    while (this.bufferDecoder_.hasNext()) {
+      const tag = this.bufferDecoder_.getUnsignedVarint32();
       const wireType = tagToWireType(tag);
       const fieldNumber = tagToFieldNumber(tag);
 
@@ -153,26 +148,6 @@
     }
     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();
-  }
 }
 
 /**
@@ -186,7 +161,6 @@
   return new Indexer(bufferDecoder).index(pivot);
 }
 
-
 exports = {
   buildIndex,
 };
diff --git a/js/experimental/runtime/kernel/reader.js b/js/experimental/runtime/kernel/reader.js
index fec79cf..2b80e15 100644
--- a/js/experimental/runtime/kernel/reader.js
+++ b/js/experimental/runtime/kernel/reader.js
@@ -6,6 +6,7 @@
 const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
 const ByteString = goog.require('protobuf.ByteString');
 const Int64 = goog.require('protobuf.Int64');
+const {checkState} = goog.require('protobuf.internal.checks');
 
 
 /******************************************************************************
@@ -13,18 +14,6 @@
  ******************************************************************************/
 
 /**
- * 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.
@@ -32,11 +21,12 @@
  * @package
  */
 function readBool(bufferDecoder, start) {
-  return readBoolValue(bufferDecoder, start).value;
+  const {lowBits, highBits} = bufferDecoder.getVarint(start);
+  return lowBits !== 0 || highBits !== 0;
 }
 
 /**
- * Reads a double value from the binary bytes.
+ * Reads a ByteString value from the binary bytes.
  * @param {!BufferDecoder} bufferDecoder Binary format encoded bytes.
  * @param {number} start Start of the data.
  * @return {!ByteString}
@@ -49,39 +39,15 @@
 /**
  * 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};
+  // 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 bufferDecoder.getUnsignedVarint32At(start) | 0;
 }
 
 /**
@@ -92,7 +58,8 @@
  * @package
  */
 function readInt64(bufferDecoder, start) {
-  return readInt64Value(bufferDecoder, start).value;
+  const {lowBits, highBits} = bufferDecoder.getVarint(start);
+  return Int64.fromBits(lowBits, highBits);
 }
 
 /**
@@ -132,45 +99,15 @@
 
 /**
  * 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
-  };
+  const bits = bufferDecoder.getUnsignedVarint32At(start);
+  // Truncate upper bits and convert from zig zag to signd int
+  return (bits >>> 1) ^ -(bits & 0x01);
 }
 
 /**
@@ -181,7 +118,11 @@
  * @package
  */
 function readSint64(bufferDecoder, start) {
-  return readSint64Value(bufferDecoder, start).value;
+  const {lowBits, highBits} = bufferDecoder.getVarint(start);
+  const sign = -(lowBits & 0x01);
+  const decodedLowerBits = ((lowBits >>> 1) | (highBits & 0x01) << 31) ^ sign;
+  const decodedUpperBits = (highBits >>> 1) ^ sign;
+  return Int64.fromBits(decodedLowerBits, decodedUpperBits);
 }
 
 /**
@@ -192,9 +133,8 @@
  * @package
  */
 function readDelimited(bufferDecoder, start) {
-  const {lowBits, dataStart} = bufferDecoder.getVarint(start);
-  const unsignedLength = lowBits >>> 0;
-  return bufferDecoder.subBufferDecoder(dataStart, unsignedLength);
+  const unsignedLength = bufferDecoder.getUnsignedVarint32At(start);
+  return bufferDecoder.subBufferDecoder(bufferDecoder.cursor(), unsignedLength);
 }
 
 /**
@@ -210,25 +150,13 @@
 
 /**
  * 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;
+  return bufferDecoder.getUnsignedVarint32At(start);
 }
 
 /**
@@ -266,7 +194,7 @@
  * @package
  */
 function readPackedBool(bufferDecoder, start) {
-  return readPackedVariableLength(bufferDecoder, start, readBoolValue);
+  return readPacked(bufferDecoder, start, readBool);
 }
 
 /**
@@ -278,7 +206,7 @@
  * @package
  */
 function readPackedDouble(bufferDecoder, start) {
-  return readPackedFixed(bufferDecoder, start, 8, readDouble);
+  return readPacked(bufferDecoder, start, readDouble);
 }
 
 /**
@@ -290,7 +218,7 @@
  * @package
  */
 function readPackedFixed32(bufferDecoder, start) {
-  return readPackedFixed(bufferDecoder, start, 4, readFixed32);
+  return readPacked(bufferDecoder, start, readFixed32);
 }
 
 /**
@@ -302,7 +230,7 @@
  * @package
  */
 function readPackedFloat(bufferDecoder, start) {
-  return readPackedFixed(bufferDecoder, start, 4, readFloat);
+  return readPacked(bufferDecoder, start, readFloat);
 }
 
 /**
@@ -314,7 +242,7 @@
  * @package
  */
 function readPackedInt32(bufferDecoder, start) {
-  return readPackedVariableLength(bufferDecoder, start, readInt32Value);
+  return readPacked(bufferDecoder, start, readInt32);
 }
 
 /**
@@ -326,7 +254,7 @@
  * @package
  */
 function readPackedInt64(bufferDecoder, start) {
-  return readPackedVariableLength(bufferDecoder, start, readInt64Value);
+  return readPacked(bufferDecoder, start, readInt64);
 }
 
 /**
@@ -338,7 +266,7 @@
  * @package
  */
 function readPackedSfixed32(bufferDecoder, start) {
-  return readPackedFixed(bufferDecoder, start, 4, readSfixed32);
+  return readPacked(bufferDecoder, start, readSfixed32);
 }
 
 /**
@@ -350,7 +278,7 @@
  * @package
  */
 function readPackedSfixed64(bufferDecoder, start) {
-  return readPackedFixed(bufferDecoder, start, 8, readSfixed64);
+  return readPacked(bufferDecoder, start, readSfixed64);
 }
 
 /**
@@ -362,7 +290,7 @@
  * @package
  */
 function readPackedSint32(bufferDecoder, start) {
-  return readPackedVariableLength(bufferDecoder, start, readSint32Value);
+  return readPacked(bufferDecoder, start, readSint32);
 }
 
 /**
@@ -374,7 +302,7 @@
  * @package
  */
 function readPackedSint64(bufferDecoder, start) {
-  return readPackedVariableLength(bufferDecoder, start, readSint64Value);
+  return readPacked(bufferDecoder, start, readSint64);
 }
 
 /**
@@ -386,51 +314,25 @@
  * @package
  */
 function readPackedUint32(bufferDecoder, start) {
-  return readPackedVariableLength(bufferDecoder, start, readUint32Value);
+  return readPacked(bufferDecoder, start, readUint32);
 }
 
 /**
- * Read packed variable length values.
+ * Read packed 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;
+function readPacked(bufferDecoder, start, valueFunction) {
+  const /** !Array<T> */ result = [];
+  const unsignedLength = bufferDecoder.getUnsignedVarint32At(start);
+  const dataStart = bufferDecoder.cursor();
+  while (bufferDecoder.cursor() < dataStart + unsignedLength) {
+    checkState(bufferDecoder.cursor() > 0);
+    result.push(valueFunction(bufferDecoder, bufferDecoder.cursor()));
   }
   return result;
 }
diff --git a/js/experimental/runtime/kernel/reader_test.js b/js/experimental/runtime/kernel/reader_test.js
index a0cbcae..dd02086 100644
--- a/js/experimental/runtime/kernel/reader_test.js
+++ b/js/experimental/runtime/kernel/reader_test.js
@@ -12,7 +12,7 @@
 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 {CHECK_CRITICAL_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');
@@ -45,7 +45,7 @@
 describe('Read bool does', () => {
   for (const pair of getBoolPairs()) {
     it(`decode ${pair.name}`, () => {
-      if (pair.error && CHECK_STATE) {
+      if (pair.error && CHECK_CRITICAL_STATE) {
         expect(() => reader.readBool(pair.bufferDecoder, 0)).toThrow();
       } else {
         const d = reader.readBool(
@@ -120,7 +120,7 @@
 
   for (const pair of getInt32Pairs()) {
     it(`decode ${pair.name}`, () => {
-      if (pair.error && CHECK_STATE) {
+      if (pair.error && CHECK_CRITICAL_STATE) {
         expect(() => reader.readInt32(pair.bufferDecoder, 0)).toThrow();
       } else {
         const d = reader.readInt32(pair.bufferDecoder, 0);
@@ -166,7 +166,7 @@
 
   for (const pair of getSint32Pairs()) {
     it(`decode ${pair.name}`, () => {
-      if (pair.error && CHECK_STATE) {
+      if (pair.error && CHECK_CRITICAL_STATE) {
         expect(() => reader.readSint32(pair.bufferDecoder, 0)).toThrow();
       } else {
         const d = reader.readSint32(pair.bufferDecoder, 0);
@@ -184,7 +184,7 @@
 
   for (const pair of getInt64Pairs()) {
     it(`decode ${pair.name}`, () => {
-      if (pair.error && CHECK_STATE) {
+      if (pair.error && CHECK_CRITICAL_STATE) {
         expect(() => reader.readInt64(pair.bufferDecoder, 0)).toThrow();
       } else {
         const d = reader.readInt64(pair.bufferDecoder, 0);
@@ -202,7 +202,7 @@
 
   for (const pair of getSint64Pairs()) {
     it(`decode ${pair.name}`, () => {
-      if (pair.error && CHECK_STATE) {
+      if (pair.error && CHECK_CRITICAL_STATE) {
         expect(() => reader.readSint64(pair.bufferDecoder, 0)).toThrow();
       } else {
         const d = reader.readSint64(pair.bufferDecoder, 0);
@@ -220,7 +220,7 @@
 
   for (const pair of getUint32Pairs()) {
     it(`decode ${pair.name}`, () => {
-      if (pair.error && CHECK_STATE) {
+      if (pair.error && CHECK_CRITICAL_STATE) {
         expect(() => reader.readUint32(pair.bufferDecoder, 0)).toThrow();
       } else {
         const d = reader.readUint32(pair.bufferDecoder, 0);
diff --git a/js/experimental/runtime/kernel/writer.js b/js/experimental/runtime/kernel/writer.js
index 1fc7418..a0178d2 100644
--- a/js/experimental/runtime/kernel/writer.js
+++ b/js/experimental/runtime/kernel/writer.js
@@ -450,12 +450,13 @@
   getLength_(bufferDecoder, start, wireType) {
     switch (wireType) {
       case WireType.VARINT:
-        return bufferDecoder.skipVarint(start) - start;
+        bufferDecoder.skipVarint(start);
+        return bufferDecoder.cursor() - start;
       case WireType.FIXED64:
         return 8;
       case WireType.DELIMITED:
-        const {lowBits: dataLength, dataStart} = bufferDecoder.getVarint(start);
-        return dataLength + dataStart - start;
+        const dataLength = bufferDecoder.getUnsignedVarint32At(start);
+        return dataLength + bufferDecoder.cursor() - start;
       case WireType.START_GROUP:
         return this.getGroupLength_(bufferDecoder, start);
       case WireType.FIXED32:
@@ -477,12 +478,13 @@
     // corresponding stop group
     let cursor = start;
     while (cursor < bufferDecoder.endIndex()) {
-      const {lowBits: tag, dataStart} = bufferDecoder.getVarint(cursor);
+      const tag = bufferDecoder.getUnsignedVarint32At(cursor);
       const wireType = /** @type {!WireType} */ (tag & 0x07);
       if (wireType === WireType.END_GROUP) {
-        return dataStart - start;
+        return bufferDecoder.cursor() - start;
       }
-      cursor = dataStart + this.getLength_(bufferDecoder, dataStart, wireType);
+      cursor = bufferDecoder.cursor() +
+          this.getLength_(bufferDecoder, bufferDecoder.cursor(), wireType);
     }
     throw new Error('No end group found');
   }