Project import generated by Copybara.

PiperOrigin-RevId: 237361882
Change-Id: I109a68f44db867b20f8c6a7732b0ce657133e52a
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc
new file mode 100644
index 0000000..bcd9db6
--- /dev/null
+++ b/quic/core/quic_framer_test.cc
@@ -0,0 +1,12929 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/quic_framer.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <map>
+#include <memory>
+#include <vector>
+
+#include "net/third_party/quiche/src/quic/core/crypto/null_decrypter.h"
+#include "net/third_party/quiche/src/quic/core/crypto/null_encrypter.h"
+#include "net/third_party/quiche/src/quic/core/crypto/quic_decrypter.h"
+#include "net/third_party/quiche/src/quic/core/crypto/quic_encrypter.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection_id.h"
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_framer_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/simple_data_producer.h"
+
+using testing::_;
+using testing::Return;
+using testing::Truly;
+
+namespace quic {
+namespace test {
+namespace {
+
+const uint64_t kEpoch = UINT64_C(1) << 32;
+const uint64_t kMask = kEpoch - 1;
+
+const QuicUint128 kTestStatelessResetToken = 1010101;  // 0x0F69B5
+
+// Use fields in which each byte is distinct to ensure that every byte is
+// framed correctly. The values are otherwise arbitrary.
+QuicConnectionId FramerTestConnectionId() {
+  return TestConnectionId(UINT64_C(0xFEDCBA9876543210));
+}
+
+QuicConnectionId FramerTestConnectionIdPlusOne() {
+  return TestConnectionId(UINT64_C(0xFEDCBA9876543211));
+}
+
+const QuicPacketNumber kPacketNumber = QuicPacketNumber(UINT64_C(0x12345678));
+const QuicPacketNumber kSmallLargestObserved =
+    QuicPacketNumber(UINT16_C(0x1234));
+const QuicPacketNumber kSmallMissingPacket = QuicPacketNumber(UINT16_C(0x1233));
+const QuicPacketNumber kLeastUnacked = QuicPacketNumber(UINT64_C(0x012345670));
+const QuicStreamId kStreamId = UINT64_C(0x01020304);
+// Note that the high 4 bits of the stream offset must be less than 0x40
+// in order to ensure that the value can be encoded using VarInt62 encoding.
+const QuicStreamOffset kStreamOffset = UINT64_C(0x3A98FEDC32107654);
+const QuicPublicResetNonceProof kNonceProof = UINT64_C(0xABCDEF0123456789);
+
+// In testing that we can ack the full range of packets...
+// This is the largest packet number that can be represented in IETF QUIC
+// varint62 format.
+const QuicPacketNumber kLargestIetfLargestObserved =
+    QuicPacketNumber(UINT64_C(0x3fffffffffffffff));
+// Encodings for the two bits in a VarInt62 that
+// describe the length of the VarInt61. For binary packet
+// formats in this file, the convention is to code the
+// first byte as
+//   kVarInt62FourBytes + 0x<value_in_that_byte>
+const uint8_t kVarInt62OneByte = 0x00;
+const uint8_t kVarInt62TwoBytes = 0x40;
+const uint8_t kVarInt62FourBytes = 0x80;
+const uint8_t kVarInt62EightBytes = 0xc0;
+
+class TestEncrypter : public QuicEncrypter {
+ public:
+  ~TestEncrypter() override {}
+  bool SetKey(QuicStringPiece key) override { return true; }
+  bool SetNoncePrefix(QuicStringPiece nonce_prefix) override { return true; }
+  bool SetIV(QuicStringPiece iv) override { return true; }
+  bool EncryptPacket(uint64_t packet_number,
+                     QuicStringPiece associated_data,
+                     QuicStringPiece plaintext,
+                     char* output,
+                     size_t* output_length,
+                     size_t max_output_length) override {
+    packet_number_ = QuicPacketNumber(packet_number);
+    associated_data_ = QuicString(associated_data);
+    plaintext_ = QuicString(plaintext);
+    memcpy(output, plaintext.data(), plaintext.length());
+    *output_length = plaintext.length();
+    return true;
+  }
+  size_t GetKeySize() const override { return 0; }
+  size_t GetNoncePrefixSize() const override { return 0; }
+  size_t GetIVSize() const override { return 0; }
+  size_t GetMaxPlaintextSize(size_t ciphertext_size) const override {
+    return ciphertext_size;
+  }
+  size_t GetCiphertextSize(size_t plaintext_size) const override {
+    return plaintext_size;
+  }
+  QuicStringPiece GetKey() const override { return QuicStringPiece(); }
+  QuicStringPiece GetNoncePrefix() const override { return QuicStringPiece(); }
+
+  QuicPacketNumber packet_number_;
+  QuicString associated_data_;
+  QuicString plaintext_;
+};
+
+class TestDecrypter : public QuicDecrypter {
+ public:
+  ~TestDecrypter() override {}
+  bool SetKey(QuicStringPiece key) override { return true; }
+  bool SetNoncePrefix(QuicStringPiece nonce_prefix) override { return true; }
+  bool SetIV(QuicStringPiece iv) override { return true; }
+  bool SetPreliminaryKey(QuicStringPiece key) override {
+    QUIC_BUG << "should not be called";
+    return false;
+  }
+  bool SetDiversificationNonce(const DiversificationNonce& key) override {
+    return true;
+  }
+  bool DecryptPacket(uint64_t packet_number,
+                     QuicStringPiece associated_data,
+                     QuicStringPiece ciphertext,
+                     char* output,
+                     size_t* output_length,
+                     size_t max_output_length) override {
+    packet_number_ = QuicPacketNumber(packet_number);
+    associated_data_ = QuicString(associated_data);
+    ciphertext_ = QuicString(ciphertext);
+    memcpy(output, ciphertext.data(), ciphertext.length());
+    *output_length = ciphertext.length();
+    return true;
+  }
+  size_t GetKeySize() const override { return 0; }
+  size_t GetIVSize() const override { return 0; }
+  QuicStringPiece GetKey() const override { return QuicStringPiece(); }
+  QuicStringPiece GetNoncePrefix() const override { return QuicStringPiece(); }
+  // Use a distinct value starting with 0xFFFFFF, which is never used by TLS.
+  uint32_t cipher_id() const override { return 0xFFFFFFF2; }
+  QuicPacketNumber packet_number_;
+  QuicString associated_data_;
+  QuicString ciphertext_;
+};
+
+class TestQuicVisitor : public QuicFramerVisitorInterface {
+ public:
+  TestQuicVisitor()
+      : error_count_(0),
+        version_mismatch_(0),
+        packet_count_(0),
+        frame_count_(0),
+        complete_packets_(0),
+        accept_packet_(true),
+        accept_public_header_(true) {}
+
+  ~TestQuicVisitor() override {}
+
+  void OnError(QuicFramer* f) override {
+    QUIC_DLOG(INFO) << "QuicFramer Error: " << QuicErrorCodeToString(f->error())
+                    << " (" << f->error() << ")";
+    ++error_count_;
+  }
+
+  void OnPacket() override {}
+
+  void OnPublicResetPacket(const QuicPublicResetPacket& packet) override {
+    public_reset_packet_ = QuicMakeUnique<QuicPublicResetPacket>((packet));
+  }
+
+  void OnVersionNegotiationPacket(
+      const QuicVersionNegotiationPacket& packet) override {
+    version_negotiation_packet_ =
+        QuicMakeUnique<QuicVersionNegotiationPacket>((packet));
+  }
+
+  bool OnProtocolVersionMismatch(ParsedQuicVersion received_version,
+                                 PacketHeaderFormat /*form*/) override {
+    QUIC_DLOG(INFO) << "QuicFramer Version Mismatch, version: "
+                    << received_version;
+    ++version_mismatch_;
+    return true;
+  }
+
+  bool OnUnauthenticatedPublicHeader(const QuicPacketHeader& header) override {
+    header_ = QuicMakeUnique<QuicPacketHeader>((header));
+    return accept_public_header_;
+  }
+
+  bool OnUnauthenticatedHeader(const QuicPacketHeader& header) override {
+    return true;
+  }
+
+  void OnDecryptedPacket(EncryptionLevel level) override {}
+
+  bool OnPacketHeader(const QuicPacketHeader& header) override {
+    ++packet_count_;
+    header_ = QuicMakeUnique<QuicPacketHeader>((header));
+    return accept_packet_;
+  }
+
+  void OnCoalescedPacket(const QuicEncryptedPacket& packet) override {
+    size_t coalesced_data_length = packet.length();
+    char* coalesced_data = new char[coalesced_data_length];
+    memcpy(coalesced_data, packet.data(), coalesced_data_length);
+    coalesced_packets_.push_back(QuicMakeUnique<QuicEncryptedPacket>(
+        coalesced_data, coalesced_data_length,
+        /*owns_buffer=*/true));
+  }
+
+  bool OnStreamFrame(const QuicStreamFrame& frame) override {
+    ++frame_count_;
+    // Save a copy of the data so it is valid after the packet is processed.
+    QuicString* string_data =
+        new QuicString(frame.data_buffer, frame.data_length);
+    stream_data_.push_back(QuicWrapUnique(string_data));
+    stream_frames_.push_back(QuicMakeUnique<QuicStreamFrame>(
+        frame.stream_id, frame.fin, frame.offset, *string_data));
+    return true;
+  }
+
+  bool OnCryptoFrame(const QuicCryptoFrame& frame) override {
+    ++frame_count_;
+    // Save a copy of the data so it is valid after the packet is processed.
+    QuicString* string_data =
+        new QuicString(frame.data_buffer, frame.data_length);
+    crypto_data_.push_back(QuicWrapUnique(string_data));
+    crypto_frames_.push_back(QuicMakeUnique<QuicCryptoFrame>(
+        ENCRYPTION_NONE, frame.offset, *string_data));
+    return true;
+  }
+
+  bool OnAckFrameStart(QuicPacketNumber largest_acked,
+                       QuicTime::Delta ack_delay_time) override {
+    ++frame_count_;
+    QuicAckFrame ack_frame;
+    ack_frame.largest_acked = largest_acked;
+    ack_frame.ack_delay_time = ack_delay_time;
+    ack_frames_.push_back(QuicMakeUnique<QuicAckFrame>(ack_frame));
+    return true;
+  }
+
+  bool OnAckRange(QuicPacketNumber start, QuicPacketNumber end) override {
+    DCHECK(!ack_frames_.empty());
+    ack_frames_[ack_frames_.size() - 1]->packets.AddRange(start, end);
+    return true;
+  }
+
+  bool OnAckTimestamp(QuicPacketNumber packet_number,
+                      QuicTime timestamp) override {
+    ack_frames_[ack_frames_.size() - 1]->received_packet_times.push_back(
+        std::make_pair(packet_number, timestamp));
+    return true;
+  }
+
+  bool OnAckFrameEnd(QuicPacketNumber /*start*/) override { return true; }
+
+  bool OnStopWaitingFrame(const QuicStopWaitingFrame& frame) override {
+    ++frame_count_;
+    stop_waiting_frames_.push_back(QuicMakeUnique<QuicStopWaitingFrame>(frame));
+    return true;
+  }
+
+  bool OnPaddingFrame(const QuicPaddingFrame& frame) override {
+    padding_frames_.push_back(QuicMakeUnique<QuicPaddingFrame>(frame));
+    return true;
+  }
+
+  bool OnPingFrame(const QuicPingFrame& frame) override {
+    ++frame_count_;
+    ping_frames_.push_back(QuicMakeUnique<QuicPingFrame>(frame));
+    return true;
+  }
+
+  bool OnMessageFrame(const QuicMessageFrame& frame) override {
+    ++frame_count_;
+    message_frames_.push_back(
+        QuicMakeUnique<QuicMessageFrame>(frame.data, frame.message_length));
+    return true;
+  }
+
+  void OnPacketComplete() override { ++complete_packets_; }
+
+  bool OnRstStreamFrame(const QuicRstStreamFrame& frame) override {
+    rst_stream_frame_ = frame;
+    return true;
+  }
+
+  bool OnConnectionCloseFrame(const QuicConnectionCloseFrame& frame) override {
+    connection_close_frame_ = frame;
+    return true;
+  }
+
+  bool OnApplicationCloseFrame(
+      const QuicApplicationCloseFrame& frame) override {
+    application_close_frame_ = frame;
+    return true;
+  }
+
+  bool OnStopSendingFrame(const QuicStopSendingFrame& frame) override {
+    stop_sending_frame_ = frame;
+    return true;
+  }
+
+  bool OnPathChallengeFrame(const QuicPathChallengeFrame& frame) override {
+    path_challenge_frame_ = frame;
+    return true;
+  }
+
+  bool OnPathResponseFrame(const QuicPathResponseFrame& frame) override {
+    path_response_frame_ = frame;
+    return true;
+  }
+
+  bool OnGoAwayFrame(const QuicGoAwayFrame& frame) override {
+    goaway_frame_ = frame;
+    return true;
+  }
+
+  bool OnMaxStreamIdFrame(const QuicMaxStreamIdFrame& frame) override {
+    max_stream_id_frame_ = frame;
+    return true;
+  }
+
+  bool OnStreamIdBlockedFrame(const QuicStreamIdBlockedFrame& frame) override {
+    stream_id_blocked_frame_ = frame;
+    return true;
+  }
+
+  bool OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame) override {
+    window_update_frame_ = frame;
+    return true;
+  }
+
+  bool OnBlockedFrame(const QuicBlockedFrame& frame) override {
+    blocked_frame_ = frame;
+    return true;
+  }
+
+  bool OnNewConnectionIdFrame(const QuicNewConnectionIdFrame& frame) override {
+    new_connection_id_ = frame;
+    return true;
+  }
+
+  bool OnRetireConnectionIdFrame(
+      const QuicRetireConnectionIdFrame& frame) override {
+    retire_connection_id_ = frame;
+    return true;
+  }
+
+  bool OnNewTokenFrame(const QuicNewTokenFrame& frame) override {
+    new_token_ = frame;
+    return true;
+  }
+
+  bool IsValidStatelessResetToken(QuicUint128 token) const override {
+    return token == kTestStatelessResetToken;
+  }
+
+  void OnAuthenticatedIetfStatelessResetPacket(
+      const QuicIetfStatelessResetPacket& packet) override {
+    stateless_reset_packet_ =
+        QuicMakeUnique<QuicIetfStatelessResetPacket>(packet);
+  }
+
+  // Counters from the visitor_ callbacks.
+  int error_count_;
+  int version_mismatch_;
+  int packet_count_;
+  int frame_count_;
+  int complete_packets_;
+  bool accept_packet_;
+  bool accept_public_header_;
+
+  std::unique_ptr<QuicPacketHeader> header_;
+  std::unique_ptr<QuicPublicResetPacket> public_reset_packet_;
+  std::unique_ptr<QuicIetfStatelessResetPacket> stateless_reset_packet_;
+  std::unique_ptr<QuicVersionNegotiationPacket> version_negotiation_packet_;
+  std::vector<std::unique_ptr<QuicStreamFrame>> stream_frames_;
+  std::vector<std::unique_ptr<QuicCryptoFrame>> crypto_frames_;
+  std::vector<std::unique_ptr<QuicAckFrame>> ack_frames_;
+  std::vector<std::unique_ptr<QuicStopWaitingFrame>> stop_waiting_frames_;
+  std::vector<std::unique_ptr<QuicPaddingFrame>> padding_frames_;
+  std::vector<std::unique_ptr<QuicPingFrame>> ping_frames_;
+  std::vector<std::unique_ptr<QuicMessageFrame>> message_frames_;
+  std::vector<std::unique_ptr<QuicEncryptedPacket>> coalesced_packets_;
+  QuicRstStreamFrame rst_stream_frame_;
+  QuicConnectionCloseFrame connection_close_frame_;
+  QuicApplicationCloseFrame application_close_frame_;
+  QuicStopSendingFrame stop_sending_frame_;
+  QuicGoAwayFrame goaway_frame_;
+  QuicPathChallengeFrame path_challenge_frame_;
+  QuicPathResponseFrame path_response_frame_;
+  QuicWindowUpdateFrame window_update_frame_;
+  QuicBlockedFrame blocked_frame_;
+  QuicStreamIdBlockedFrame stream_id_blocked_frame_;
+  QuicMaxStreamIdFrame max_stream_id_frame_;
+  QuicNewConnectionIdFrame new_connection_id_;
+  QuicRetireConnectionIdFrame retire_connection_id_;
+  QuicNewTokenFrame new_token_;
+  std::vector<std::unique_ptr<QuicString>> stream_data_;
+  std::vector<std::unique_ptr<QuicString>> crypto_data_;
+};
+
+// Simple struct for defining a packet's content, and associated
+// parse error.
+struct PacketFragment {
+  QuicString error_if_missing;
+  std::vector<unsigned char> fragment;
+};
+
+using PacketFragments = std::vector<struct PacketFragment>;
+
+ParsedQuicVersionVector AllSupportedVersionsIncludingTls() {
+  QuicFlagSaver flags;
+  SetQuicFlag(&FLAGS_quic_supports_tls_handshake, true);
+  return AllSupportedVersions();
+}
+
+class QuicFramerTest : public QuicTestWithParam<ParsedQuicVersion> {
+ public:
+  QuicFramerTest()
+      : encrypter_(new test::TestEncrypter()),
+        decrypter_(new test::TestDecrypter()),
+        version_(GetParam()),
+        start_(QuicTime::Zero() + QuicTime::Delta::FromMicroseconds(0x10)),
+        framer_(AllSupportedVersionsIncludingTls(),
+                start_,
+                Perspective::IS_SERVER,
+                kQuicDefaultConnectionIdLength) {
+    SetQuicFlag(&FLAGS_quic_supports_tls_handshake, true);
+    framer_.set_version(version_);
+    framer_.SetDecrypter(ENCRYPTION_NONE,
+                         std::unique_ptr<QuicDecrypter>(decrypter_));
+    framer_.SetEncrypter(ENCRYPTION_NONE,
+                         std::unique_ptr<QuicEncrypter>(encrypter_));
+
+    framer_.set_visitor(&visitor_);
+    framer_.InferPacketHeaderTypeFromVersion();
+  }
+
+  // Helper function to get unsigned char representation of the handshake
+  // protocol byte of the current QUIC version number.
+  unsigned char GetQuicVersionProtocolByte() {
+    return (CreateQuicVersionLabel(version_) >> 24) & 0xff;
+  }
+
+  // Helper function to get unsigned char representation of digit in the
+  // units place of the current QUIC version number.
+  unsigned char GetQuicVersionDigitOnes() {
+    return CreateQuicVersionLabel(version_) & 0xff;
+  }
+
+  // Helper function to get unsigned char representation of digit in the
+  // tens place of the current QUIC version number.
+  unsigned char GetQuicVersionDigitTens() {
+    return (CreateQuicVersionLabel(version_) >> 8) & 0xff;
+  }
+
+  bool CheckEncryption(QuicPacketNumber packet_number, QuicPacket* packet) {
+    if (packet_number != encrypter_->packet_number_) {
+      QUIC_LOG(ERROR) << "Encrypted incorrect packet number.  expected "
+                      << packet_number
+                      << " actual: " << encrypter_->packet_number_;
+      return false;
+    }
+    if (packet->AssociatedData(framer_.transport_version()) !=
+        encrypter_->associated_data_) {
+      QUIC_LOG(ERROR) << "Encrypted incorrect associated data.  expected "
+                      << packet->AssociatedData(framer_.transport_version())
+                      << " actual: " << encrypter_->associated_data_;
+      return false;
+    }
+    if (packet->Plaintext(framer_.transport_version()) !=
+        encrypter_->plaintext_) {
+      QUIC_LOG(ERROR) << "Encrypted incorrect plaintext data.  expected "
+                      << packet->Plaintext(framer_.transport_version())
+                      << " actual: " << encrypter_->plaintext_;
+      return false;
+    }
+    return true;
+  }
+
+  bool CheckDecryption(const QuicEncryptedPacket& encrypted,
+                       bool includes_version,
+                       bool includes_diversification_nonce,
+                       QuicConnectionIdLength destination_connection_id_length,
+                       QuicConnectionIdLength source_connection_id_length) {
+    return CheckDecryption(
+        encrypted, includes_version, includes_diversification_nonce,
+        destination_connection_id_length, source_connection_id_length,
+        VARIABLE_LENGTH_INTEGER_LENGTH_0, 0, VARIABLE_LENGTH_INTEGER_LENGTH_0);
+  }
+
+  bool CheckDecryption(
+      const QuicEncryptedPacket& encrypted,
+      bool includes_version,
+      bool includes_diversification_nonce,
+      QuicConnectionIdLength destination_connection_id_length,
+      QuicConnectionIdLength source_connection_id_length,
+      QuicVariableLengthIntegerLength retry_token_length_length,
+      size_t retry_token_length,
+      QuicVariableLengthIntegerLength length_length) {
+    if (visitor_.header_->packet_number != decrypter_->packet_number_) {
+      QUIC_LOG(ERROR) << "Decrypted incorrect packet number.  expected "
+                      << visitor_.header_->packet_number
+                      << " actual: " << decrypter_->packet_number_;
+      return false;
+    }
+    QuicStringPiece associated_data =
+        QuicFramer::GetAssociatedDataFromEncryptedPacket(
+            framer_.transport_version(), encrypted,
+            destination_connection_id_length, source_connection_id_length,
+            includes_version, includes_diversification_nonce,
+            PACKET_4BYTE_PACKET_NUMBER, retry_token_length_length,
+            retry_token_length, length_length);
+    if (associated_data != decrypter_->associated_data_) {
+      QUIC_LOG(ERROR) << "Decrypted incorrect associated data.  expected "
+                      << QuicTextUtils::HexEncode(associated_data)
+                      << " actual: "
+                      << QuicTextUtils::HexEncode(decrypter_->associated_data_);
+      return false;
+    }
+    QuicStringPiece ciphertext(
+        encrypted.AsStringPiece().substr(GetStartOfEncryptedData(
+            framer_.transport_version(), destination_connection_id_length,
+            source_connection_id_length, includes_version,
+            includes_diversification_nonce, PACKET_4BYTE_PACKET_NUMBER,
+            retry_token_length_length, retry_token_length, length_length)));
+    if (ciphertext != decrypter_->ciphertext_) {
+      QUIC_LOG(ERROR) << "Decrypted incorrect ciphertext data.  expected "
+                      << QuicTextUtils::HexEncode(ciphertext) << " actual: "
+                      << QuicTextUtils::HexEncode(decrypter_->ciphertext_)
+                      << " associated data: "
+                      << QuicTextUtils::HexEncode(associated_data);
+      return false;
+    }
+    return true;
+  }
+
+  char* AsChars(unsigned char* data) { return reinterpret_cast<char*>(data); }
+
+  // Creates a new QuicEncryptedPacket by concatenating the various
+  // packet fragments in |fragments|.
+  std::unique_ptr<QuicEncryptedPacket> AssemblePacketFromFragments(
+      const PacketFragments& fragments) {
+    char* buffer = new char[kMaxPacketSize + 1];
+    size_t len = 0;
+    for (const auto& fragment : fragments) {
+      memcpy(buffer + len, fragment.fragment.data(), fragment.fragment.size());
+      len += fragment.fragment.size();
+    }
+    return QuicMakeUnique<QuicEncryptedPacket>(buffer, len, true);
+  }
+
+  void CheckFramingBoundaries(const PacketFragments& fragments,
+                              QuicErrorCode error_code) {
+    std::unique_ptr<QuicEncryptedPacket> packet(
+        AssemblePacketFromFragments(fragments));
+    // Check all the various prefixes of |packet| for the expected
+    // parse error and error code.
+    for (size_t i = 0; i < packet->length(); ++i) {
+      QuicString expected_error;
+      size_t len = 0;
+      for (const auto& fragment : fragments) {
+        len += fragment.fragment.size();
+        if (i < len) {
+          expected_error = fragment.error_if_missing;
+          break;
+        }
+      }
+
+      if (expected_error.empty())
+        continue;
+
+      CheckProcessingFails(*packet, i, expected_error, error_code);
+    }
+  }
+
+  void CheckProcessingFails(const QuicEncryptedPacket& packet,
+                            size_t len,
+                            QuicString expected_error,
+                            QuicErrorCode error_code) {
+    QuicEncryptedPacket encrypted(packet.data(), len, false);
+    EXPECT_FALSE(framer_.ProcessPacket(encrypted)) << "len: " << len;
+    EXPECT_EQ(expected_error, framer_.detailed_error()) << "len: " << len;
+    EXPECT_EQ(error_code, framer_.error()) << "len: " << len;
+  }
+
+  void CheckProcessingFails(unsigned char* packet,
+                            size_t len,
+                            QuicString expected_error,
+                            QuicErrorCode error_code) {
+    QuicEncryptedPacket encrypted(AsChars(packet), len, false);
+    EXPECT_FALSE(framer_.ProcessPacket(encrypted)) << "len: " << len;
+    EXPECT_EQ(expected_error, framer_.detailed_error()) << "len: " << len;
+    EXPECT_EQ(error_code, framer_.error()) << "len: " << len;
+  }
+
+  // Checks if the supplied string matches data in the supplied StreamFrame.
+  void CheckStreamFrameData(QuicString str, QuicStreamFrame* frame) {
+    EXPECT_EQ(str, QuicString(frame->data_buffer, frame->data_length));
+  }
+
+  void CheckCalculatePacketNumber(uint64_t expected_packet_number,
+                                  QuicPacketNumber last_packet_number) {
+    uint64_t wire_packet_number = expected_packet_number & kMask;
+    EXPECT_EQ(expected_packet_number,
+              QuicFramerPeer::CalculatePacketNumberFromWire(
+                  &framer_, PACKET_4BYTE_PACKET_NUMBER, last_packet_number,
+                  wire_packet_number))
+        << "last_packet_number: " << last_packet_number
+        << " wire_packet_number: " << wire_packet_number;
+  }
+
+  std::unique_ptr<QuicPacket> BuildDataPacket(const QuicPacketHeader& header,
+                                              const QuicFrames& frames) {
+    return BuildUnsizedDataPacket(&framer_, header, frames);
+  }
+
+  std::unique_ptr<QuicPacket> BuildDataPacket(const QuicPacketHeader& header,
+                                              const QuicFrames& frames,
+                                              size_t packet_size) {
+    return BuildUnsizedDataPacket(&framer_, header, frames, packet_size);
+  }
+
+  // N starts at 1.
+  QuicStreamId GetNthStreamid(QuicTransportVersion transport_version,
+                              Perspective perspective,
+                              bool bidirectional,
+                              int n) {
+    if (bidirectional) {
+      return QuicUtils::GetFirstBidirectionalStreamId(transport_version,
+                                                      perspective) +
+             ((n - 1) * QuicUtils::StreamIdDelta(transport_version));
+    }
+    // Unidirectional
+    return QuicUtils::GetFirstUnidirectionalStreamId(transport_version,
+                                                     perspective) +
+           ((n - 1) * QuicUtils::StreamIdDelta(transport_version));
+  }
+
+  test::TestEncrypter* encrypter_;
+  test::TestDecrypter* decrypter_;
+  ParsedQuicVersion version_;
+  QuicTime start_;
+  QuicFramer framer_;
+  test::TestQuicVisitor visitor_;
+  SimpleBufferAllocator allocator_;
+};
+
+// Multiple test cases of QuicFramerTest use byte arrays to define packets for
+// testing, and these byte arrays contain the QUIC version. This macro explodes
+// the 32-bit version into four bytes in network order. Since it uses methods of
+// QuicFramerTest, it is only valid to use this in a QuicFramerTest.
+#define QUIC_VERSION_BYTES                                      \
+  GetQuicVersionProtocolByte(), '0', GetQuicVersionDigitTens(), \
+      GetQuicVersionDigitOnes()
+
+// Run all framer tests with all supported versions of QUIC.
+INSTANTIATE_TEST_SUITE_P(
+    QuicFramerTests,
+    QuicFramerTest,
+    ::testing::ValuesIn(AllSupportedVersionsIncludingTls()));
+
+TEST_P(QuicFramerTest, CalculatePacketNumberFromWireNearEpochStart) {
+  // A few quick manual sanity checks.
+  CheckCalculatePacketNumber(UINT64_C(1), QuicPacketNumber());
+  CheckCalculatePacketNumber(kEpoch + 1, QuicPacketNumber(kMask));
+  CheckCalculatePacketNumber(kEpoch, QuicPacketNumber(kMask));
+  for (uint64_t j = 0; j < 10; j++) {
+    CheckCalculatePacketNumber(j, QuicPacketNumber());
+    CheckCalculatePacketNumber(kEpoch - 1 - j, QuicPacketNumber());
+  }
+
+  // Cases where the last number was close to the start of the range.
+  for (QuicPacketNumber last = QuicPacketNumber(1); last < QuicPacketNumber(10);
+       last++) {
+    // Small numbers should not wrap (even if they're out of order).
+    for (uint64_t j = 0; j < 10; j++) {
+      CheckCalculatePacketNumber(j, last);
+    }
+
+    // Large numbers should not wrap either (because we're near 0 already).
+    for (uint64_t j = 0; j < 10; j++) {
+      CheckCalculatePacketNumber(kEpoch - 1 - j, last);
+    }
+  }
+}
+
+TEST_P(QuicFramerTest, CalculatePacketNumberFromWireNearEpochEnd) {
+  // Cases where the last number was close to the end of the range
+  for (uint64_t i = 0; i < 10; i++) {
+    QuicPacketNumber last = QuicPacketNumber(kEpoch - i);
+
+    // Small numbers should wrap.
+    for (uint64_t j = 0; j < 10; j++) {
+      CheckCalculatePacketNumber(kEpoch + j, last);
+    }
+
+    // Large numbers should not (even if they're out of order).
+    for (uint64_t j = 0; j < 10; j++) {
+      CheckCalculatePacketNumber(kEpoch - 1 - j, last);
+    }
+  }
+}
+
+// Next check where we're in a non-zero epoch to verify we handle
+// reverse wrapping, too.
+TEST_P(QuicFramerTest, CalculatePacketNumberFromWireNearPrevEpoch) {
+  const uint64_t prev_epoch = 1 * kEpoch;
+  const uint64_t cur_epoch = 2 * kEpoch;
+  // Cases where the last number was close to the start of the range
+  for (uint64_t i = 0; i < 10; i++) {
+    QuicPacketNumber last = QuicPacketNumber(cur_epoch + i);
+    // Small number should not wrap (even if they're out of order).
+    for (uint64_t j = 0; j < 10; j++) {
+      CheckCalculatePacketNumber(cur_epoch + j, last);
+    }
+
+    // But large numbers should reverse wrap.
+    for (uint64_t j = 0; j < 10; j++) {
+      uint64_t num = kEpoch - 1 - j;
+      CheckCalculatePacketNumber(prev_epoch + num, last);
+    }
+  }
+}
+
+TEST_P(QuicFramerTest, CalculatePacketNumberFromWireNearNextEpoch) {
+  const uint64_t cur_epoch = 2 * kEpoch;
+  const uint64_t next_epoch = 3 * kEpoch;
+  // Cases where the last number was close to the end of the range
+  for (uint64_t i = 0; i < 10; i++) {
+    QuicPacketNumber last = QuicPacketNumber(next_epoch - 1 - i);
+
+    // Small numbers should wrap.
+    for (uint64_t j = 0; j < 10; j++) {
+      CheckCalculatePacketNumber(next_epoch + j, last);
+    }
+
+    // but large numbers should not (even if they're out of order).
+    for (uint64_t j = 0; j < 10; j++) {
+      uint64_t num = kEpoch - 1 - j;
+      CheckCalculatePacketNumber(cur_epoch + num, last);
+    }
+  }
+}
+
+TEST_P(QuicFramerTest, CalculatePacketNumberFromWireNearNextMax) {
+  const uint64_t max_number = std::numeric_limits<uint64_t>::max();
+  const uint64_t max_epoch = max_number & ~kMask;
+
+  // Cases where the last number was close to the end of the range
+  for (uint64_t i = 0; i < 10; i++) {
+    // Subtract 1, because the expected next packet number is 1 more than the
+    // last packet number.
+    QuicPacketNumber last = QuicPacketNumber(max_number - i - 1);
+
+    // Small numbers should not wrap, because they have nowhere to go.
+    for (uint64_t j = 0; j < 10; j++) {
+      CheckCalculatePacketNumber(max_epoch + j, last);
+    }
+
+    // Large numbers should not wrap either.
+    for (uint64_t j = 0; j < 10; j++) {
+      uint64_t num = kEpoch - 1 - j;
+      CheckCalculatePacketNumber(max_epoch + num, last);
+    }
+  }
+}
+
+TEST_P(QuicFramerTest, EmptyPacket) {
+  char packet[] = {0x00};
+  QuicEncryptedPacket encrypted(packet, 0, false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_INVALID_PACKET_HEADER, framer_.error());
+}
+
+TEST_P(QuicFramerTest, LargePacket) {
+  // clang-format off
+  unsigned char packet[kMaxPacketSize + 1] = {
+    // public flags (8 byte connection_id)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x78, 0x56, 0x34, 0x12,
+    // private flags
+    0x00,
+  };
+  unsigned char packet44[kMaxPacketSize + 1] = {
+    // type (short header 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x78, 0x56, 0x34, 0x12,
+  };
+  unsigned char packet46[kMaxPacketSize + 1] = {
+    // type (short header 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x78, 0x56, 0x34, 0x12,
+  };
+  // clang-format on
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    p_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+
+  const size_t header_size = GetPacketHeaderSize(
+      framer_.transport_version(), PACKET_8BYTE_CONNECTION_ID,
+      PACKET_0BYTE_CONNECTION_ID, !kIncludeVersion,
+      !kIncludeDiversificationNonce, PACKET_4BYTE_PACKET_NUMBER,
+      VARIABLE_LENGTH_INTEGER_LENGTH_0, 0, VARIABLE_LENGTH_INTEGER_LENGTH_0);
+
+  memset(p + header_size, 0, kMaxPacketSize - header_size);
+
+  QuicEncryptedPacket encrypted(AsChars(p), p_size, false);
+  EXPECT_QUIC_BUG(framer_.ProcessPacket(encrypted), "Packet too large:1");
+
+  ASSERT_TRUE(visitor_.header_.get());
+  // Make sure we've parsed the packet header, so we can send an error.
+  EXPECT_EQ(FramerTestConnectionId(),
+            visitor_.header_->destination_connection_id);
+  // Make sure the correct error is propagated.
+  EXPECT_EQ(QUIC_PACKET_TOO_LARGE, framer_.error());
+}
+
+TEST_P(QuicFramerTest, PacketHeader) {
+  if (framer_.transport_version() > QUIC_VERSION_43) {
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (8 byte connection_id)
+      {"Unable to read public flags.",
+       {0x28}},
+      // connection_id
+      {"Unable to read ConnectionId.",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"Unable to read packet number.",
+       {0x12, 0x34, 0x56, 0x78}},
+  };
+  // clang-format on
+
+  PacketFragments& fragments = packet;
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+  EXPECT_EQ(QUIC_MISSING_PAYLOAD, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(FramerTestConnectionId(),
+            visitor_.header_->destination_connection_id);
+  EXPECT_FALSE(visitor_.header_->reset_flag);
+  EXPECT_FALSE(visitor_.header_->version_flag);
+  EXPECT_EQ(kPacketNumber, visitor_.header_->packet_number);
+
+  CheckFramingBoundaries(fragments, QUIC_INVALID_PACKET_HEADER);
+}
+
+TEST_P(QuicFramerTest, LongPacketHeader) {
+  // clang-format off
+  PacketFragments packet44 = {
+    // type (long header with packet type INITIAL)
+    {"Unable to read type.",
+     {0xFF}},
+    // version tag
+    {"Unable to read protocol version.",
+     {QUIC_VERSION_BYTES}},
+    // connection_id length
+    {"Unable to read ConnectionId length.",
+     {0x50}},
+    // connection_id
+    {"Unable to read Destination ConnectionId.",
+     {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+    // packet number
+    {"Unable to read packet number.",
+     {0x12, 0x34, 0x56, 0x78}},
+  };
+  PacketFragments packet46 = {
+    // type (long header with packet type INITIAL)
+    {"Unable to read type.",
+     {0xC3}},
+    // version tag
+    {"Unable to read protocol version.",
+     {QUIC_VERSION_BYTES}},
+    // connection_id length
+    {"Unable to read ConnectionId length.",
+     {0x50}},
+    // connection_id
+    {"Unable to read Destination ConnectionId.",
+     {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+    // packet number
+    {"Unable to read packet number.",
+     {0x12, 0x34, 0x56, 0x78}},
+  };
+  // clang-format on
+
+  if (framer_.transport_version() <= QUIC_VERSION_43 ||
+      QuicVersionHasLongHeaderLengths(framer_.transport_version())) {
+    return;
+  }
+
+  PacketFragments& fragments =
+      framer_.transport_version() > QUIC_VERSION_44 ? packet46 : packet44;
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+  EXPECT_EQ(QUIC_MISSING_PAYLOAD, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(FramerTestConnectionId(),
+            visitor_.header_->destination_connection_id);
+  EXPECT_FALSE(visitor_.header_->reset_flag);
+  EXPECT_TRUE(visitor_.header_->version_flag);
+  EXPECT_EQ(kPacketNumber, visitor_.header_->packet_number);
+
+  CheckFramingBoundaries(
+      framer_.transport_version() > QUIC_VERSION_44 ? packet46 : packet44,
+      QUIC_INVALID_PACKET_HEADER);
+}
+
+TEST_P(QuicFramerTest, PacketHeaderWith0ByteConnectionId) {
+  QuicFramerPeer::SetLastSerializedConnectionId(&framer_,
+                                                FramerTestConnectionId());
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (0 byte connection_id)
+      {"Unable to read public flags.",
+       {0x20}},
+      // connection_id
+      // packet number
+      {"Unable to read packet number.",
+       {0x12, 0x34, 0x56, 0x78}},
+  };
+
+  PacketFragments packet44 = {
+        // type (short header, 4 byte packet number)
+        {"Unable to read type.",
+         {0x32}},
+        // connection_id
+        // packet number
+        {"Unable to read packet number.",
+         {0x12, 0x34, 0x56, 0x78}},
+   };
+
+  PacketFragments packet46 = {
+        // type (short header, 4 byte packet number)
+        {"Unable to read type.",
+         {0x43}},
+        // connection_id
+        // packet number
+        {"Unable to read packet number.",
+         {0x12, 0x34, 0x56, 0x78}},
+   };
+  // clang-format on
+
+  PacketFragments& fragments =
+      framer_.transport_version() > QUIC_VERSION_44
+          ? packet46
+          : (framer_.transport_version() > QUIC_VERSION_43 ? packet44 : packet);
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+  EXPECT_EQ(QUIC_MISSING_PAYLOAD, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(FramerTestConnectionId(),
+            visitor_.header_->destination_connection_id);
+  EXPECT_FALSE(visitor_.header_->reset_flag);
+  EXPECT_FALSE(visitor_.header_->version_flag);
+  EXPECT_EQ(kPacketNumber, visitor_.header_->packet_number);
+
+  CheckFramingBoundaries(fragments, QUIC_INVALID_PACKET_HEADER);
+}
+
+TEST_P(QuicFramerTest, PacketHeaderWithVersionFlag) {
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (0 byte connection_id)
+      {"Unable to read public flags.",
+       {0x29}},
+      // connection_id
+      {"Unable to read ConnectionId.",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // version tag
+      {"Unable to read protocol version.",
+       {QUIC_VERSION_BYTES}},
+      // packet number
+      {"Unable to read packet number.",
+       {0x12, 0x34, 0x56, 0x78}},
+  };
+
+  PacketFragments packet44 = {
+      // type (long header with packet type ZERO_RTT_PROTECTED)
+      {"Unable to read type.",
+       {0xFC}},
+      // version tag
+      {"Unable to read protocol version.",
+       {QUIC_VERSION_BYTES}},
+      // connection_id length
+      {"Unable to read ConnectionId length.",
+       {0x50}},
+      // connection_id
+      {"Unable to read Destination ConnectionId.",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"Unable to read packet number.",
+       {0x12, 0x34, 0x56, 0x78}},
+  };
+
+  PacketFragments packet46 = {
+      // type (long header with packet type ZERO_RTT_PROTECTED and 4 bytes
+      // packet number)
+      {"Unable to read type.",
+       {0xD3}},
+      // version tag
+      {"Unable to read protocol version.",
+       {QUIC_VERSION_BYTES}},
+      // connection_id length
+      {"Unable to read ConnectionId length.",
+       {0x50}},
+      // connection_id
+      {"Unable to read Destination ConnectionId.",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"Unable to read packet number.",
+       {0x12, 0x34, 0x56, 0x78}},
+  };
+
+  PacketFragments packet99 = {
+      // type (long header with packet type ZERO_RTT_PROTECTED and 4 bytes
+      // packet number)
+      {"Unable to read type.",
+       {0xD3}},
+      // version tag
+      {"Unable to read protocol version.",
+       {QUIC_VERSION_BYTES}},
+      // connection_id length
+      {"Unable to read ConnectionId length.",
+       {0x50}},
+      // connection_id
+      {"Unable to read Destination ConnectionId.",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // long header packet length
+      {"Unable to read long header payload length.",
+       {0x04}},
+      // packet number
+      {"Long header payload length longer than packet.",
+       {0x12, 0x34, 0x56, 0x78}},
+  };
+  // clang-format on
+
+  PacketFragments& fragments =
+      framer_.transport_version() == QUIC_VERSION_99
+          ? packet99
+          : framer_.transport_version() > QUIC_VERSION_44
+                ? packet46
+                : (framer_.transport_version() > QUIC_VERSION_43 ? packet44
+                                                                 : packet);
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+  EXPECT_EQ(QUIC_MISSING_PAYLOAD, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(FramerTestConnectionId(),
+            visitor_.header_->destination_connection_id);
+  EXPECT_FALSE(visitor_.header_->reset_flag);
+  EXPECT_TRUE(visitor_.header_->version_flag);
+  EXPECT_EQ(GetParam(), visitor_.header_->version);
+  EXPECT_EQ(kPacketNumber, visitor_.header_->packet_number);
+
+  CheckFramingBoundaries(fragments, QUIC_INVALID_PACKET_HEADER);
+}
+
+TEST_P(QuicFramerTest, PacketHeaderWith4BytePacketNumber) {
+  QuicFramerPeer::SetLargestPacketNumber(&framer_, kPacketNumber - 2);
+
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (8 byte connection_id and 4 byte packet number)
+      {"Unable to read public flags.",
+       {0x28}},
+      // connection_id
+      {"Unable to read ConnectionId.",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"Unable to read packet number.",
+       {0x12, 0x34, 0x56, 0x78}},
+  };
+
+  PacketFragments packet44 = {
+      // type (short header, 4 byte packet number)
+      {"Unable to read type.",
+       {0x32}},
+      // connection_id
+      {"Unable to read Destination ConnectionId.",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"Unable to read packet number.",
+       {0x12, 0x34, 0x56, 0x78}},
+  };
+
+  PacketFragments packet46 = {
+      // type (short header, 4 byte packet number)
+      {"Unable to read type.",
+       {0x43}},
+      // connection_id
+      {"Unable to read Destination ConnectionId.",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"Unable to read packet number.",
+       {0x12, 0x34, 0x56, 0x78}},
+  };
+  // clang-format on
+
+  PacketFragments& fragments =
+      framer_.transport_version() > QUIC_VERSION_44
+          ? packet46
+          : (framer_.transport_version() > QUIC_VERSION_43 ? packet44 : packet);
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+  EXPECT_EQ(QUIC_MISSING_PAYLOAD, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(FramerTestConnectionId(),
+            visitor_.header_->destination_connection_id);
+  EXPECT_FALSE(visitor_.header_->reset_flag);
+  EXPECT_FALSE(visitor_.header_->version_flag);
+  EXPECT_EQ(kPacketNumber, visitor_.header_->packet_number);
+
+  CheckFramingBoundaries(fragments, QUIC_INVALID_PACKET_HEADER);
+}
+
+TEST_P(QuicFramerTest, PacketHeaderWith2BytePacketNumber) {
+  QuicFramerPeer::SetLargestPacketNumber(&framer_, kPacketNumber - 2);
+
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (8 byte connection_id and 2 byte packet number)
+      {"Unable to read public flags.",
+       {0x18}},
+      // connection_id
+      {"Unable to read ConnectionId.",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"Unable to read packet number.",
+       {0x56, 0x78}},
+  };
+
+  PacketFragments packet44 = {
+      // type (short header, 2 byte packet number)
+      {"Unable to read type.",
+       {0x31}},
+      // connection_id
+      {"Unable to read Destination ConnectionId.",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"Unable to read packet number.",
+       {0x56, 0x78}},
+  };
+
+  PacketFragments packet46 = {
+      // type (short header, 2 byte packet number)
+      {"Unable to read type.",
+       {0x41}},
+      // connection_id
+      {"Unable to read Destination ConnectionId.",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"Unable to read packet number.",
+       {0x56, 0x78}},
+  };
+  // clang-format on
+
+  PacketFragments& fragments =
+      framer_.transport_version() > QUIC_VERSION_44
+          ? packet46
+          : (framer_.transport_version() > QUIC_VERSION_43 ? packet44 : packet);
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+  EXPECT_EQ(QUIC_MISSING_PAYLOAD, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(FramerTestConnectionId(),
+            visitor_.header_->destination_connection_id);
+  EXPECT_FALSE(visitor_.header_->reset_flag);
+  EXPECT_FALSE(visitor_.header_->version_flag);
+  EXPECT_EQ(PACKET_2BYTE_PACKET_NUMBER, visitor_.header_->packet_number_length);
+  EXPECT_EQ(kPacketNumber, visitor_.header_->packet_number);
+
+  CheckFramingBoundaries(fragments, QUIC_INVALID_PACKET_HEADER);
+}
+
+TEST_P(QuicFramerTest, PacketHeaderWith1BytePacketNumber) {
+  QuicFramerPeer::SetLargestPacketNumber(&framer_, kPacketNumber - 2);
+
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (8 byte connection_id and 1 byte packet number)
+      {"Unable to read public flags.",
+       {0x08}},
+      // connection_id
+      {"Unable to read ConnectionId.",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"Unable to read packet number.",
+       {0x78}},
+  };
+
+  PacketFragments packet44 = {
+      // type (8 byte connection_id and 1 byte packet number)
+      {"Unable to read type.",
+       {0x30}},
+      // connection_id
+      {"Unable to read Destination ConnectionId.",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"Unable to read packet number.",
+       {0x78}},
+  };
+
+  PacketFragments packet46 = {
+      // type (8 byte connection_id and 1 byte packet number)
+      {"Unable to read type.",
+       {0x40}},
+      // connection_id
+      {"Unable to read Destination ConnectionId.",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"Unable to read packet number.",
+       {0x78}},
+  };
+
+  // clang-format on
+
+  PacketFragments& fragments =
+      framer_.transport_version() > QUIC_VERSION_44
+          ? packet46
+          : (framer_.transport_version() > QUIC_VERSION_43 ? packet44 : packet);
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+  EXPECT_EQ(QUIC_MISSING_PAYLOAD, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(FramerTestConnectionId(),
+            visitor_.header_->destination_connection_id);
+  EXPECT_FALSE(visitor_.header_->reset_flag);
+  EXPECT_FALSE(visitor_.header_->version_flag);
+  EXPECT_EQ(PACKET_1BYTE_PACKET_NUMBER, visitor_.header_->packet_number_length);
+  EXPECT_EQ(kPacketNumber, visitor_.header_->packet_number);
+
+  CheckFramingBoundaries(fragments, QUIC_INVALID_PACKET_HEADER);
+}
+
+TEST_P(QuicFramerTest, PacketNumberDecreasesThenIncreases) {
+  // Test the case when a packet is received from the past and future packet
+  // numbers are still calculated relative to the largest received packet.
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber - 2;
+
+  QuicFrames frames = {QuicFrame(QuicPaddingFrame())};
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  QuicEncryptedPacket encrypted(data->data(), data->length(), false);
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_SERVER);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(FramerTestConnectionId(),
+            visitor_.header_->destination_connection_id);
+  EXPECT_EQ(PACKET_4BYTE_PACKET_NUMBER, visitor_.header_->packet_number_length);
+  EXPECT_EQ(kPacketNumber - 2, visitor_.header_->packet_number);
+
+  // Receive a 1 byte packet number.
+  header.packet_number = kPacketNumber;
+  header.packet_number_length = PACKET_1BYTE_PACKET_NUMBER;
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  data = BuildDataPacket(header, frames);
+  QuicEncryptedPacket encrypted1(data->data(), data->length(), false);
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_SERVER);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted1));
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(FramerTestConnectionId(),
+            visitor_.header_->destination_connection_id);
+  EXPECT_EQ(PACKET_1BYTE_PACKET_NUMBER, visitor_.header_->packet_number_length);
+  EXPECT_EQ(kPacketNumber, visitor_.header_->packet_number);
+
+  // Process a 2 byte packet number 256 packets ago.
+  header.packet_number = kPacketNumber - 256;
+  header.packet_number_length = PACKET_2BYTE_PACKET_NUMBER;
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  data = BuildDataPacket(header, frames);
+  QuicEncryptedPacket encrypted2(data->data(), data->length(), false);
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_SERVER);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted2));
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(FramerTestConnectionId(),
+            visitor_.header_->destination_connection_id);
+  EXPECT_EQ(PACKET_2BYTE_PACKET_NUMBER, visitor_.header_->packet_number_length);
+  EXPECT_EQ(kPacketNumber - 256, visitor_.header_->packet_number);
+
+  // Process another 1 byte packet number and ensure it works.
+  header.packet_number = kPacketNumber - 1;
+  header.packet_number_length = PACKET_1BYTE_PACKET_NUMBER;
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  data = BuildDataPacket(header, frames);
+  QuicEncryptedPacket encrypted3(data->data(), data->length(), false);
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_SERVER);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted3));
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(FramerTestConnectionId(),
+            visitor_.header_->destination_connection_id);
+  EXPECT_EQ(PACKET_1BYTE_PACKET_NUMBER, visitor_.header_->packet_number_length);
+  EXPECT_EQ(kPacketNumber - 1, visitor_.header_->packet_number);
+}
+
+TEST_P(QuicFramerTest, PacketWithDiversificationNonce) {
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags: includes nonce flag
+    0x2C,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // nonce
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (padding)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  unsigned char packet44[] = {
+    // type: Long header with packet type ZERO_RTT_PROTECTED
+    0xFC,
+    // version tag
+    QUIC_VERSION_BYTES,
+    // connection_id length
+    0x05,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+    // nonce
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+
+    // frame type (padding)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  unsigned char packet46[] = {
+    // type: Long header with packet type ZERO_RTT_PROTECTED and 1 byte packet
+    // number.
+    0xD0,
+    // version tag
+    QUIC_VERSION_BYTES,
+    // connection_id length
+    0x05,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x78,
+    // nonce
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+
+    // frame type (padding)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  unsigned char packet99[] = {
+    // type: Long header with packet type ZERO_RTT_PROTECTED and 1 byte packet
+    // number.
+    0xD0,
+    // version tag
+    QUIC_VERSION_BYTES,
+    // connection_id length
+    0x05,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // long header packet length
+    0x26,
+    // packet number
+    0x78,
+    // nonce
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+
+    // frame type (padding)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+  // clang-format on
+
+  if (framer_.version().handshake_protocol != PROTOCOL_QUIC_CRYPTO) {
+    return;
+  }
+
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    p_size = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    p_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+
+  QuicEncryptedPacket encrypted(AsChars(p), p_size, false);
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+  ASSERT_TRUE(visitor_.header_->nonce != nullptr);
+  for (char i = 0; i < 32; ++i) {
+    EXPECT_EQ(i, (*visitor_.header_->nonce)[static_cast<size_t>(i)]);
+  }
+  EXPECT_EQ(1u, visitor_.padding_frames_.size());
+  EXPECT_EQ(5, visitor_.padding_frames_[0]->num_padding_bytes);
+}
+
+TEST_P(QuicFramerTest, LargePublicFlagWithMismatchedVersions) {
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id, version flag and an unknown flag)
+    0x29,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // version tag
+    'Q', '0', '0', '0',
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  unsigned char packet44[] = {
+    // type (long header with packet type ZERO_RTT_PROTECTED)
+    0xFC,
+    // version tag
+    'Q', '0', '0', '0',
+    // connection_id length
+    0x50,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+  // clang-format on
+
+  QuicEncryptedPacket encrypted(
+      AsChars(framer_.transport_version() > QUIC_VERSION_43 ? packet44
+                                                            : packet),
+      framer_.transport_version() > QUIC_VERSION_43 ? QUIC_ARRAYSIZE(packet44)
+                                                    : QUIC_ARRAYSIZE(packet),
+      false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(0, visitor_.frame_count_);
+  EXPECT_EQ(1, visitor_.version_mismatch_);
+  EXPECT_EQ(1u, visitor_.padding_frames_.size());
+  EXPECT_EQ(5, visitor_.padding_frames_[0]->num_padding_bytes);
+}
+
+TEST_P(QuicFramerTest, PaddingFrame) {
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // paddings
+    0x00, 0x00,
+    // frame type (stream frame with fin)
+    0xFF,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // offset
+    0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // data length
+    0x00, 0x0c,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+    // paddings
+    0x00, 0x00,
+  };
+
+  unsigned char packet44[] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // paddings
+    0x00, 0x00,
+    // frame type (stream frame with fin)
+    0xFF,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // offset
+    0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // data length
+    0x00, 0x0c,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+    // paddings
+    0x00, 0x00,
+  };
+
+  unsigned char packet46[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // paddings
+    0x00, 0x00,
+    // frame type (stream frame with fin)
+    0xFF,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // offset
+    0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // data length
+    0x00, 0x0c,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+    // paddings
+    0x00, 0x00,
+  };
+
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // paddings
+    0x00, 0x00,
+    // frame type - IETF_STREAM with FIN, LEN, and OFFSET bits set.
+    0x08 | 0x01 | 0x02 | 0x04,
+
+    // stream id
+    kVarInt62FourBytes + 0x01, 0x02, 0x03, 0x04,
+    // offset
+    kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // data length
+    kVarInt62OneByte + 0x0c,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+    // paddings
+    0x00, 0x00,
+  };
+  // clang-format on
+
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    p_size = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    p_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+
+  QuicEncryptedPacket encrypted(AsChars(p), p_size, false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  ASSERT_EQ(1u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+  EXPECT_EQ(2u, visitor_.padding_frames_.size());
+  EXPECT_EQ(2, visitor_.padding_frames_[0]->num_padding_bytes);
+  EXPECT_EQ(2, visitor_.padding_frames_[1]->num_padding_bytes);
+  EXPECT_EQ(kStreamId, visitor_.stream_frames_[0]->stream_id);
+  EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+  EXPECT_EQ(kStreamOffset, visitor_.stream_frames_[0]->offset);
+  CheckStreamFrameData("hello world!", visitor_.stream_frames_[0].get());
+}
+
+TEST_P(QuicFramerTest, StreamFrame) {
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (8 byte connection_id)
+      {"",
+       {0x28}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (stream frame with fin)
+      {"",
+       {0xFF}},
+      // stream id
+      {"Unable to read stream_id.",
+       {0x01, 0x02, 0x03, 0x04}},
+      // offset
+      {"Unable to read offset.",
+       {0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+      {"Unable to read frame data.",
+       {
+         // data length
+         0x00, 0x0c,
+         // data
+         'h',  'e',  'l',  'l',
+         'o',  ' ',  'w',  'o',
+         'r',  'l',  'd',  '!'}},
+  };
+
+  PacketFragments packet44 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x32}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (stream frame with fin)
+      {"",
+       {0xFF}},
+      // stream id
+      {"Unable to read stream_id.",
+       {0x01, 0x02, 0x03, 0x04}},
+      // offset
+      {"Unable to read offset.",
+       {0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+      {"Unable to read frame data.",
+       {
+         // data length
+         0x00, 0x0c,
+         // data
+         'h',  'e',  'l',  'l',
+         'o',  ' ',  'w',  'o',
+         'r',  'l',  'd',  '!'}},
+  };
+
+  PacketFragments packet46 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (stream frame with fin)
+      {"",
+       {0xFF}},
+      // stream id
+      {"Unable to read stream_id.",
+       {0x01, 0x02, 0x03, 0x04}},
+      // offset
+      {"Unable to read offset.",
+       {0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+      {"Unable to read frame data.",
+       {
+         // data length
+         0x00, 0x0c,
+         // data
+         'h',  'e',  'l',  'l',
+         'o',  ' ',  'w',  'o',
+         'r',  'l',  'd',  '!'}},
+  };
+
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type - IETF_STREAM with FIN, LEN, and OFFSET bits set.
+      {"",
+       { 0x08 | 0x01 | 0x02 | 0x04 }},
+      // stream id
+      {"Unable to read stream_id.",
+       {kVarInt62FourBytes + 0x01, 0x02, 0x03, 0x04}},
+      // offset
+      {"Unable to read stream data offset.",
+       {kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+      // data length
+      {"Unable to read stream data length.",
+       {kVarInt62OneByte + 0x0c}},
+      // data
+      {"Unable to read frame data.",
+       { 'h',  'e',  'l',  'l',
+         'o',  ' ',  'w',  'o',
+         'r',  'l',  'd',  '!'}},
+  };
+  // clang-format on
+
+  PacketFragments& fragments =
+      framer_.transport_version() == QUIC_VERSION_99
+          ? packet99
+          : (framer_.transport_version() > QUIC_VERSION_44
+                 ? packet46
+                 : (framer_.transport_version() > QUIC_VERSION_43 ? packet44
+                                                                  : packet));
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  ASSERT_EQ(1u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+  EXPECT_EQ(kStreamId, visitor_.stream_frames_[0]->stream_id);
+  EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+  EXPECT_EQ(kStreamOffset, visitor_.stream_frames_[0]->offset);
+  CheckStreamFrameData("hello world!", visitor_.stream_frames_[0].get());
+
+  CheckFramingBoundaries(fragments, QUIC_INVALID_STREAM_DATA);
+}
+
+// Test an empty (no data) stream frame.
+TEST_P(QuicFramerTest, EmptyStreamFrame) {
+  // Only the IETF QUIC spec explicitly says that empty
+  // stream frames are supported.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+  // clang-format off
+  PacketFragments packet = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type - IETF_STREAM with FIN, LEN, and OFFSET bits set.
+      {"",
+       { 0x08 | 0x01 | 0x02 | 0x04 }},
+      // stream id
+      {"Unable to read stream_id.",
+       {kVarInt62FourBytes + 0x01, 0x02, 0x03, 0x04}},
+      // offset
+      {"Unable to read stream data offset.",
+       {kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+      // data length
+      {"Unable to read stream data length.",
+       {kVarInt62OneByte + 0x00}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  ASSERT_EQ(1u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+  EXPECT_EQ(kStreamId, visitor_.stream_frames_[0]->stream_id);
+  EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+  EXPECT_EQ(kStreamOffset, visitor_.stream_frames_[0]->offset);
+  EXPECT_EQ(visitor_.stream_frames_[0].get()->data_length, 0u);
+
+  CheckFramingBoundaries(packet, QUIC_INVALID_STREAM_DATA);
+}
+
+TEST_P(QuicFramerTest, MissingDiversificationNonce) {
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  framer_.SetDecrypter(ENCRYPTION_NONE,
+                       QuicMakeUnique<NullDecrypter>(Perspective::IS_CLIENT));
+  decrypter_ = new test::TestDecrypter();
+  framer_.SetAlternativeDecrypter(
+      ENCRYPTION_ZERO_RTT, std::unique_ptr<QuicDecrypter>(decrypter_), false);
+
+  // clang-format off
+  unsigned char packet[] = {
+        // public flags (8 byte connection_id)
+        0x28,
+        // connection_id
+        0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0xFE,
+        // packet number
+        0x12, 0x34, 0x56, 0x78,
+
+        // frame type (stream frame with fin)
+        0xFF,
+        // stream id
+        0x01, 0x02, 0x03, 0x04,
+        // offset
+        0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54,
+        // data length
+        0x00, 0x0c,
+        // data
+        'h',  'e',  'l',  'l',
+        'o',  ' ',  'w',  'o',
+        'r',  'l',  'd',  '!',
+    };
+
+  unsigned char packet44[] = {
+        // type (long header with packet type ZERO_RTT_PROTECTED)
+        0xFC,
+        // version tag
+        QUIC_VERSION_BYTES,
+        // connection_id length
+        0x50,
+        // connection_id
+        0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0xFE,
+        // packet number
+        0x12, 0x34, 0x56, 0x78,
+
+        // frame type - STREAM with LEN, FIN, and OFFSET bits set.
+        0x10 | 0x01 | 0x02 | 0x04,
+        // stream id
+        0x01, 0x02, 0x03, 0x04,
+        // offset
+        0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54,
+        // data length
+        0x00, 0x0c,
+        // data
+        'h',  'e',  'l',  'l',
+        'o',  ' ',  'w',  'o',
+        'r',  'l',  'd',  '!',
+    };
+  // clang-format on
+
+  unsigned char* p = packet;
+  if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+  }
+  QuicEncryptedPacket encrypted(AsChars(p),
+                                framer_.transport_version() > QUIC_VERSION_43
+                                    ? QUIC_ARRAYSIZE(packet44)
+                                    : QUIC_ARRAYSIZE(packet),
+                                false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  if (framer_.transport_version() > QUIC_VERSION_43) {
+    // Cannot read diversification nonce.
+    EXPECT_EQ(QUIC_INVALID_PACKET_HEADER, framer_.error());
+  } else {
+    EXPECT_EQ(QUIC_DECRYPTION_FAILURE, framer_.error());
+  }
+}
+
+TEST_P(QuicFramerTest, StreamFrame3ByteStreamId) {
+  if (framer_.transport_version() > QUIC_VERSION_43) {
+    // This test is nonsensical for IETF Quic.
+    return;
+  }
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (8 byte connection_id)
+      {"",
+       {0x28}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (stream frame with fin)
+      {"",
+       {0xFE}},
+      // stream id
+      {"Unable to read stream_id.",
+       {0x02, 0x03, 0x04}},
+      // offset
+      {"Unable to read offset.",
+       {0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+      {"Unable to read frame data.",
+       {
+         // data length
+         0x00, 0x0c,
+         // data
+         'h',  'e',  'l',  'l',
+         'o',  ' ',  'w',  'o',
+         'r',  'l',  'd',  '!'}},
+  };
+  // clang-format on
+
+  PacketFragments& fragments = packet;
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  ASSERT_EQ(1u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+  // Stream ID should be the last 3 bytes of kStreamId.
+  EXPECT_EQ(0x00FFFFFF & kStreamId, visitor_.stream_frames_[0]->stream_id);
+  EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+  EXPECT_EQ(kStreamOffset, visitor_.stream_frames_[0]->offset);
+  CheckStreamFrameData("hello world!", visitor_.stream_frames_[0].get());
+
+  CheckFramingBoundaries(fragments, QUIC_INVALID_STREAM_DATA);
+}
+
+TEST_P(QuicFramerTest, StreamFrame2ByteStreamId) {
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (8 byte connection_id)
+      {"",
+       {0x28}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (stream frame with fin)
+      {"",
+       {0xFD}},
+      // stream id
+      {"Unable to read stream_id.",
+       {0x03, 0x04}},
+      // offset
+      {"Unable to read offset.",
+       {0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+      {"Unable to read frame data.",
+       {
+         // data length
+         0x00, 0x0c,
+         // data
+         'h',  'e',  'l',  'l',
+         'o',  ' ',  'w',  'o',
+         'r',  'l',  'd',  '!'}},
+  };
+
+  PacketFragments packet44 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x32}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+       // frame type (stream frame with fin)
+       {"",
+        {0xFD}},
+       // stream id
+       {"Unable to read stream_id.",
+        {0x03, 0x04}},
+       // offset
+       {"Unable to read offset.",
+        {0x3A, 0x98, 0xFE, 0xDC,
+         0x32, 0x10, 0x76, 0x54}},
+       {"Unable to read frame data.",
+        {
+          // data length
+          0x00, 0x0c,
+          // data
+          'h',  'e',  'l',  'l',
+          'o',  ' ',  'w',  'o',
+          'r',  'l',  'd',  '!'}},
+  };
+
+  PacketFragments packet46 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+       // frame type (stream frame with fin)
+       {"",
+        {0xFD}},
+       // stream id
+       {"Unable to read stream_id.",
+        {0x03, 0x04}},
+       // offset
+       {"Unable to read offset.",
+        {0x3A, 0x98, 0xFE, 0xDC,
+         0x32, 0x10, 0x76, 0x54}},
+       {"Unable to read frame data.",
+        {
+          // data length
+          0x00, 0x0c,
+          // data
+          'h',  'e',  'l',  'l',
+          'o',  ' ',  'w',  'o',
+          'r',  'l',  'd',  '!'}},
+  };
+
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (IETF_STREAM frame with LEN, FIN, and OFFSET bits set)
+      {"",
+       {0x08 | 0x01 | 0x02 | 0x04}},
+      // stream id
+      {"Unable to read stream_id.",
+       {kVarInt62TwoBytes + 0x03, 0x04}},
+      // offset
+      {"Unable to read stream data offset.",
+       {kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+      // data length
+      {"Unable to read stream data length.",
+       {kVarInt62OneByte + 0x0c}},
+      // data
+      {"Unable to read frame data.",
+       { 'h',  'e',  'l',  'l',
+         'o',  ' ',  'w',  'o',
+         'r',  'l',  'd',  '!'}},
+  };
+  // clang-format on
+
+  PacketFragments& fragments =
+      framer_.transport_version() == QUIC_VERSION_99
+          ? packet99
+          : (framer_.transport_version() > QUIC_VERSION_44
+                 ? packet46
+                 : (framer_.transport_version() > QUIC_VERSION_43 ? packet44
+                                                                  : packet));
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  ASSERT_EQ(1u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+  // Stream ID should be the last 2 bytes of kStreamId.
+  EXPECT_EQ(0x0000FFFF & kStreamId, visitor_.stream_frames_[0]->stream_id);
+  EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+  EXPECT_EQ(kStreamOffset, visitor_.stream_frames_[0]->offset);
+  CheckStreamFrameData("hello world!", visitor_.stream_frames_[0].get());
+
+  CheckFramingBoundaries(fragments, QUIC_INVALID_STREAM_DATA);
+}
+
+TEST_P(QuicFramerTest, StreamFrame1ByteStreamId) {
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (8 byte connection_id)
+      {"",
+       {0x28}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (stream frame with fin)
+      {"",
+       {0xFC}},
+      // stream id
+      {"Unable to read stream_id.",
+       {0x04}},
+      // offset
+      {"Unable to read offset.",
+       {0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+      {"Unable to read frame data.",
+       {
+         // data length
+         0x00, 0x0c,
+         // data
+         'h',  'e',  'l',  'l',
+         'o',  ' ',  'w',  'o',
+         'r',  'l',  'd',  '!'}},
+  };
+
+  PacketFragments packet44 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x32}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (stream frame with fin)
+      {"",
+       {0xFC}},
+      // stream id
+      {"Unable to read stream_id.",
+       {0x04}},
+      // offset
+      {"Unable to read offset.",
+       {0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+      {"Unable to read frame data.",
+       {
+         // data length
+         0x00, 0x0c,
+         // data
+         'h',  'e',  'l',  'l',
+         'o',  ' ',  'w',  'o',
+         'r',  'l',  'd',  '!'}},
+  };
+
+  PacketFragments packet46 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (stream frame with fin)
+      {"",
+       {0xFC}},
+      // stream id
+      {"Unable to read stream_id.",
+       {0x04}},
+      // offset
+      {"Unable to read offset.",
+       {0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+      {"Unable to read frame data.",
+       {
+         // data length
+         0x00, 0x0c,
+         // data
+         'h',  'e',  'l',  'l',
+         'o',  ' ',  'w',  'o',
+         'r',  'l',  'd',  '!'}},
+  };
+
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (IETF_STREAM frame with LEN, FIN, and OFFSET bits set)
+      {"",
+       {0x08 | 0x01 | 0x02 | 0x04}},
+      // stream id
+      {"Unable to read stream_id.",
+       {kVarInt62OneByte + 0x04}},
+      // offset
+      {"Unable to read stream data offset.",
+       {kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+      // data length
+      {"Unable to read stream data length.",
+       {kVarInt62OneByte + 0x0c}},
+      // data
+      {"Unable to read frame data.",
+       { 'h',  'e',  'l',  'l',
+         'o',  ' ',  'w',  'o',
+         'r',  'l',  'd',  '!'}},
+  };
+  // clang-format on
+
+  PacketFragments& fragments =
+      framer_.transport_version() == QUIC_VERSION_99
+          ? packet99
+          : (framer_.transport_version() > QUIC_VERSION_44
+                 ? packet46
+                 : (framer_.transport_version() > QUIC_VERSION_43 ? packet44
+                                                                  : packet));
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  ASSERT_EQ(1u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+  // Stream ID should be the last 1 byte of kStreamId.
+  EXPECT_EQ(0x000000FF & kStreamId, visitor_.stream_frames_[0]->stream_id);
+  EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+  EXPECT_EQ(kStreamOffset, visitor_.stream_frames_[0]->offset);
+  CheckStreamFrameData("hello world!", visitor_.stream_frames_[0].get());
+
+  CheckFramingBoundaries(fragments, QUIC_INVALID_STREAM_DATA);
+}
+
+TEST_P(QuicFramerTest, StreamFrameWithVersion) {
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (version, 8 byte connection_id)
+      {"",
+       {0x29}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // version tag
+      {"",
+       {QUIC_VERSION_BYTES}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (stream frame with fin)
+      {"",
+       {0xFE}},
+      // stream id
+      {"Unable to read stream_id.",
+       {0x02, 0x03, 0x04}},
+      // offset
+      {"Unable to read offset.",
+       {0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+      {"Unable to read frame data.",
+       {
+         // data length
+         0x00, 0x0c,
+         // data
+         'h',  'e',  'l',  'l',
+         'o',  ' ',  'w',  'o',
+         'r',  'l',  'd',  '!'}},
+  };
+
+  PacketFragments packet44 = {
+      // public flags (long header with packet type ZERO_RTT_PROTECTED)
+      {"",
+       {0xFC}},
+      // version tag
+      {"",
+       {QUIC_VERSION_BYTES}},
+      // connection_id length
+      {"",
+       {0x50}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (stream frame with fin)
+      {"",
+       {0xFE}},
+      // stream id
+      {"Unable to read stream_id.",
+       {0x02, 0x03, 0x04}},
+      // offset
+      {"Unable to read offset.",
+       {0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+      {"Unable to read frame data.",
+       {
+         // data length
+         0x00, 0x0c,
+         // data
+         'h',  'e',  'l',  'l',
+         'o',  ' ',  'w',  'o',
+         'r',  'l',  'd',  '!'}},
+  };
+
+  PacketFragments packet46 = {
+      // public flags (long header with packet type ZERO_RTT_PROTECTED and
+      // 4-byte packet number)
+      {"",
+       {0xD3}},
+      // version tag
+      {"",
+       {QUIC_VERSION_BYTES}},
+      // connection_id length
+      {"",
+       {0x50}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (stream frame with fin)
+      {"",
+       {0xFE}},
+      // stream id
+      {"Unable to read stream_id.",
+       {0x02, 0x03, 0x04}},
+      // offset
+      {"Unable to read offset.",
+       {0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+      {"Unable to read frame data.",
+       {
+         // data length
+         0x00, 0x0c,
+         // data
+         'h',  'e',  'l',  'l',
+         'o',  ' ',  'w',  'o',
+         'r',  'l',  'd',  '!'}},
+  };
+
+  PacketFragments packet99 = {
+      // public flags (long header with packet type ZERO_RTT_PROTECTED and
+      // 4-byte packet number)
+      {"",
+       {0xD3}},
+      // version tag
+      {"",
+       {QUIC_VERSION_BYTES}},
+      // connection_id length
+      {"",
+       {0x50}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // long header packet length
+      {"",
+       {0x1E}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (IETF_STREAM frame with FIN, LEN, and OFFSET bits set)
+      {"",
+       {0x08 | 0x01 | 0x02 | 0x04}},
+      // stream id
+      {"Long header payload length longer than packet.",
+       {kVarInt62FourBytes + 0x00, 0x02, 0x03, 0x04}},
+      // offset
+      {"Long header payload length longer than packet.",
+       {kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+      // data length
+      {"Long header payload length longer than packet.",
+       {kVarInt62OneByte + 0x0c}},
+      // data
+      {"Long header payload length longer than packet.",
+       { 'h',  'e',  'l',  'l',
+         'o',  ' ',  'w',  'o',
+         'r',  'l',  'd',  '!'}},
+  };
+  // clang-format on
+
+  QuicVariableLengthIntegerLength retry_token_length_length =
+      VARIABLE_LENGTH_INTEGER_LENGTH_0;
+  size_t retry_token_length = 0;
+  QuicVariableLengthIntegerLength length_length =
+      QuicVersionHasLongHeaderLengths(framer_.transport_version())
+          ? VARIABLE_LENGTH_INTEGER_LENGTH_1
+          : VARIABLE_LENGTH_INTEGER_LENGTH_0;
+
+  PacketFragments& fragments =
+      framer_.transport_version() == QUIC_VERSION_99
+          ? packet99
+          : (framer_.transport_version() > QUIC_VERSION_44
+                 ? packet46
+                 : (framer_.transport_version() > QUIC_VERSION_43 ? packet44
+                                                                  : packet));
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID,
+      retry_token_length_length, retry_token_length, length_length));
+
+  ASSERT_EQ(1u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+  // Stream ID should be the last 3 bytes of kStreamId.
+  EXPECT_EQ(0x00FFFFFF & kStreamId, visitor_.stream_frames_[0]->stream_id);
+  EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+  EXPECT_EQ(kStreamOffset, visitor_.stream_frames_[0]->offset);
+  CheckStreamFrameData("hello world!", visitor_.stream_frames_[0].get());
+
+  CheckFramingBoundaries(fragments,
+                         framer_.transport_version() == QUIC_VERSION_99
+                             ? QUIC_INVALID_PACKET_HEADER
+                             : QUIC_INVALID_STREAM_DATA);
+}
+
+TEST_P(QuicFramerTest, RejectPacket) {
+  visitor_.accept_packet_ = false;
+
+  // clang-format off
+  unsigned char packet[] = {
+      // public flags (8 byte connection_id)
+      0x28,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (stream frame with fin)
+      0xFF,
+      // stream id
+      0x01, 0x02, 0x03, 0x04,
+      // offset
+      0x3A, 0x98, 0xFE, 0xDC,
+      0x32, 0x10, 0x76, 0x54,
+      // data length
+      0x00, 0x0c,
+      // data
+      'h',  'e',  'l',  'l',
+      'o',  ' ',  'w',  'o',
+      'r',  'l',  'd',  '!',
+  };
+
+  unsigned char packet44[] = {
+      // type (short header, 4 byte packet number)
+      0x32,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (STREAM Frame with FIN, LEN, and OFFSET bits set)
+      0x10 | 0x01 | 0x02 | 0x04,
+      // stream id
+      kVarInt62FourBytes + 0x01, 0x02, 0x03, 0x04,
+      // offset
+      kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+      0x32, 0x10, 0x76, 0x54,
+      // data length
+      kVarInt62OneByte + 0x0c,
+      // data
+      'h',  'e',  'l',  'l',
+      'o',  ' ',  'w',  'o',
+      'r',  'l',  'd',  '!',
+  };
+
+  unsigned char packet46[] = {
+      // type (short header, 4 byte packet number)
+      0x43,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (STREAM Frame with FIN, LEN, and OFFSET bits set)
+      0x10 | 0x01 | 0x02 | 0x04,
+      // stream id
+      kVarInt62FourBytes + 0x01, 0x02, 0x03, 0x04,
+      // offset
+      kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+      0x32, 0x10, 0x76, 0x54,
+      // data length
+      kVarInt62OneByte + 0x0c,
+      // data
+      'h',  'e',  'l',  'l',
+      'o',  ' ',  'w',  'o',
+      'r',  'l',  'd',  '!',
+  };
+  // clang-format on
+
+  unsigned char* p = packet;
+  if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+  }
+  QuicEncryptedPacket encrypted(AsChars(p),
+                                framer_.transport_version() > QUIC_VERSION_43
+                                    ? QUIC_ARRAYSIZE(packet44)
+                                    : QUIC_ARRAYSIZE(packet),
+                                false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  ASSERT_EQ(0u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+}
+
+TEST_P(QuicFramerTest, RejectPublicHeader) {
+  visitor_.accept_public_header_ = false;
+
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+  };
+
+  unsigned char packet44[] = {
+    // type (short header, 1 byte packet number)
+    0x30,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x01,
+  };
+
+  unsigned char packet46[] = {
+    // type (short header, 1 byte packet number)
+    0x40,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x01,
+  };
+  // clang-format on
+
+  QuicEncryptedPacket encrypted(
+      framer_.transport_version() > QUIC_VERSION_44
+          ? AsChars(packet46)
+          : (framer_.transport_version() > QUIC_VERSION_43 ? AsChars(packet44)
+                                                           : AsChars(packet)),
+      framer_.transport_version() > QUIC_VERSION_44
+          ? QUIC_ARRAYSIZE(packet46)
+          : (framer_.transport_version() > QUIC_VERSION_43
+                 ? QUIC_ARRAYSIZE(packet44)
+                 : QUIC_ARRAYSIZE(packet)),
+      false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_FALSE(visitor_.header_->packet_number.IsInitialized());
+}
+
+TEST_P(QuicFramerTest, AckFrameOneAckBlock) {
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (8 byte connection_id)
+      {"",
+       {0x2C}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (ack frame)
+      // (one ack block, 2 byte largest observed, 2 byte block length)
+      {"",
+       {0x45}},
+      // largest acked
+      {"Unable to read largest acked.",
+       {0x12, 0x34}},
+      // Zero delta time.
+      {"Unable to read ack delay time.",
+       {0x00, 0x00}},
+      // first ack block length.
+      {"Unable to read first ack block length.",
+       {0x12, 0x34}},
+      // num timestamps.
+      {"Unable to read num received packets.",
+       {0x00}}
+  };
+
+  PacketFragments packet44 = {
+      // type (short packet, 4 byte packet number)
+      {"",
+       {0x32}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (ack frame)
+      // (one ack block, 2 byte largest observed, 2 byte block length)
+      {"",
+       {0x45}},
+      // largest acked
+      {"Unable to read largest acked.",
+       {0x12, 0x34}},
+      // Zero delta time.
+      {"Unable to read ack delay time.",
+       {0x00, 0x00}},
+      // first ack block length.
+      {"Unable to read first ack block length.",
+       {0x12, 0x34}},
+      // num timestamps.
+      {"Unable to read num received packets.",
+       {0x00}}
+  };
+
+  PacketFragments packet46 = {
+      // type (short packet, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (ack frame)
+      // (one ack block, 2 byte largest observed, 2 byte block length)
+      {"",
+       {0x45}},
+      // largest acked
+      {"Unable to read largest acked.",
+       {0x12, 0x34}},
+      // Zero delta time.
+      {"Unable to read ack delay time.",
+       {0x00, 0x00}},
+      // first ack block length.
+      {"Unable to read first ack block length.",
+       {0x12, 0x34}},
+      // num timestamps.
+      {"Unable to read num received packets.",
+       {0x00}}
+  };
+
+  PacketFragments packet99 = {
+      // type (short packet, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+       // frame type (IETF_ACK)
+       // (one ack block, 2 byte largest observed, 2 byte block length)
+       // IETF-Quic ignores the bit-fields in the ack type, all of
+       // that information is encoded elsewhere in the frame.
+       {"",
+        {0x02}},
+       // largest acked
+       {"Unable to read largest acked.",
+        {kVarInt62TwoBytes  + 0x12, 0x34}},
+       // Zero delta time.
+       {"Unable to read ack delay time.",
+        {kVarInt62OneByte + 0x00}},
+      // Ack block count (0 -- no blocks after the first)
+      {"Unable to read ack block count.",
+       {kVarInt62OneByte + 0x00}},
+       // first ack block length - 1.
+       // IETF Quic defines the ack block's value as the "number of
+       // packets that preceed the largest packet number in the block"
+       // which for the 1st ack block is the largest acked field,
+       // above. This means that if we are acking just packet 0x1234
+       // then the 1st ack block will be 0.
+       {"Unable to read first ack block length.",
+        {kVarInt62TwoBytes + 0x12, 0x33}}
+  };
+  // clang-format on
+
+  PacketFragments& fragments =
+      framer_.transport_version() == QUIC_VERSION_99
+          ? packet99
+          : (framer_.transport_version() > QUIC_VERSION_44
+                 ? packet46
+                 : (framer_.transport_version() > QUIC_VERSION_43 ? packet44
+                                                                  : packet));
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+  ASSERT_EQ(1u, visitor_.ack_frames_.size());
+  const QuicAckFrame& frame = *visitor_.ack_frames_[0];
+  EXPECT_EQ(kSmallLargestObserved, LargestAcked(frame));
+  ASSERT_EQ(4660u, frame.packets.NumPacketsSlow());
+
+  CheckFramingBoundaries(fragments, QUIC_INVALID_ACK_DATA);
+}
+
+// This test checks that the ack frame processor correctly identifies
+// and handles the case where the first ack block is larger than the
+// largest_acked packet.
+TEST_P(QuicFramerTest, FirstAckFrameUnderflow) {
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (8 byte connection_id)
+      {"",
+       {0x2C}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (ack frame)
+      // (one ack block, 2 byte largest observed, 2 byte block length)
+      {"",
+       {0x45}},
+      // largest acked
+      {"Unable to read largest acked.",
+       {0x12, 0x34}},
+      // Zero delta time.
+      {"Unable to read ack delay time.",
+       {0x00, 0x00}},
+      // first ack block length.
+      {"Unable to read first ack block length.",
+       {0x88, 0x88}},
+      // num timestamps.
+      {"Underflow with first ack block length 34952 largest acked is 4660.",
+       {0x00}}
+  };
+
+  PacketFragments packet44 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x32}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (ack frame)
+      // (one ack block, 2 byte largest observed, 2 byte block length)
+      {"",
+       {0x45}},
+      // largest acked
+      {"Unable to read largest acked.",
+       {0x12, 0x34}},
+      // Zero delta time.
+      {"Unable to read ack delay time.",
+       {0x00, 0x00}},
+      // first ack block length.
+      {"Unable to read first ack block length.",
+       {0x88, 0x88}},
+      // num timestamps.
+      {"Underflow with first ack block length 34952 largest acked is 4660.",
+       {0x00}}
+  };
+
+  PacketFragments packet46 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (ack frame)
+      // (one ack block, 2 byte largest observed, 2 byte block length)
+      {"",
+       {0x45}},
+      // largest acked
+      {"Unable to read largest acked.",
+       {0x12, 0x34}},
+      // Zero delta time.
+      {"Unable to read ack delay time.",
+       {0x00, 0x00}},
+      // first ack block length.
+      {"Unable to read first ack block length.",
+       {0x88, 0x88}},
+      // num timestamps.
+      {"Underflow with first ack block length 34952 largest acked is 4660.",
+       {0x00}}
+  };
+
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+       // frame type (IETF_ACK)
+       {"",
+        {0x02}},
+       // largest acked
+       {"Unable to read largest acked.",
+        {kVarInt62TwoBytes  + 0x12, 0x34}},
+       // Zero delta time.
+       {"Unable to read ack delay time.",
+        {kVarInt62OneByte + 0x00}},
+       // Ack block count (0 -- no blocks after the first)
+       {"Unable to read ack block count.",
+        {kVarInt62OneByte + 0x00}},
+       // first ack block length.
+       {"Unable to read first ack block length.",
+        {kVarInt62TwoBytes + 0x28, 0x88}}
+  };
+  // clang-format on
+
+  PacketFragments& fragments =
+      framer_.transport_version() == QUIC_VERSION_99
+          ? packet99
+          : (framer_.transport_version() > QUIC_VERSION_44
+                 ? packet46
+                 : (framer_.transport_version() > QUIC_VERSION_43 ? packet44
+                                                                  : packet));
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+  CheckFramingBoundaries(fragments, QUIC_INVALID_ACK_DATA);
+}
+
+// This test checks that the ack frame processor correctly identifies
+// and handles the case where the third ack block's gap is larger than the
+// available space in the ack range.
+TEST_P(QuicFramerTest, ThirdAckBlockUnderflowGap) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    // for now, only v99
+    return;
+  }
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+       // frame type (IETF_ACK frame)
+       {"",
+        {0x02}},
+       // largest acked
+       {"Unable to read largest acked.",
+        {kVarInt62OneByte  + 63}},
+       // Zero delta time.
+       {"Unable to read ack delay time.",
+        {kVarInt62OneByte + 0x00}},
+       // Ack block count (2 -- 2 blocks after the first)
+       {"Unable to read ack block count.",
+        {kVarInt62OneByte + 0x02}},
+       // first ack block length.
+       {"Unable to read first ack block length.",
+        {kVarInt62OneByte + 13}},  // Ack 14 packets, range 50..63 (inclusive)
+
+       {"Unable to read gap block value.",
+        {kVarInt62OneByte + 9}},  // Gap 10 packets, 40..49 (inclusive)
+       {"Unable to read ack block value.",
+        {kVarInt62OneByte + 9}},  // Ack 10 packets, 30..39 (inclusive)
+       {"Unable to read gap block value.",
+        {kVarInt62OneByte + 29}},  // A gap of 30 packets (0..29 inclusive)
+                                   // should be too big, leaving no room
+                                   // for the ack.
+       {"Underflow with gap block length 30 previous ack block start is 30.",
+        {kVarInt62OneByte + 10}},  // Don't care
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+  EXPECT_EQ(
+      framer_.detailed_error(),
+      "Underflow with gap block length 30 previous ack block start is 30.");
+  CheckFramingBoundaries(packet99, QUIC_INVALID_ACK_DATA);
+}
+
+// This test checks that the ack frame processor correctly identifies
+// and handles the case where the third ack block's length is larger than the
+// available space in the ack range.
+TEST_P(QuicFramerTest, ThirdAckBlockUnderflowAck) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    // for now, only v99
+    return;
+  }
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+       // frame type (IETF_ACK frame)
+       {"",
+        {0x02}},
+       // largest acked
+       {"Unable to read largest acked.",
+        {kVarInt62OneByte  + 63}},
+       // Zero delta time.
+       {"Unable to read ack delay time.",
+        {kVarInt62OneByte + 0x00}},
+       // Ack block count (2 -- 2 blocks after the first)
+       {"Unable to read ack block count.",
+        {kVarInt62OneByte + 0x02}},
+       // first ack block length.
+       {"Unable to read first ack block length.",
+        {kVarInt62OneByte + 13}},  // only 50 packet numbers "left"
+
+       {"Unable to read gap block value.",
+        {kVarInt62OneByte + 10}},  // Only 40 packet numbers left
+       {"Unable to read ack block value.",
+        {kVarInt62OneByte + 10}},  // only 30 packet numbers left.
+       {"Unable to read gap block value.",
+        {kVarInt62OneByte + 1}},  // Gap is OK, 29 packet numbers left
+      {"Unable to read ack block value.",
+        {kVarInt62OneByte + 30}},  // Use up all 30, should be an error
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+  EXPECT_EQ(framer_.detailed_error(),
+            "Underflow with ack block length 31 latest ack block end is 25.");
+  CheckFramingBoundaries(packet99, QUIC_INVALID_ACK_DATA);
+}
+
+// Tests a variety of ack block wrap scenarios. For example, if the
+// N-1th block causes packet 0 to be acked, then a gap would wrap
+// around to 0x3fffffff ffffffff... Make sure we detect this
+// condition.
+TEST_P(QuicFramerTest, AckBlockUnderflowGapWrap) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    // for now, only v99
+    return;
+  }
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+       // frame type (IETF_ACK frame)
+       {"",
+        {0x02}},
+       // largest acked
+       {"Unable to read largest acked.",
+        {kVarInt62OneByte  + 10}},
+       // Zero delta time.
+       {"Unable to read ack delay time.",
+        {kVarInt62OneByte + 0x00}},
+       // Ack block count (1 -- 1 blocks after the first)
+       {"Unable to read ack block count.",
+        {kVarInt62OneByte + 1}},
+       // first ack block length.
+       {"Unable to read first ack block length.",
+        {kVarInt62OneByte + 9}},  // Ack packets 1..10 (inclusive)
+
+       {"Unable to read gap block value.",
+        {kVarInt62OneByte + 1}},  // Gap of 2 packets (-1...0), should wrap
+       {"Underflow with gap block length 2 previous ack block start is 1.",
+        {kVarInt62OneByte + 9}},  // irrelevant
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+  EXPECT_EQ(framer_.detailed_error(),
+            "Underflow with gap block length 2 previous ack block start is 1.");
+  CheckFramingBoundaries(packet99, QUIC_INVALID_ACK_DATA);
+}
+
+// As AckBlockUnderflowGapWrap, but in this test, it's the ack
+// component of the ack-block that causes the wrap, not the gap.
+TEST_P(QuicFramerTest, AckBlockUnderflowAckWrap) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    // for now, only v99
+    return;
+  }
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+       // frame type (IETF_ACK frame)
+       {"",
+        {0x02}},
+       // largest acked
+       {"Unable to read largest acked.",
+        {kVarInt62OneByte  + 10}},
+       // Zero delta time.
+       {"Unable to read ack delay time.",
+        {kVarInt62OneByte + 0x00}},
+       // Ack block count (1 -- 1 blocks after the first)
+       {"Unable to read ack block count.",
+        {kVarInt62OneByte + 1}},
+       // first ack block length.
+       {"Unable to read first ack block length.",
+        {kVarInt62OneByte + 6}},  // Ack packets 4..10 (inclusive)
+
+       {"Unable to read gap block value.",
+        {kVarInt62OneByte + 1}},  // Gap of 2 packets (2..3)
+       {"Unable to read ack block value.",
+        {kVarInt62OneByte + 9}},  // Should wrap.
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+  EXPECT_EQ(framer_.detailed_error(),
+            "Underflow with ack block length 10 latest ack block end is 1.");
+  CheckFramingBoundaries(packet99, QUIC_INVALID_ACK_DATA);
+}
+
+// An ack block that acks the entire range, 1...0x3fffffffffffffff
+TEST_P(QuicFramerTest, AckBlockAcksEverything) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    // for now, only v99
+    return;
+  }
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+       // frame type (IETF_ACK frame)
+       {"",
+        {0x02}},
+       // largest acked
+       {"Unable to read largest acked.",
+        {kVarInt62EightBytes  + 0x3f, 0xff, 0xff, 0xff,
+         0xff, 0xff, 0xff, 0xff}},
+       // Zero delta time.
+       {"Unable to read ack delay time.",
+        {kVarInt62OneByte + 0x00}},
+       // Ack block count No additional blocks
+       {"Unable to read ack block count.",
+        {kVarInt62OneByte + 0}},
+       // first ack block length.
+       {"Unable to read first ack block length.",
+        {kVarInt62EightBytes  + 0x3f, 0xff, 0xff, 0xff,
+         0xff, 0xff, 0xff, 0xfe}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+  EXPECT_EQ(1u, visitor_.ack_frames_.size());
+  const QuicAckFrame& frame = *visitor_.ack_frames_[0];
+  EXPECT_EQ(1u, frame.packets.NumIntervals());
+  EXPECT_EQ(kLargestIetfLargestObserved, LargestAcked(frame));
+  EXPECT_EQ(kLargestIetfLargestObserved.ToUint64(),
+            frame.packets.NumPacketsSlow());
+}
+
+// This test looks for a malformed ack where
+//  - There is a largest-acked value (that is, the frame is acking
+//    something,
+//  - But the length of the first ack block is 0 saying that no frames
+//    are being acked with the largest-acked value or there are no
+//    additional ack blocks.
+//
+TEST_P(QuicFramerTest, AckFrameFirstAckBlockLengthZero) {
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    // Not applicable to version 99 -- first ack block contains the
+    // number of packets that preceed the largest_acked packet.
+    // A value of 0 means no packets preceed --- that the block's
+    // length is 1. Therefore the condition that this test checks can
+    // not arise.
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (8 byte connection_id)
+      {"",
+       { 0x2C }},
+      // connection_id
+      {"",
+       { 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10 }},
+      // packet number
+      {"",
+       { 0x12, 0x34, 0x56, 0x78 }},
+
+      // frame type (ack frame)
+      // (more than one ack block, 2 byte largest observed, 2 byte block length)
+      {"",
+       { 0x65 }},
+      // largest acked
+      {"Unable to read largest acked.",
+       { 0x12, 0x34 }},
+      // Zero delta time.
+      {"Unable to read ack delay time.",
+       { 0x00, 0x00 }},
+      // num ack blocks ranges.
+      {"Unable to read num of ack blocks.",
+       { 0x01 }},
+      // first ack block length.
+      {"Unable to read first ack block length.",
+       { 0x00, 0x00 }},
+      // gap to next block.
+      { "First block length is zero.",
+        { 0x01 }},
+      // ack block length.
+      { "First block length is zero.",
+        { 0x0e, 0xaf }},
+      // Number of timestamps.
+      { "First block length is zero.",
+        { 0x00 }},
+  };
+
+  PacketFragments packet44 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       { 0x32 }},
+      // connection_id
+      {"",
+       { 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10 }},
+      // packet number
+      {"",
+       { 0x12, 0x34, 0x56, 0x78 }},
+
+      // frame type (ack frame)
+      // (more than one ack block, 2 byte largest observed, 2 byte block length)
+      {"",
+       { 0x65 }},
+      // largest acked
+      {"Unable to read largest acked.",
+       { 0x12, 0x34 }},
+      // Zero delta time.
+      {"Unable to read ack delay time.",
+       { 0x00, 0x00 }},
+      // num ack blocks ranges.
+      {"Unable to read num of ack blocks.",
+       { 0x01 }},
+      // first ack block length.
+      {"Unable to read first ack block length.",
+       { 0x00, 0x00 }},
+      // gap to next block.
+      { "First block length is zero.",
+        { 0x01 }},
+      // ack block length.
+      { "First block length is zero.",
+        { 0x0e, 0xaf }},
+      // Number of timestamps.
+      { "First block length is zero.",
+        { 0x00 }},
+  };
+
+  PacketFragments packet46 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       { 0x43 }},
+      // connection_id
+      {"",
+       { 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10 }},
+      // packet number
+      {"",
+       { 0x12, 0x34, 0x56, 0x78 }},
+
+      // frame type (ack frame)
+      // (more than one ack block, 2 byte largest observed, 2 byte block length)
+      {"",
+       { 0x65 }},
+      // largest acked
+      {"Unable to read largest acked.",
+       { 0x12, 0x34 }},
+      // Zero delta time.
+      {"Unable to read ack delay time.",
+       { 0x00, 0x00 }},
+      // num ack blocks ranges.
+      {"Unable to read num of ack blocks.",
+       { 0x01 }},
+      // first ack block length.
+      {"Unable to read first ack block length.",
+       { 0x00, 0x00 }},
+      // gap to next block.
+      { "First block length is zero.",
+        { 0x01 }},
+      // ack block length.
+      { "First block length is zero.",
+        { 0x0e, 0xaf }},
+      // Number of timestamps.
+      { "First block length is zero.",
+        { 0x00 }},
+  };
+
+  // clang-format on
+  PacketFragments& fragments =
+      framer_.transport_version() > QUIC_VERSION_44
+          ? packet46
+          : (framer_.transport_version() > QUIC_VERSION_43 ? packet44 : packet);
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+  EXPECT_EQ(QUIC_INVALID_ACK_DATA, framer_.error());
+
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+  ASSERT_EQ(1u, visitor_.ack_frames_.size());
+
+  CheckFramingBoundaries(fragments, QUIC_INVALID_ACK_DATA);
+}
+
+TEST_P(QuicFramerTest, AckFrameOneAckBlockMaxLength) {
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (8 byte connection_id)
+      {"",
+       {0x2C}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (ack frame)
+      // (one ack block, 4 byte largest observed, 2 byte block length)
+      {"",
+       {0x49}},
+      // largest acked
+      {"Unable to read largest acked.",
+       {0x12, 0x34, 0x56, 0x78}},
+      // Zero delta time.
+      {"Unable to read ack delay time.",
+       {0x00, 0x00}},
+      // first ack block length.
+      {"Unable to read first ack block length.",
+       {0x12, 0x34}},
+      // num timestamps.
+      {"Unable to read num received packets.",
+       {0x00}}
+  };
+
+  PacketFragments packet44 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x32}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x56, 0x78, 0x9A, 0xBC}},
+      // frame type (ack frame)
+      // (one ack block, 4 byte largest observed, 2 byte block length)
+      {"",
+       {0x49}},
+      // largest acked
+      {"Unable to read largest acked.",
+       {0x12, 0x34, 0x56, 0x78}},
+      // Zero delta time.
+      {"Unable to read ack delay time.",
+       {0x00, 0x00}},
+      // first ack block length.
+      {"Unable to read first ack block length.",
+       {0x12, 0x34}},
+      // num timestamps.
+      {"Unable to read num received packets.",
+       {0x00}}
+  };
+
+  PacketFragments packet46 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x56, 0x78, 0x9A, 0xBC}},
+      // frame type (ack frame)
+      // (one ack block, 4 byte largest observed, 2 byte block length)
+      {"",
+       {0x49}},
+      // largest acked
+      {"Unable to read largest acked.",
+       {0x12, 0x34, 0x56, 0x78}},
+      // Zero delta time.
+      {"Unable to read ack delay time.",
+       {0x00, 0x00}},
+      // first ack block length.
+      {"Unable to read first ack block length.",
+       {0x12, 0x34}},
+      // num timestamps.
+      {"Unable to read num received packets.",
+       {0x00}}
+  };
+
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x56, 0x78, 0x9A, 0xBC}},
+       // frame type (IETF_ACK frame)
+       {"",
+        {0x02}},
+       // largest acked
+       {"Unable to read largest acked.",
+        {kVarInt62FourBytes  + 0x12, 0x34, 0x56, 0x78}},
+       // Zero delta time.
+       {"Unable to read ack delay time.",
+        {kVarInt62OneByte + 0x00}},
+       // Number of ack blocks after first
+       {"Unable to read ack block count.",
+        {kVarInt62OneByte + 0x00}},
+       // first ack block length.
+       {"Unable to read first ack block length.",
+        {kVarInt62TwoBytes  + 0x12, 0x33}}
+  };
+  // clang-format on
+
+  PacketFragments& fragments =
+      framer_.transport_version() == QUIC_VERSION_99
+          ? packet99
+          : (framer_.transport_version() > QUIC_VERSION_44
+                 ? packet46
+                 : (framer_.transport_version() > QUIC_VERSION_43 ? packet44
+                                                                  : packet));
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+  ASSERT_EQ(1u, visitor_.ack_frames_.size());
+  const QuicAckFrame& frame = *visitor_.ack_frames_[0];
+  EXPECT_EQ(kPacketNumber, LargestAcked(frame));
+  ASSERT_EQ(4660u, frame.packets.NumPacketsSlow());
+
+  CheckFramingBoundaries(fragments, QUIC_INVALID_ACK_DATA);
+}
+
+// Tests ability to handle multiple ackblocks after the first ack
+// block. Non-version-99 tests include multiple timestamps as well.
+TEST_P(QuicFramerTest, AckFrameTwoTimeStampsMultipleAckBlocks) {
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (8 byte connection_id)
+      {"",
+       { 0x2C }},
+      // connection_id
+      {"",
+       { 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10 }},
+      // packet number
+      {"",
+       { 0x12, 0x34, 0x56, 0x78 }},
+
+      // frame type (ack frame)
+      // (more than one ack block, 2 byte largest observed, 2 byte block length)
+      {"",
+       { 0x65 }},
+      // largest acked
+      {"Unable to read largest acked.",
+       { 0x12, 0x34 }},
+      // Zero delta time.
+      {"Unable to read ack delay time.",
+       { 0x00, 0x00 }},
+      // num ack blocks ranges.
+      {"Unable to read num of ack blocks.",
+       { 0x04 }},
+      // first ack block length.
+      {"Unable to read first ack block length.",
+       { 0x00, 0x01 }},
+      // gap to next block.
+      { "Unable to read gap to next ack block.",
+        { 0x01 }},
+      // ack block length.
+      { "Unable to ack block length.",
+        { 0x0e, 0xaf }},
+      // gap to next block.
+      { "Unable to read gap to next ack block.",
+        { 0xff }},
+      // ack block length.
+      { "Unable to ack block length.",
+        { 0x00, 0x00 }},
+      // gap to next block.
+      { "Unable to read gap to next ack block.",
+        { 0x91 }},
+      // ack block length.
+      { "Unable to ack block length.",
+        { 0x01, 0xea }},
+      // gap to next block.
+      { "Unable to read gap to next ack block.",
+        { 0x05 }},
+      // ack block length.
+      { "Unable to ack block length.",
+        { 0x00, 0x04 }},
+      // Number of timestamps.
+      { "Unable to read num received packets.",
+        { 0x02 }},
+      // Delta from largest observed.
+      { "Unable to read sequence delta in received packets.",
+        { 0x01 }},
+      // Delta time.
+      { "Unable to read time delta in received packets.",
+        { 0x76, 0x54, 0x32, 0x10 }},
+      // Delta from largest observed.
+      { "Unable to read sequence delta in received packets.",
+        { 0x02 }},
+      // Delta time.
+      { "Unable to read incremental time delta in received packets.",
+        { 0x32, 0x10 }},
+  };
+
+  PacketFragments packet44 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       { 0x32 }},
+      // connection_id
+      {"",
+       { 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10 }},
+      // packet number
+      {"",
+       { 0x12, 0x34, 0x56, 0x78 }},
+
+      // frame type (ack frame)
+      // (more than one ack block, 2 byte largest observed, 2 byte block length)
+      {"",
+       { 0x65 }},
+      // largest acked
+      {"Unable to read largest acked.",
+       { 0x12, 0x34 }},
+      // Zero delta time.
+      {"Unable to read ack delay time.",
+       { 0x00, 0x00 }},
+      // num ack blocks ranges.
+      {"Unable to read num of ack blocks.",
+       { 0x04 }},
+      // first ack block length.
+      {"Unable to read first ack block length.",
+       { 0x00, 0x01 }},
+      // gap to next block.
+      { "Unable to read gap to next ack block.",
+        { 0x01 }},
+      // ack block length.
+      { "Unable to ack block length.",
+        { 0x0e, 0xaf }},
+      // gap to next block.
+      { "Unable to read gap to next ack block.",
+        { 0xff }},
+      // ack block length.
+      { "Unable to ack block length.",
+        { 0x00, 0x00 }},
+      // gap to next block.
+      { "Unable to read gap to next ack block.",
+        { 0x91 }},
+      // ack block length.
+      { "Unable to ack block length.",
+        { 0x01, 0xea }},
+      // gap to next block.
+      { "Unable to read gap to next ack block.",
+        { 0x05 }},
+      // ack block length.
+      { "Unable to ack block length.",
+        { 0x00, 0x04 }},
+      // Number of timestamps.
+      { "Unable to read num received packets.",
+        { 0x02 }},
+      // Delta from largest observed.
+      { "Unable to read sequence delta in received packets.",
+        { 0x01 }},
+      // Delta time.
+      { "Unable to read time delta in received packets.",
+        { 0x76, 0x54, 0x32, 0x10 }},
+      // Delta from largest observed.
+      { "Unable to read sequence delta in received packets.",
+        { 0x02 }},
+      // Delta time.
+      { "Unable to read incremental time delta in received packets.",
+        { 0x32, 0x10 }},
+  };
+
+  PacketFragments packet46 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       { 0x43 }},
+      // connection_id
+      {"",
+       { 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10 }},
+      // packet number
+      {"",
+       { 0x12, 0x34, 0x56, 0x78 }},
+
+      // frame type (ack frame)
+      // (more than one ack block, 2 byte largest observed, 2 byte block length)
+      {"",
+       { 0x65 }},
+      // largest acked
+      {"Unable to read largest acked.",
+       { 0x12, 0x34 }},
+      // Zero delta time.
+      {"Unable to read ack delay time.",
+       { 0x00, 0x00 }},
+      // num ack blocks ranges.
+      {"Unable to read num of ack blocks.",
+       { 0x04 }},
+      // first ack block length.
+      {"Unable to read first ack block length.",
+       { 0x00, 0x01 }},
+      // gap to next block.
+      { "Unable to read gap to next ack block.",
+        { 0x01 }},
+      // ack block length.
+      { "Unable to ack block length.",
+        { 0x0e, 0xaf }},
+      // gap to next block.
+      { "Unable to read gap to next ack block.",
+        { 0xff }},
+      // ack block length.
+      { "Unable to ack block length.",
+        { 0x00, 0x00 }},
+      // gap to next block.
+      { "Unable to read gap to next ack block.",
+        { 0x91 }},
+      // ack block length.
+      { "Unable to ack block length.",
+        { 0x01, 0xea }},
+      // gap to next block.
+      { "Unable to read gap to next ack block.",
+        { 0x05 }},
+      // ack block length.
+      { "Unable to ack block length.",
+        { 0x00, 0x04 }},
+      // Number of timestamps.
+      { "Unable to read num received packets.",
+        { 0x02 }},
+      // Delta from largest observed.
+      { "Unable to read sequence delta in received packets.",
+        { 0x01 }},
+      // Delta time.
+      { "Unable to read time delta in received packets.",
+        { 0x76, 0x54, 0x32, 0x10 }},
+      // Delta from largest observed.
+      { "Unable to read sequence delta in received packets.",
+        { 0x02 }},
+      // Delta time.
+      { "Unable to read incremental time delta in received packets.",
+        { 0x32, 0x10 }},
+  };
+
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       { 0x43 }},
+      // connection_id
+      {"",
+       { 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10 }},
+      // packet number
+      {"",
+       { 0x12, 0x34, 0x56, 0x78 }},
+
+      // frame type (IETF_ACK frame)
+      {"",
+       { 0x02 }},
+       // largest acked
+       {"Unable to read largest acked.",
+        { kVarInt62TwoBytes + 0x12, 0x34 }},   // = 4660
+       // Zero delta time.
+       {"Unable to read ack delay time.",
+        { kVarInt62OneByte + 0x00 }},
+       // number of additional ack blocks
+       {"Unable to read ack block count.",
+        { kVarInt62OneByte + 0x03 }},
+       // first ack block length.
+       {"Unable to read first ack block length.",
+        { kVarInt62OneByte + 0x00 }},  // 1st block length = 1
+
+       // Additional ACK Block #1
+       // gap to next block.
+       { "Unable to read gap block value.",
+         { kVarInt62OneByte + 0x00 }},   // gap of 1 packet
+       // ack block length.
+       { "Unable to read ack block value.",
+         { kVarInt62TwoBytes + 0x0e, 0xae }},   // 3759
+
+       // pre-version-99 test includes an ack block of 0 length. this
+       // can not happen in version 99. ergo the second block is not
+       // present in the v99 test and the gap length of the next block
+       // is the sum of the two gaps in the pre-version-99 tests.
+       // Additional ACK Block #2
+       // gap to next block.
+       { "Unable to read gap block value.",
+         { kVarInt62TwoBytes + 0x01, 0x8f }},  // Gap is 400 (0x190) pkts
+       // ack block length.
+       { "Unable to read ack block value.",
+         { kVarInt62TwoBytes + 0x01, 0xe9 }},  // block is 389 (x1ea) pkts
+
+       // Additional ACK Block #3
+       // gap to next block.
+       { "Unable to read gap block value.",
+         { kVarInt62OneByte + 0x04 }},   // Gap is 5 packets.
+       // ack block length.
+       { "Unable to read ack block value.",
+         { kVarInt62OneByte + 0x03 }},   // block is 3 packets.
+  };
+
+  // clang-format on
+  PacketFragments& fragments =
+      framer_.transport_version() == QUIC_VERSION_99
+          ? packet99
+          : (framer_.transport_version() > QUIC_VERSION_44
+                 ? packet46
+                 : (framer_.transport_version() > QUIC_VERSION_43 ? packet44
+                                                                  : packet));
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+
+  framer_.set_process_timestamps(true);
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+  ASSERT_EQ(1u, visitor_.ack_frames_.size());
+  const QuicAckFrame& frame = *visitor_.ack_frames_[0];
+  EXPECT_EQ(kSmallLargestObserved, LargestAcked(frame));
+  ASSERT_EQ(4254u, frame.packets.NumPacketsSlow());
+  EXPECT_EQ(4u, frame.packets.NumIntervals());
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    EXPECT_EQ(0u, frame.received_packet_times.size());
+  } else {
+    EXPECT_EQ(2u, frame.received_packet_times.size());
+  }
+  CheckFramingBoundaries(fragments, QUIC_INVALID_ACK_DATA);
+}
+
+TEST_P(QuicFramerTest, AckFrameTimeStampDeltaTooHigh) {
+  // clang-format off
+  unsigned char packet[] = {
+      // public flags (8 byte connection_id)
+      0x28,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (ack frame)
+      // (no ack blocks, 1 byte largest observed, 1 byte block length)
+      0x40,
+      // largest acked
+      0x01,
+      // Zero delta time.
+      0x00, 0x00,
+      // first ack block length.
+      0x01,
+      // num timestamps.
+      0x01,
+      // Delta from largest observed.
+      0x01,
+      // Delta time.
+      0x10, 0x32, 0x54, 0x76,
+  };
+
+  unsigned char packet44[] = {
+      // type (short header, 4 byte packet number)
+      0x32,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (ack frame)
+      // (no ack blocks, 1 byte largest observed, 1 byte block length)
+      0x40,
+      // largest acked
+      0x01,
+      // Zero delta time.
+      0x00, 0x00,
+      // first ack block length.
+      0x01,
+      // num timestamps.
+      0x01,
+      // Delta from largest observed.
+      0x01,
+      // Delta time.
+      0x10, 0x32, 0x54, 0x76,
+  };
+
+  unsigned char packet46[] = {
+      // type (short header, 4 byte packet number)
+      0x43,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (ack frame)
+      // (no ack blocks, 1 byte largest observed, 1 byte block length)
+      0x40,
+      // largest acked
+      0x01,
+      // Zero delta time.
+      0x00, 0x00,
+      // first ack block length.
+      0x01,
+      // num timestamps.
+      0x01,
+      // Delta from largest observed.
+      0x01,
+      // Delta time.
+      0x10, 0x32, 0x54, 0x76,
+  };
+  // clang-format on
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    return;
+  }
+  QuicEncryptedPacket encrypted(
+      AsChars(framer_.transport_version() > QUIC_VERSION_44
+                  ? packet46
+                  : (framer_.transport_version() > QUIC_VERSION_43 ? packet44
+                                                                   : packet)),
+      QUIC_ARRAYSIZE(packet), false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  EXPECT_TRUE(QuicTextUtils::StartsWith(
+      framer_.detailed_error(), "delta_from_largest_observed too high"));
+}
+
+TEST_P(QuicFramerTest, AckFrameTimeStampSecondDeltaTooHigh) {
+  // clang-format off
+  unsigned char packet[] = {
+      // public flags (8 byte connection_id)
+      0x28,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (ack frame)
+      // (no ack blocks, 1 byte largest observed, 1 byte block length)
+      0x40,
+      // largest acked
+      0x03,
+      // Zero delta time.
+      0x00, 0x00,
+      // first ack block length.
+      0x03,
+      // num timestamps.
+      0x02,
+      // Delta from largest observed.
+      0x01,
+      // Delta time.
+      0x10, 0x32, 0x54, 0x76,
+      // Delta from largest observed.
+      0x03,
+      // Delta time.
+      0x10, 0x32,
+  };
+
+  unsigned char packet44[] = {
+      // type (short header, 4 byte packet number)
+      0x32,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (ack frame)
+      // (no ack blocks, 1 byte largest observed, 1 byte block length)
+      0x40,
+      // largest acked
+      0x03,
+      // Zero delta time.
+      0x00, 0x00,
+      // first ack block length.
+      0x03,
+      // num timestamps.
+      0x02,
+      // Delta from largest observed.
+      0x01,
+      // Delta time.
+      0x10, 0x32, 0x54, 0x76,
+      // Delta from largest observed.
+      0x03,
+      // Delta time.
+      0x10, 0x32,
+  };
+
+  unsigned char packet46[] = {
+      // type (short header, 4 byte packet number)
+      0x43,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (ack frame)
+      // (no ack blocks, 1 byte largest observed, 1 byte block length)
+      0x40,
+      // largest acked
+      0x03,
+      // Zero delta time.
+      0x00, 0x00,
+      // first ack block length.
+      0x03,
+      // num timestamps.
+      0x02,
+      // Delta from largest observed.
+      0x01,
+      // Delta time.
+      0x10, 0x32, 0x54, 0x76,
+      // Delta from largest observed.
+      0x03,
+      // Delta time.
+      0x10, 0x32,
+  };
+  // clang-format on
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    return;
+  }
+  QuicEncryptedPacket encrypted(
+      AsChars(framer_.transport_version() > QUIC_VERSION_44
+                  ? packet46
+                  : (framer_.transport_version() > QUIC_VERSION_43 ? packet44
+                                                                   : packet)),
+      QUIC_ARRAYSIZE(packet), false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  EXPECT_TRUE(QuicTextUtils::StartsWith(
+      framer_.detailed_error(), "delta_from_largest_observed too high"));
+}
+
+TEST_P(QuicFramerTest, NewStopWaitingFrame) {
+  if (version_.transport_version == QUIC_VERSION_99) {
+    return;
+  }
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (8 byte connection_id)
+      {"",
+       {0x2C}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (stop waiting frame)
+      {"",
+       {0x06}},
+      // least packet number awaiting an ack, delta from packet number.
+      {"Unable to read least unacked delta.",
+        {0x00, 0x00, 0x00, 0x08}}
+  };
+
+  PacketFragments packet44 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x32}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (stop waiting frame)
+      {"",
+       {0x06}},
+      // least packet number awaiting an ack, delta from packet number.
+      {"Unable to read least unacked delta.",
+        {0x00, 0x00, 0x00, 0x08}}
+  };
+
+  PacketFragments packet46 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (stop waiting frame)
+      {"",
+       {0x06}},
+      // least packet number awaiting an ack, delta from packet number.
+      {"Unable to read least unacked delta.",
+        {0x00, 0x00, 0x00, 0x08}}
+  };
+  // clang-format on
+
+  PacketFragments& fragments =
+      framer_.transport_version() > QUIC_VERSION_44
+          ? packet46
+          : (framer_.transport_version() > QUIC_VERSION_43 ? packet44 : packet);
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+  ASSERT_EQ(1u, visitor_.stop_waiting_frames_.size());
+  const QuicStopWaitingFrame& frame = *visitor_.stop_waiting_frames_[0];
+  EXPECT_EQ(kLeastUnacked, frame.least_unacked);
+
+  CheckFramingBoundaries(fragments, QUIC_INVALID_STOP_WAITING_DATA);
+}
+
+TEST_P(QuicFramerTest, InvalidNewStopWaitingFrame) {
+  if (version_.transport_version == QUIC_VERSION_99) {
+    return;
+  }
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x2C,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+    // frame type (stop waiting frame)
+    0x06,
+    // least packet number awaiting an ack, delta from packet number.
+    0x13, 0x34, 0x56, 0x78,
+    0x9A, 0xA8,
+  };
+
+  unsigned char packet44[] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+    // frame type (stop waiting frame)
+    0x06,
+    // least packet number awaiting an ack, delta from packet number.
+    0x57, 0x78, 0x9A, 0xA8,
+  };
+
+  unsigned char packet46[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+    // frame type (stop waiting frame)
+    0x06,
+    // least packet number awaiting an ack, delta from packet number.
+    0x57, 0x78, 0x9A, 0xA8,
+  };
+  // clang-format on
+
+  QuicEncryptedPacket encrypted(
+      AsChars(framer_.transport_version() > QUIC_VERSION_44
+                  ? packet46
+                  : (framer_.transport_version() > QUIC_VERSION_43 ? packet44
+                                                                   : packet)),
+      framer_.transport_version() > QUIC_VERSION_43 ? QUIC_ARRAYSIZE(packet44)
+                                                    : QUIC_ARRAYSIZE(packet),
+      false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_INVALID_STOP_WAITING_DATA, framer_.error());
+  EXPECT_EQ("Invalid unacked delta.", framer_.detailed_error());
+}
+
+TEST_P(QuicFramerTest, RstStreamFrame) {
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (8 byte connection_id)
+      {"",
+       {0x28}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (rst stream frame)
+      {"",
+       {0x01}},
+      // stream id
+      {"Unable to read stream_id.",
+       {0x01, 0x02, 0x03, 0x04}},
+      // sent byte offset
+      {"Unable to read rst stream sent byte offset.",
+       {0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+      // error code
+      {"Unable to read rst stream error code.",
+       {0x00, 0x00, 0x00, 0x01}}
+  };
+
+  PacketFragments packet44 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x32}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (rst stream frame)
+      {"",
+       {0x01}},
+      // stream id
+      {"Unable to read stream_id.",
+       {0x01, 0x02, 0x03, 0x04}},
+      // sent byte offset
+      {"Unable to read rst stream sent byte offset.",
+       {0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+      // error code
+      {"Unable to read rst stream error code.",
+       {0x00, 0x00, 0x00, 0x01}}
+  };
+
+  PacketFragments packet46 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (rst stream frame)
+      {"",
+       {0x01}},
+      // stream id
+      {"Unable to read stream_id.",
+       {0x01, 0x02, 0x03, 0x04}},
+      // sent byte offset
+      {"Unable to read rst stream sent byte offset.",
+       {0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+      // error code
+      {"Unable to read rst stream error code.",
+       {0x00, 0x00, 0x00, 0x01}}
+  };
+
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (IETF_RST_STREAM frame)
+      {"",
+       {0x04}},
+      // stream id
+      {"Unable to read rst stream stream id.",
+       {kVarInt62FourBytes + 0x01, 0x02, 0x03, 0x04}},
+      // application error code
+      {"Unable to read rst stream error code.",
+       {0x00, 0x01}},   // Not varint62 encoded
+      // Final Offset
+      {"Unable to read rst stream sent byte offset.",
+       {kVarInt62EightBytes + 0x3a, 0x98, 0xFE, 0xDC, 0x32, 0x10, 0x76, 0x54}}
+  };
+  // clang-format on
+
+  PacketFragments& fragments =
+      framer_.transport_version() == QUIC_VERSION_99
+          ? packet99
+          : (framer_.transport_version() > QUIC_VERSION_44
+                 ? packet46
+                 : (framer_.transport_version() > QUIC_VERSION_43 ? packet44
+                                                                  : packet));
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(kStreamId, visitor_.rst_stream_frame_.stream_id);
+  EXPECT_EQ(0x01, visitor_.rst_stream_frame_.error_code);
+  EXPECT_EQ(kStreamOffset, visitor_.rst_stream_frame_.byte_offset);
+  CheckFramingBoundaries(fragments, QUIC_INVALID_RST_STREAM_DATA);
+}
+
+TEST_P(QuicFramerTest, ConnectionCloseFrame) {
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (8 byte connection_id)
+      {"",
+       {0x28}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (connection close frame)
+      {"",
+       {0x02}},
+      // error code
+      {"Unable to read connection close error code.",
+       {0x00, 0x00, 0x00, 0x11}},
+      {"Unable to read connection close error details.",
+       {
+         // error details length
+         0x0, 0x0d,
+         // error details
+         'b',  'e',  'c',  'a',
+         'u',  's',  'e',  ' ',
+         'I',  ' ',  'c',  'a',
+         'n'}
+      }
+  };
+
+  PacketFragments packet44 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x32}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (connection close frame)
+      {"",
+       {0x02}},
+      // error code
+      {"Unable to read connection close error code.",
+       {0x00, 0x00, 0x00, 0x11}},
+      {"Unable to read connection close error details.",
+       {
+         // error details length
+         0x0, 0x0d,
+         // error details
+         'b',  'e',  'c',  'a',
+         'u',  's',  'e',  ' ',
+         'I',  ' ',  'c',  'a',
+         'n'}
+      }
+  };
+
+  PacketFragments packet46 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (connection close frame)
+      {"",
+       {0x02}},
+      // error code
+      {"Unable to read connection close error code.",
+       {0x00, 0x00, 0x00, 0x11}},
+      {"Unable to read connection close error details.",
+       {
+         // error details length
+         0x0, 0x0d,
+         // error details
+         'b',  'e',  'c',  'a',
+         'u',  's',  'e',  ' ',
+         'I',  ' ',  'c',  'a',
+         'n'}
+      }
+  };
+
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (IETF_CONNECTION_CLOSE frame)
+      {"",
+       {0x1c}},
+      // error code
+      {"Unable to read connection close error code.",
+       {0x00, 0x11}},
+      {"Unable to read connection close frame type.",
+       {kVarInt62TwoBytes + 0x12, 0x34 }},
+      {"Unable to read connection close error details.",
+       {
+         // error details length
+         kVarInt62OneByte + 0x0d,
+         // error details
+         'b',  'e',  'c',  'a',
+         'u',  's',  'e',  ' ',
+         'I',  ' ',  'c',  'a',
+         'n'}
+      }
+  };
+  // clang-format on
+
+  PacketFragments& fragments =
+      framer_.transport_version() == QUIC_VERSION_99
+          ? packet99
+          : (framer_.transport_version() > QUIC_VERSION_44
+                 ? packet46
+                 : (framer_.transport_version() > QUIC_VERSION_43 ? packet44
+                                                                  : packet));
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+
+  EXPECT_EQ(0x11, visitor_.connection_close_frame_.error_code);
+  EXPECT_EQ("because I can", visitor_.connection_close_frame_.error_details);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    EXPECT_EQ(0x1234u, visitor_.connection_close_frame_.frame_type);
+  }
+
+  ASSERT_EQ(0u, visitor_.ack_frames_.size());
+
+  CheckFramingBoundaries(fragments, QUIC_INVALID_CONNECTION_CLOSE_DATA);
+}
+
+TEST_P(QuicFramerTest, ApplicationCloseFrame) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    // This frame does not exist in versions other than 99.
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (IETF_APPLICATION_CLOSE frame)
+      {"",
+       {0x1d}},
+      // error code
+      {"Unable to read application close error code.",
+       {0x00, 0x11}},
+      {"Unable to read application close error details.",
+       {
+         // error details length
+         kVarInt62OneByte + 0x0d,
+         // error details
+         'b',  'e',  'c',  'a',
+         'u',  's',  'e',  ' ',
+         'I',  ' ',  'c',  'a',
+         'n'}
+      }
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+
+  EXPECT_EQ(0x11, visitor_.application_close_frame_.error_code);
+  EXPECT_EQ("because I can", visitor_.application_close_frame_.error_details);
+
+  ASSERT_EQ(0u, visitor_.ack_frames_.size());
+
+  CheckFramingBoundaries(packet99, QUIC_INVALID_APPLICATION_CLOSE_DATA);
+}
+
+TEST_P(QuicFramerTest, GoAwayFrame) {
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    // This frame is not supported in version 99.
+    return;
+  }
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (8 byte connection_id)
+      {"",
+       {0x28}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (go away frame)
+      {"",
+       {0x03}},
+      // error code
+      {"Unable to read go away error code.",
+       {0x00, 0x00, 0x00, 0x09}},
+      // stream id
+      {"Unable to read last good stream id.",
+       {0x01, 0x02, 0x03, 0x04}},
+      // stream id
+      {"Unable to read goaway reason.",
+       {
+         // error details length
+         0x0, 0x0d,
+         // error details
+         'b',  'e',  'c',  'a',
+         'u',  's',  'e',  ' ',
+         'I',  ' ',  'c',  'a',
+         'n'}
+      }
+  };
+
+  PacketFragments packet44 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x32}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (go away frame)
+      {"",
+       {0x03}},
+      // error code
+      {"Unable to read go away error code.",
+       {0x00, 0x00, 0x00, 0x09}},
+      // stream id
+      {"Unable to read last good stream id.",
+       {0x01, 0x02, 0x03, 0x04}},
+      // stream id
+      {"Unable to read goaway reason.",
+       {
+         // error details length
+         0x0, 0x0d,
+         // error details
+         'b',  'e',  'c',  'a',
+         'u',  's',  'e',  ' ',
+         'I',  ' ',  'c',  'a',
+         'n'}
+      }
+  };
+
+  PacketFragments packet46 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (go away frame)
+      {"",
+       {0x03}},
+      // error code
+      {"Unable to read go away error code.",
+       {0x00, 0x00, 0x00, 0x09}},
+      // stream id
+      {"Unable to read last good stream id.",
+       {0x01, 0x02, 0x03, 0x04}},
+      // stream id
+      {"Unable to read goaway reason.",
+       {
+         // error details length
+         0x0, 0x0d,
+         // error details
+         'b',  'e',  'c',  'a',
+         'u',  's',  'e',  ' ',
+         'I',  ' ',  'c',  'a',
+         'n'}
+      }
+  };
+  // clang-format on
+
+  PacketFragments& fragments =
+      framer_.transport_version() > QUIC_VERSION_44
+          ? packet46
+          : (framer_.transport_version() > QUIC_VERSION_43 ? packet44 : packet);
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(kStreamId, visitor_.goaway_frame_.last_good_stream_id);
+  EXPECT_EQ(0x9, visitor_.goaway_frame_.error_code);
+  EXPECT_EQ("because I can", visitor_.goaway_frame_.reason_phrase);
+
+  CheckFramingBoundaries(fragments, QUIC_INVALID_GOAWAY_DATA);
+}
+
+TEST_P(QuicFramerTest, WindowUpdateFrame) {
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    // This frame is not in version 99, see MaxDataFrame and MaxStreamDataFrame
+    // for Version 99 equivalents.
+    return;
+  }
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (8 byte connection_id)
+      {"",
+       {0x28}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (window update frame)
+      {"",
+       {0x04}},
+      // stream id
+      {"Unable to read stream_id.",
+       {0x01, 0x02, 0x03, 0x04}},
+      // byte offset
+      {"Unable to read window byte_offset.",
+       {0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+  };
+
+  PacketFragments packet44 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x32}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (window update frame)
+      {"",
+       {0x04}},
+      // stream id
+      {"Unable to read stream_id.",
+       {0x01, 0x02, 0x03, 0x04}},
+      // byte offset
+      {"Unable to read window byte_offset.",
+       {0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+  };
+
+  PacketFragments packet46 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (window update frame)
+      {"",
+       {0x04}},
+      // stream id
+      {"Unable to read stream_id.",
+       {0x01, 0x02, 0x03, 0x04}},
+      // byte offset
+      {"Unable to read window byte_offset.",
+       {0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+  };
+
+  // clang-format on
+
+  PacketFragments& fragments =
+      framer_.transport_version() > QUIC_VERSION_44
+          ? packet46
+          : (framer_.transport_version() > QUIC_VERSION_43 ? packet44 : packet);
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(kStreamId, visitor_.window_update_frame_.stream_id);
+  EXPECT_EQ(kStreamOffset, visitor_.window_update_frame_.byte_offset);
+
+  CheckFramingBoundaries(fragments, QUIC_INVALID_WINDOW_UPDATE_DATA);
+}
+
+TEST_P(QuicFramerTest, MaxDataFrame) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    // This frame is available only in version 99.
+    return;
+  }
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (IETF_MAX_DATA frame)
+      {"",
+       {0x10}},
+      // byte offset
+      {"Can not read MAX_DATA byte-offset",
+       {kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(QuicUtils::GetInvalidStreamId(framer_.transport_version()),
+            visitor_.window_update_frame_.stream_id);
+  EXPECT_EQ(kStreamOffset, visitor_.window_update_frame_.byte_offset);
+
+  CheckFramingBoundaries(packet99, QUIC_INVALID_MAX_DATA_FRAME_DATA);
+}
+
+TEST_P(QuicFramerTest, MaxStreamDataFrame) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    // This frame available only in version 99.
+    return;
+  }
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (IETF_MAX_STREAM_DATA frame)
+      {"",
+       {0x11}},
+      // stream id
+      {"Can not read MAX_STREAM_DATA stream id",
+       {kVarInt62FourBytes + 0x01, 0x02, 0x03, 0x04}},
+      // byte offset
+      {"Can not read MAX_STREAM_DATA byte-count",
+       {kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(kStreamId, visitor_.window_update_frame_.stream_id);
+  EXPECT_EQ(kStreamOffset, visitor_.window_update_frame_.byte_offset);
+
+  CheckFramingBoundaries(packet99, QUIC_INVALID_MAX_STREAM_DATA_FRAME_DATA);
+}
+
+TEST_P(QuicFramerTest, BlockedFrame) {
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (8 byte connection_id)
+      {"",
+       {0x28}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (blocked frame)
+      {"",
+       {0x05}},
+      // stream id
+      {"Unable to read stream_id.",
+       {0x01, 0x02, 0x03, 0x04}},
+  };
+
+  PacketFragments packet44 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x32}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (blocked frame)
+      {"",
+       {0x05}},
+      // stream id
+      {"Unable to read stream_id.",
+       {0x01, 0x02, 0x03, 0x04}},
+  };
+
+  PacketFragments packet46 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (blocked frame)
+      {"",
+       {0x05}},
+      // stream id
+      {"Unable to read stream_id.",
+       {0x01, 0x02, 0x03, 0x04}},
+  };
+
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (IETF_STREAM_BLOCKED frame)
+      {"",
+       {0x15}},
+      // stream id
+      {"Can not read stream blocked stream id.",
+       {kVarInt62FourBytes + 0x01, 0x02, 0x03, 0x04}},
+      // Offset
+      {"Can not read stream blocked offset.",
+       {kVarInt62EightBytes + 0x3a, 0x98, 0xFE, 0xDC, 0x32, 0x10, 0x76, 0x54}},
+  };
+  // clang-format on
+
+  PacketFragments& fragments =
+      framer_.transport_version() == QUIC_VERSION_99
+          ? packet99
+          : (framer_.transport_version() > QUIC_VERSION_44
+                 ? packet46
+                 : (framer_.transport_version() > QUIC_VERSION_43 ? packet44
+                                                                  : packet));
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    EXPECT_EQ(kStreamOffset, visitor_.blocked_frame_.offset);
+  } else {
+    EXPECT_EQ(0u, visitor_.blocked_frame_.offset);
+  }
+  EXPECT_EQ(kStreamId, visitor_.blocked_frame_.stream_id);
+
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    CheckFramingBoundaries(fragments, QUIC_INVALID_STREAM_BLOCKED_DATA);
+  } else {
+    CheckFramingBoundaries(fragments, QUIC_INVALID_BLOCKED_DATA);
+  }
+}
+
+TEST_P(QuicFramerTest, PingFrame) {
+  // clang-format off
+  unsigned char packet[] = {
+     // public flags (8 byte connection_id)
+     0x28,
+     // connection_id
+     0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+     // packet number
+     0x12, 0x34, 0x56, 0x78,
+
+     // frame type (ping frame)
+     0x07,
+    };
+
+  unsigned char packet44[] = {
+     // type (short header, 4 byte packet number)
+     0x32,
+     // connection_id
+     0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+     // packet number
+     0x12, 0x34, 0x56, 0x78,
+
+     // frame type
+     0x07,
+    };
+
+  unsigned char packet46[] = {
+     // type (short header, 4 byte packet number)
+     0x43,
+     // connection_id
+     0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+     // packet number
+     0x12, 0x34, 0x56, 0x78,
+
+     // frame type
+     0x07,
+    };
+
+  unsigned char packet99[] = {
+     // type (short header, 4 byte packet number)
+     0x43,
+     // connection_id
+     0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+     // packet number
+     0x12, 0x34, 0x56, 0x78,
+
+     // frame type (IETF_PING frame)
+     0x01,
+    };
+  // clang-format on
+
+  QuicEncryptedPacket encrypted(
+      AsChars(framer_.transport_version() == QUIC_VERSION_99
+                  ? packet99
+                  : (framer_.transport_version() > QUIC_VERSION_44
+                         ? packet46
+                         : framer_.transport_version() > QUIC_VERSION_43
+                               ? packet44
+                               : packet)),
+      framer_.transport_version() == QUIC_VERSION_99
+          ? QUIC_ARRAYSIZE(packet99)
+          : (framer_.transport_version() > QUIC_VERSION_44
+                 ? QUIC_ARRAYSIZE(packet46)
+                 : framer_.transport_version() > QUIC_VERSION_43
+                       ? QUIC_ARRAYSIZE(packet44)
+                       : QUIC_ARRAYSIZE(packet)),
+      false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(1u, visitor_.ping_frames_.size());
+
+  // No need to check the PING frame boundaries because it has no payload.
+}
+
+TEST_P(QuicFramerTest, MessageFrame) {
+  if (framer_.transport_version() <= QUIC_VERSION_44) {
+    return;
+  }
+  // clang-format off
+  PacketFragments packet45 = {
+       // type (short header, 4 byte packet number)
+       {"",
+        {0x32}},
+       // connection_id
+       {"",
+        {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+       // packet number
+       {"",
+        {0x12, 0x34, 0x56, 0x78}},
+       // message frame type.
+       {"",
+        { 0x21 }},
+       // message length
+       {"Unable to read message length",
+        {0x07}},
+       // message data
+       {"Unable to read message data",
+        {'m', 'e', 's', 's', 'a', 'g', 'e'}},
+        // message frame no length.
+        {"",
+         { 0x20 }},
+        // message data
+        {{},
+         {'m', 'e', 's', 's', 'a', 'g', 'e', '2'}},
+   };
+
+  PacketFragments packet46 = {
+       // type (short header, 4 byte packet number)
+       {"",
+        {0x43}},
+       // connection_id
+       {"",
+        {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+       // packet number
+       {"",
+        {0x12, 0x34, 0x56, 0x78}},
+       // message frame type.
+       {"",
+        { 0x21 }},
+       // message length
+       {"Unable to read message length",
+        {0x07}},
+       // message data
+       {"Unable to read message data",
+        {'m', 'e', 's', 's', 'a', 'g', 'e'}},
+        // message frame no length.
+        {"",
+         { 0x20 }},
+        // message data
+        {{},
+         {'m', 'e', 's', 's', 'a', 'g', 'e', '2'}},
+   };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(AssemblePacketFromFragments(
+      framer_.transport_version() > QUIC_VERSION_44 ? packet46 : packet45));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  ASSERT_EQ(2u, visitor_.message_frames_.size());
+  EXPECT_EQ(7u, visitor_.message_frames_[0]->message_length);
+  EXPECT_EQ(8u, visitor_.message_frames_[1]->message_length);
+
+  CheckFramingBoundaries(
+      framer_.transport_version() > QUIC_VERSION_44 ? packet46 : packet45,
+      QUIC_INVALID_MESSAGE_DATA);
+}
+
+TEST_P(QuicFramerTest, PublicResetPacketV33) {
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (public reset, 8 byte connection_id)
+      {"",
+       {0x0A}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      {"Unable to read reset message.",
+       {
+         // message tag (kPRST)
+         'P', 'R', 'S', 'T',
+         // num_entries (2) + padding
+         0x02, 0x00, 0x00, 0x00,
+         // tag kRNON
+         'R', 'N', 'O', 'N',
+         // end offset 8
+         0x08, 0x00, 0x00, 0x00,
+         // tag kRSEQ
+         'R', 'S', 'E', 'Q',
+         // end offset 16
+         0x10, 0x00, 0x00, 0x00,
+         // nonce proof
+         0x89, 0x67, 0x45, 0x23,
+         0x01, 0xEF, 0xCD, 0xAB,
+         // rejected packet number
+         0xBC, 0x9A, 0x78, 0x56,
+         0x34, 0x12, 0x00, 0x00,
+       }
+      }
+  };
+  // clang-format on
+  if (framer_.transport_version() > QUIC_VERSION_43) {
+    return;
+  }
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+  ASSERT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.public_reset_packet_.get());
+  EXPECT_EQ(FramerTestConnectionId(),
+            visitor_.public_reset_packet_->connection_id);
+  EXPECT_EQ(kNonceProof, visitor_.public_reset_packet_->nonce_proof);
+  EXPECT_EQ(
+      IpAddressFamily::IP_UNSPEC,
+      visitor_.public_reset_packet_->client_address.host().address_family());
+
+  CheckFramingBoundaries(packet, QUIC_INVALID_PUBLIC_RST_PACKET);
+}
+
+TEST_P(QuicFramerTest, PublicResetPacket) {
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (public reset, 8 byte connection_id)
+      {"",
+       {0x0E}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      {"Unable to read reset message.",
+       {
+         // message tag (kPRST)
+         'P', 'R', 'S', 'T',
+         // num_entries (2) + padding
+         0x02, 0x00, 0x00, 0x00,
+         // tag kRNON
+         'R', 'N', 'O', 'N',
+         // end offset 8
+         0x08, 0x00, 0x00, 0x00,
+         // tag kRSEQ
+         'R', 'S', 'E', 'Q',
+         // end offset 16
+         0x10, 0x00, 0x00, 0x00,
+         // nonce proof
+         0x89, 0x67, 0x45, 0x23,
+         0x01, 0xEF, 0xCD, 0xAB,
+         // rejected packet number
+         0xBC, 0x9A, 0x78, 0x56,
+         0x34, 0x12, 0x00, 0x00,
+       }
+      }
+  };
+  // clang-format on
+
+  if (framer_.transport_version() > QUIC_VERSION_43) {
+    return;
+  }
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+  ASSERT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.public_reset_packet_.get());
+  EXPECT_EQ(FramerTestConnectionId(),
+            visitor_.public_reset_packet_->connection_id);
+  EXPECT_EQ(kNonceProof, visitor_.public_reset_packet_->nonce_proof);
+  EXPECT_EQ(
+      IpAddressFamily::IP_UNSPEC,
+      visitor_.public_reset_packet_->client_address.host().address_family());
+
+  CheckFramingBoundaries(packet, QUIC_INVALID_PUBLIC_RST_PACKET);
+}
+
+TEST_P(QuicFramerTest, PublicResetPacketWithTrailingJunk) {
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (public reset, 8 byte connection_id)
+    0x0A,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // message tag (kPRST)
+    'P', 'R', 'S', 'T',
+    // num_entries (2) + padding
+    0x02, 0x00, 0x00, 0x00,
+    // tag kRNON
+    'R', 'N', 'O', 'N',
+    // end offset 8
+    0x08, 0x00, 0x00, 0x00,
+    // tag kRSEQ
+    'R', 'S', 'E', 'Q',
+    // end offset 16
+    0x10, 0x00, 0x00, 0x00,
+    // nonce proof
+    0x89, 0x67, 0x45, 0x23,
+    0x01, 0xEF, 0xCD, 0xAB,
+    // rejected packet number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12, 0x00, 0x00,
+    // trailing junk
+    'j', 'u', 'n', 'k',
+  };
+  // clang-format on
+  if (framer_.transport_version() > QUIC_VERSION_43) {
+    return;
+  }
+
+  QuicEncryptedPacket encrypted(AsChars(packet), QUIC_ARRAYSIZE(packet), false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  ASSERT_EQ(QUIC_INVALID_PUBLIC_RST_PACKET, framer_.error());
+  EXPECT_EQ("Unable to read reset message.", framer_.detailed_error());
+}
+
+TEST_P(QuicFramerTest, PublicResetPacketWithClientAddress) {
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (public reset, 8 byte connection_id)
+      {"",
+       {0x0A}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      {"Unable to read reset message.",
+       {
+         // message tag (kPRST)
+         'P', 'R', 'S', 'T',
+         // num_entries (2) + padding
+         0x03, 0x00, 0x00, 0x00,
+         // tag kRNON
+         'R', 'N', 'O', 'N',
+         // end offset 8
+         0x08, 0x00, 0x00, 0x00,
+         // tag kRSEQ
+         'R', 'S', 'E', 'Q',
+         // end offset 16
+         0x10, 0x00, 0x00, 0x00,
+         // tag kCADR
+         'C', 'A', 'D', 'R',
+         // end offset 24
+         0x18, 0x00, 0x00, 0x00,
+         // nonce proof
+         0x89, 0x67, 0x45, 0x23,
+         0x01, 0xEF, 0xCD, 0xAB,
+         // rejected packet number
+         0xBC, 0x9A, 0x78, 0x56,
+         0x34, 0x12, 0x00, 0x00,
+         // client address: 4.31.198.44:443
+         0x02, 0x00,
+         0x04, 0x1F, 0xC6, 0x2C,
+         0xBB, 0x01,
+       }
+      }
+  };
+  // clang-format on
+  if (framer_.transport_version() > QUIC_VERSION_43) {
+    return;
+  }
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+  ASSERT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.public_reset_packet_.get());
+  EXPECT_EQ(FramerTestConnectionId(),
+            visitor_.public_reset_packet_->connection_id);
+  EXPECT_EQ(kNonceProof, visitor_.public_reset_packet_->nonce_proof);
+  EXPECT_EQ("4.31.198.44",
+            visitor_.public_reset_packet_->client_address.host().ToString());
+  EXPECT_EQ(443, visitor_.public_reset_packet_->client_address.port());
+
+  CheckFramingBoundaries(packet, QUIC_INVALID_PUBLIC_RST_PACKET);
+}
+
+TEST_P(QuicFramerTest, IetfStatelessResetPacket) {
+  // clang-format off
+  unsigned char packet[] = {
+      // type (short packet, 1 byte packet number)
+      0x50,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // Random bytes
+      0x01, 0x11, 0x02, 0x22, 0x03, 0x33, 0x04, 0x44,
+      0x01, 0x11, 0x02, 0x22, 0x03, 0x33, 0x04, 0x44,
+      0x01, 0x11, 0x02, 0x22, 0x03, 0x33, 0x04, 0x44,
+      0x01, 0x11, 0x02, 0x22, 0x03, 0x33, 0x04, 0x44,
+      // stateless reset token
+      0xB5, 0x69, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  };
+  // clang-format on
+  if (framer_.transport_version() <= QUIC_VERSION_43) {
+    return;
+  }
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  framer_.SetDecrypter(ENCRYPTION_NONE,
+                       QuicMakeUnique<NullDecrypter>(Perspective::IS_CLIENT));
+  decrypter_ = new test::TestDecrypter();
+  framer_.SetAlternativeDecrypter(
+      ENCRYPTION_ZERO_RTT, std::unique_ptr<QuicDecrypter>(decrypter_), false);
+  // This packet cannot be decrypted because diversification nonce is missing.
+  QuicEncryptedPacket encrypted(AsChars(packet), QUIC_ARRAYSIZE(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+  ASSERT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.stateless_reset_packet_.get());
+  EXPECT_EQ(kTestStatelessResetToken,
+            visitor_.stateless_reset_packet_->stateless_reset_token);
+}
+
+TEST_P(QuicFramerTest, IetfStatelessResetPacketInvalidStatelessResetToken) {
+  // clang-format off
+  unsigned char packet[] = {
+      // type (short packet, 1 byte packet number)
+      0x50,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // stateless reset token
+      0xB6, 0x69, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  };
+  // clang-format on
+  if (framer_.transport_version() <= QUIC_VERSION_43) {
+    return;
+  }
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  framer_.SetDecrypter(ENCRYPTION_NONE,
+                       QuicMakeUnique<NullDecrypter>(Perspective::IS_CLIENT));
+  decrypter_ = new test::TestDecrypter();
+  framer_.SetAlternativeDecrypter(
+      ENCRYPTION_ZERO_RTT, std::unique_ptr<QuicDecrypter>(decrypter_), false);
+  // This packet cannot be decrypted because diversification nonce is missing.
+  QuicEncryptedPacket encrypted(AsChars(packet), QUIC_ARRAYSIZE(packet), false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_DECRYPTION_FAILURE, framer_.error());
+  ASSERT_FALSE(visitor_.stateless_reset_packet_);
+}
+
+TEST_P(QuicFramerTest, VersionNegotiationPacket) {
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (version, 8 byte connection_id)
+      {"",
+       {0x29}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // version tag
+      {"Unable to read supported version in negotiation.",
+       {QUIC_VERSION_BYTES,
+        'Q', '2', '.', '0'}},
+  };
+
+  PacketFragments packet44 = {
+      // type (long header)
+      {"",
+       {0x8F}},
+      // version tag
+      {"",
+       {0x00, 0x00, 0x00, 0x00}},
+      {"",
+       {0x05}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // Supported versions
+      {"Unable to read supported version in negotiation.",
+       {QUIC_VERSION_BYTES,
+        'Q', '2', '.', '0'}},
+  };
+  // clang-format on
+
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+
+  PacketFragments& fragments =
+      framer_.transport_version() > QUIC_VERSION_43 ? packet44 : packet;
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(fragments));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+  ASSERT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.version_negotiation_packet_.get());
+  EXPECT_EQ(2u, visitor_.version_negotiation_packet_->versions.size());
+  EXPECT_EQ(GetParam(), visitor_.version_negotiation_packet_->versions[0]);
+
+  // Remove the last version from the packet so that every truncated
+  // version of the packet is invalid, otherwise checking boundaries
+  // is annoyingly complicated.
+  for (size_t i = 0; i < 4; ++i) {
+    fragments.back().fragment.pop_back();
+  }
+  CheckFramingBoundaries(fragments, QUIC_INVALID_VERSION_NEGOTIATION_PACKET);
+}
+
+TEST_P(QuicFramerTest, OldVersionNegotiationPacket) {
+  // clang-format off
+  PacketFragments packet = {
+      // public flags (version, 8 byte connection_id)
+      {"",
+       {0x2D}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // version tag
+      {"Unable to read supported version in negotiation.",
+       {QUIC_VERSION_BYTES,
+        'Q', '2', '.', '0'}},
+  };
+  // clang-format on
+
+  if (framer_.transport_version() > QUIC_VERSION_43) {
+    return;
+  }
+
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+  ASSERT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.version_negotiation_packet_.get());
+  EXPECT_EQ(2u, visitor_.version_negotiation_packet_->versions.size());
+  EXPECT_EQ(GetParam(), visitor_.version_negotiation_packet_->versions[0]);
+
+  // Remove the last version from the packet so that every truncated
+  // version of the packet is invalid, otherwise checking boundaries
+  // is annoyingly complicated.
+  for (size_t i = 0; i < 4; ++i) {
+    packet.back().fragment.pop_back();
+  }
+  CheckFramingBoundaries(packet, QUIC_INVALID_VERSION_NEGOTIATION_PACKET);
+}
+
+TEST_P(QuicFramerTest, BuildPaddingFramePacket) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicFrames frames = {QuicFrame(QuicPaddingFrame())};
+
+  // clang-format off
+  unsigned char packet[kMaxPacketSize] = {
+    // public flags (8 byte connection_id)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  unsigned char packet44[kMaxPacketSize] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  unsigned char packet46[kMaxPacketSize] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  unsigned char packet99[kMaxPacketSize] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+  // clang-format on
+
+  unsigned char* p = packet;
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+  }
+
+  uint64_t header_size = GetPacketHeaderSize(
+      framer_.transport_version(), PACKET_8BYTE_CONNECTION_ID,
+      PACKET_0BYTE_CONNECTION_ID, !kIncludeVersion,
+      !kIncludeDiversificationNonce, PACKET_4BYTE_PACKET_NUMBER,
+      VARIABLE_LENGTH_INTEGER_LENGTH_0, 0, VARIABLE_LENGTH_INTEGER_LENGTH_0);
+  memset(p + header_size + 1, 0x00, kMaxPacketSize - header_size - 1);
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError(
+      "constructed packet", data->data(), data->length(), AsChars(p),
+      framer_.transport_version() > QUIC_VERSION_43 ? QUIC_ARRAYSIZE(packet44)
+                                                    : QUIC_ARRAYSIZE(packet));
+}
+
+TEST_P(QuicFramerTest, BuildStreamFramePacketWithNewPaddingFrame) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+  QuicStreamFrame stream_frame(kStreamId, true, kStreamOffset,
+                               QuicStringPiece("hello world!"));
+  QuicPaddingFrame padding_frame(2);
+  QuicFrames frames = {QuicFrame(padding_frame), QuicFrame(stream_frame),
+                       QuicFrame(padding_frame)};
+
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // paddings
+    0x00, 0x00,
+    // frame type (stream frame with fin)
+    0xFF,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // offset
+    0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // data length
+    0x00, 0x0c,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+    // paddings
+    0x00, 0x00,
+  };
+
+  unsigned char packet44[] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // paddings
+    0x00, 0x00,
+    // frame type (stream frame with fin)
+    0xFF,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // offset
+    0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // data length
+    0x00, 0x0c,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+    // paddings
+    0x00, 0x00,
+  };
+
+  unsigned char packet46[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // paddings
+    0x00, 0x00,
+    // frame type (stream frame with fin)
+    0xFF,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // offset
+    0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // data length
+    0x00, 0x0c,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+    // paddings
+    0x00, 0x00,
+  };
+
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // paddings
+    0x00, 0x00,
+    // frame type (IETF_STREAM with FIN, LEN, and OFFSET bits set)
+    0x08 | 0x01 | 0x02 | 0x04,
+    // stream id
+    kVarInt62FourBytes + 0x01, 0x02, 0x03, 0x04,
+    // offset
+    kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // data length
+    kVarInt62OneByte + 0x0c,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+    // paddings
+    0x00, 0x00,
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    p_size = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    p_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+  QuicEncryptedPacket encrypted(AsChars(p), p_size, false);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(p), p_size);
+}
+
+TEST_P(QuicFramerTest, Build4ByteSequenceNumberPaddingFramePacket) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number_length = PACKET_4BYTE_PACKET_NUMBER;
+  header.packet_number = kPacketNumber;
+
+  QuicFrames frames = {QuicFrame(QuicPaddingFrame())};
+
+  // clang-format off
+  unsigned char packet[kMaxPacketSize] = {
+    // public flags (8 byte connection_id and 4 byte packet number)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  unsigned char packet44[kMaxPacketSize] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  unsigned char packet46[kMaxPacketSize] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  unsigned char packet99[kMaxPacketSize] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+  // clang-format on
+
+  unsigned char* p = packet;
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+  }
+
+  uint64_t header_size = GetPacketHeaderSize(
+      framer_.transport_version(), PACKET_8BYTE_CONNECTION_ID,
+      PACKET_0BYTE_CONNECTION_ID, !kIncludeVersion,
+      !kIncludeDiversificationNonce, PACKET_4BYTE_PACKET_NUMBER,
+      VARIABLE_LENGTH_INTEGER_LENGTH_0, 0, VARIABLE_LENGTH_INTEGER_LENGTH_0);
+  memset(p + header_size + 1, 0x00, kMaxPacketSize - header_size - 1);
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError(
+      "constructed packet", data->data(), data->length(), AsChars(p),
+      framer_.transport_version() > QUIC_VERSION_43 ? QUIC_ARRAYSIZE(packet44)
+                                                    : QUIC_ARRAYSIZE(packet));
+}
+
+TEST_P(QuicFramerTest, Build2ByteSequenceNumberPaddingFramePacket) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number_length = PACKET_2BYTE_PACKET_NUMBER;
+  header.packet_number = kPacketNumber;
+
+  QuicFrames frames = {QuicFrame(QuicPaddingFrame())};
+
+  // clang-format off
+  unsigned char packet[kMaxPacketSize] = {
+    // public flags (8 byte connection_id and 2 byte packet number)
+    0x18,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x56, 0x78,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  unsigned char packet44[kMaxPacketSize] = {
+    // type (short header, 2 byte packet number)
+    0x31,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x56, 0x78,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  unsigned char packet46[kMaxPacketSize] = {
+    // type (short header, 2 byte packet number)
+    0x41,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x56, 0x78,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  unsigned char packet99[kMaxPacketSize] = {
+    // type (short header, 2 byte packet number)
+    0x41,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x56, 0x78,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+  // clang-format on
+
+  unsigned char* p = packet;
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+  }
+
+  uint64_t header_size = GetPacketHeaderSize(
+      framer_.transport_version(), PACKET_8BYTE_CONNECTION_ID,
+      PACKET_0BYTE_CONNECTION_ID, !kIncludeVersion,
+      !kIncludeDiversificationNonce, PACKET_2BYTE_PACKET_NUMBER,
+      VARIABLE_LENGTH_INTEGER_LENGTH_0, 0, VARIABLE_LENGTH_INTEGER_LENGTH_0);
+  memset(p + header_size + 1, 0x00, kMaxPacketSize - header_size - 1);
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError(
+      "constructed packet", data->data(), data->length(), AsChars(p),
+      framer_.transport_version() > QUIC_VERSION_43 ? QUIC_ARRAYSIZE(packet44)
+                                                    : QUIC_ARRAYSIZE(packet));
+}
+
+TEST_P(QuicFramerTest, Build1ByteSequenceNumberPaddingFramePacket) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number_length = PACKET_1BYTE_PACKET_NUMBER;
+  header.packet_number = kPacketNumber;
+
+  QuicFrames frames = {QuicFrame(QuicPaddingFrame())};
+
+  // clang-format off
+  unsigned char packet[kMaxPacketSize] = {
+    // public flags (8 byte connection_id and 1 byte packet number)
+    0x08,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x78,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  unsigned char packet44[kMaxPacketSize] = {
+    // type (short header, 1 byte packet number)
+    0x30,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x78,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  unsigned char packet46[kMaxPacketSize] = {
+    // type (short header, 1 byte packet number)
+    0x40,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x78,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  unsigned char packet99[kMaxPacketSize] = {
+    // type (short header, 1 byte packet number)
+    0x40,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x78,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+  // clang-format on
+
+  unsigned char* p = packet;
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+  }
+
+  uint64_t header_size = GetPacketHeaderSize(
+      framer_.transport_version(), PACKET_8BYTE_CONNECTION_ID,
+      PACKET_0BYTE_CONNECTION_ID, !kIncludeVersion,
+      !kIncludeDiversificationNonce, PACKET_1BYTE_PACKET_NUMBER,
+      VARIABLE_LENGTH_INTEGER_LENGTH_0, 0, VARIABLE_LENGTH_INTEGER_LENGTH_0);
+  memset(p + header_size + 1, 0x00, kMaxPacketSize - header_size - 1);
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError(
+      "constructed packet", data->data(), data->length(), AsChars(p),
+      framer_.transport_version() > QUIC_VERSION_43 ? QUIC_ARRAYSIZE(packet44)
+                                                    : QUIC_ARRAYSIZE(packet));
+}
+
+TEST_P(QuicFramerTest, BuildStreamFramePacket) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+  if (QuicVersionHasLongHeaderLengths(framer_.transport_version())) {
+    header.length_length = VARIABLE_LENGTH_INTEGER_LENGTH_2;
+  }
+
+  QuicStreamFrame stream_frame(kStreamId, true, kStreamOffset,
+                               QuicStringPiece("hello world!"));
+
+  QuicFrames frames = {QuicFrame(stream_frame)};
+
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (stream frame with fin and no length)
+    0xDF,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // offset
+    0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+
+  unsigned char packet44[] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (stream frame with fin and no length)
+    0xDF,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // offset
+    0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+
+  unsigned char packet46[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (stream frame with fin and no length)
+    0xDF,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // offset
+    0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_STREAM frame with FIN and OFFSET, no length)
+    0x08 | 0x01 | 0x04,
+    // stream id
+    kVarInt62FourBytes + 0x01, 0x02, 0x03, 0x04,
+    // offset
+    kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    p_size = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    p_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(p), p_size);
+}
+
+TEST_P(QuicFramerTest, BuildStreamFramePacketWithVersionFlag) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = true;
+  if (framer_.transport_version() > QUIC_VERSION_43) {
+    header.long_packet_type = ZERO_RTT_PROTECTED;
+  }
+  header.packet_number = kPacketNumber;
+  if (QuicVersionHasLongHeaderLengths(framer_.transport_version())) {
+    header.length_length = VARIABLE_LENGTH_INTEGER_LENGTH_2;
+  }
+
+  QuicStreamFrame stream_frame(kStreamId, true, kStreamOffset,
+                               QuicStringPiece("hello world!"));
+  QuicFrames frames = {QuicFrame(stream_frame)};
+
+  // clang-format off
+  unsigned char packet[] = {
+      // public flags (version, 8 byte connection_id)
+      0x2D,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // version tag
+      QUIC_VERSION_BYTES,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (stream frame with fin and no length)
+      0xDF,
+      // stream id
+      0x01, 0x02, 0x03, 0x04,
+      // offset
+      0x3A, 0x98, 0xFE, 0xDC, 0x32, 0x10, 0x76, 0x54,
+      // data
+      'h',  'e',  'l',  'l',  'o',  ' ',  'w',  'o',  'r', 'l', 'd', '!',
+  };
+
+  unsigned char packet44[] = {
+      // type (long header with packet type ZERO_RTT_PROTECTED)
+      0xFC,
+      // version tag
+      QUIC_VERSION_BYTES,
+      // connection_id length
+      0x50,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (stream frame with fin and no length)
+      0xDF,
+      // stream id
+      0x01, 0x02, 0x03, 0x04,
+      // offset
+      0x3A, 0x98, 0xFE, 0xDC, 0x32, 0x10, 0x76, 0x54,
+      // data
+      'h',  'e',  'l',  'l',  'o',  ' ',  'w',  'o',  'r', 'l', 'd', '!',
+  };
+
+  unsigned char packet46[] = {
+      // type (long header with packet type ZERO_RTT_PROTECTED)
+      0xD3,
+      // version tag
+      QUIC_VERSION_BYTES,
+      // connection_id length
+      0x50,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (stream frame with fin and no length)
+      0xDF,
+      // stream id
+      0x01, 0x02, 0x03, 0x04,
+      // offset
+      0x3A, 0x98, 0xFE, 0xDC, 0x32, 0x10, 0x76, 0x54,
+      // data
+      'h',  'e',  'l',  'l',  'o',  ' ',  'w',  'o',  'r', 'l', 'd', '!',
+  };
+
+  unsigned char packet99[] = {
+      // type (long header with packet type ZERO_RTT_PROTECTED)
+      0xD3,
+      // version tag
+      QUIC_VERSION_BYTES,
+      // connection_id length
+      0x50,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // length
+      0x40, 0x1D,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (IETF_STREAM frame with fin and offset, no length)
+      0x08 | 0x01 | 0x04,
+      // stream id
+      kVarInt62FourBytes + 0x01, 0x02, 0x03, 0x04,
+      // offset
+      kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC, 0x32, 0x10, 0x76, 0x54,
+      // data
+      'h',  'e',  'l',  'l',  'o',  ' ',  'w',  'o',  'r', 'l', 'd', '!',
+  };
+  // clang-format on
+
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    p_size = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    p_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(p), p_size);
+}
+
+TEST_P(QuicFramerTest, BuildCryptoFramePacket) {
+  if (framer_.transport_version() < QUIC_VERSION_99) {
+    // CRYPTO frames aren't supported prior to v46.
+    return;
+  }
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  SimpleDataProducer data_producer;
+  framer_.set_data_producer(&data_producer);
+
+  QuicStringPiece crypto_frame_contents("hello world!");
+  QuicCryptoFrame crypto_frame(ENCRYPTION_NONE, kStreamOffset,
+                               crypto_frame_contents.length());
+  data_producer.SaveCryptoData(ENCRYPTION_NONE, kStreamOffset,
+                               crypto_frame_contents);
+
+  QuicFrames frames = {QuicFrame(&crypto_frame)};
+
+  // clang-format off
+  unsigned char packet[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_CRYPTO frame)
+    0x06,
+    // offset
+    kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // length
+    kVarInt62OneByte + 12,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+  // clang-format on
+
+  size_t packet_size = QUIC_ARRAYSIZE(packet);
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet),
+                                      packet_size);
+}
+
+TEST_P(QuicFramerTest, CryptoFrame) {
+  if (framer_.transport_version() < QUIC_VERSION_99) {
+    // CRYPTO frames aren't supported prior to v46.
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (IETF_CRYPTO frame)
+      {"",
+       {0x06}},
+      // offset
+      {"",
+       {kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+        0x32, 0x10, 0x76, 0x54}},
+      // data length
+      {"Invalid data length.",
+       {kVarInt62OneByte + 12}},
+      // data
+      {"Unable to read frame data.",
+       {'h',  'e',  'l',  'l',
+        'o',  ' ',  'w',  'o',
+        'r',  'l',  'd',  '!'}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+  ASSERT_EQ(1u, visitor_.crypto_frames_.size());
+  QuicCryptoFrame* frame = visitor_.crypto_frames_[0].get();
+  EXPECT_EQ(kStreamOffset, frame->offset);
+  EXPECT_EQ("hello world!", QuicString(frame->data_buffer, frame->data_length));
+
+  CheckFramingBoundaries(packet, QUIC_INVALID_FRAME_DATA);
+}
+
+TEST_P(QuicFramerTest, BuildVersionNegotiationPacket) {
+  // clang-format off
+  unsigned char packet[] = {
+      // public flags (version, 8 byte connection_id)
+      0x0D,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // version tag
+      QUIC_VERSION_BYTES,
+  };
+  unsigned char packet44[] = {
+      // type (long header)
+      0x80,
+      // version tag
+      0x00, 0x00, 0x00, 0x00,
+      // connection_id length
+      0x05,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // version tag
+      QUIC_VERSION_BYTES,
+  };
+  // clang-format on
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+
+  QuicConnectionId connection_id = FramerTestConnectionId();
+  std::unique_ptr<QuicEncryptedPacket> data(
+      framer_.BuildVersionNegotiationPacket(
+          connection_id, framer_.transport_version() > QUIC_VERSION_43,
+          SupportedVersions(GetParam())));
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(p), p_size);
+}
+
+TEST_P(QuicFramerTest, BuildAckFramePacketOneAckBlock) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  // Use kSmallLargestObserved to make this test finished in a short time.
+  QuicAckFrame ack_frame = InitAckFrame(kSmallLargestObserved);
+  ack_frame.ack_delay_time = QuicTime::Delta::Zero();
+
+  QuicFrames frames = {QuicFrame(&ack_frame)};
+
+  // clang-format off
+  unsigned char packet[] = {
+      // public flags (8 byte connection_id)
+      0x28,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (ack frame)
+      // (no ack blocks, 2 byte largest observed, 2 byte block length)
+      0x45,
+      // largest acked
+      0x12, 0x34,
+      // Zero delta time.
+      0x00, 0x00,
+      // first ack block length.
+      0x12, 0x34,
+      // num timestamps.
+      0x00,
+  };
+
+  unsigned char packet44[] = {
+      // type (short header, 4 byte packet number)
+      0x32,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (ack frame)
+      // (no ack blocks, 2 byte largest observed, 2 byte block length)
+      0x45,
+      // largest acked
+      0x12, 0x34,
+      // Zero delta time.
+      0x00, 0x00,
+      // first ack block length.
+      0x12, 0x34,
+      // num timestamps.
+      0x00,
+  };
+
+  unsigned char packet46[] = {
+      // type (short header, 4 byte packet number)
+      0x43,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (ack frame)
+      // (no ack blocks, 2 byte largest observed, 2 byte block length)
+      0x45,
+      // largest acked
+      0x12, 0x34,
+      // Zero delta time.
+      0x00, 0x00,
+      // first ack block length.
+      0x12, 0x34,
+      // num timestamps.
+      0x00,
+  };
+
+  unsigned char packet99[] = {
+      // type (short header, 4 byte packet number)
+      0x43,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (IETF_ACK frame)
+      0x02,
+      // largest acked
+      kVarInt62TwoBytes + 0x12, 0x34,
+      // Zero delta time.
+      kVarInt62OneByte + 0x00,
+      // Number of additional ack blocks.
+      kVarInt62OneByte + 0x00,
+      // first ack block length.
+      kVarInt62TwoBytes + 0x12, 0x33,
+  };
+  // clang-format on
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    p_size = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    p_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(p), p_size);
+}
+
+TEST_P(QuicFramerTest, BuildAckFramePacketOneAckBlockMaxLength) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicAckFrame ack_frame = InitAckFrame(kPacketNumber);
+  ack_frame.ack_delay_time = QuicTime::Delta::Zero();
+
+  QuicFrames frames = {QuicFrame(&ack_frame)};
+
+  // clang-format off
+  unsigned char packet[] = {
+      // public flags (8 byte connection_id)
+      0x28,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (ack frame)
+      // (no ack blocks, 4 byte largest observed, 4 byte block length)
+      0x4A,
+      // largest acked
+      0x12, 0x34, 0x56, 0x78,
+      // Zero delta time.
+      0x00, 0x00,
+      // first ack block length.
+      0x12, 0x34, 0x56, 0x78,
+      // num timestamps.
+      0x00,
+  };
+
+  unsigned char packet44[] = {
+      // type (short header, 4 byte packet number)
+      0x32,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (ack frame)
+      // (no ack blocks, 4 byte largest observed, 4 byte block length)
+      0x4A,
+      // largest acked
+      0x12, 0x34, 0x56, 0x78,
+      // Zero delta time.
+      0x00, 0x00,
+      // first ack block length.
+      0x12, 0x34, 0x56, 0x78,
+      // num timestamps.
+      0x00,
+  };
+
+  unsigned char packet46[] = {
+      // type (short header, 4 byte packet number)
+      0x43,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (ack frame)
+      // (no ack blocks, 4 byte largest observed, 4 byte block length)
+      0x4A,
+      // largest acked
+      0x12, 0x34, 0x56, 0x78,
+      // Zero delta time.
+      0x00, 0x00,
+      // first ack block length.
+      0x12, 0x34, 0x56, 0x78,
+      // num timestamps.
+      0x00,
+  };
+
+
+  unsigned char packet99[] = {
+      // type (short header, 4 byte packet number)
+      0x43,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (IETF_ACK frame)
+      0x02,
+      // largest acked
+      kVarInt62FourBytes + 0x12, 0x34, 0x56, 0x78,
+      // Zero delta time.
+      kVarInt62OneByte + 0x00,
+      // Nr. of additional ack blocks
+      kVarInt62OneByte + 0x00,
+      // first ack block length.
+      kVarInt62FourBytes + 0x12, 0x34, 0x56, 0x77,
+  };
+  // clang-format on
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    p_size = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    p_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(p), p_size);
+}
+
+TEST_P(QuicFramerTest, BuildAckFramePacketMultipleAckBlocks) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  // Use kSmallLargestObserved to make this test finished in a short time.
+  QuicAckFrame ack_frame =
+      InitAckFrame({{QuicPacketNumber(1), QuicPacketNumber(5)},
+                    {QuicPacketNumber(10), QuicPacketNumber(500)},
+                    {QuicPacketNumber(900), kSmallMissingPacket},
+                    {kSmallMissingPacket + 1, kSmallLargestObserved + 1}});
+  ack_frame.ack_delay_time = QuicTime::Delta::Zero();
+
+  QuicFrames frames = {QuicFrame(&ack_frame)};
+
+  // clang-format off
+  unsigned char packet[] = {
+      // public flags (8 byte connection_id)
+      0x28,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (ack frame)
+      // (has ack blocks, 2 byte largest observed, 2 byte block length)
+      0x65,
+      // largest acked
+      0x12, 0x34,
+      // Zero delta time.
+      0x00, 0x00,
+      // num ack blocks ranges.
+      0x04,
+      // first ack block length.
+      0x00, 0x01,
+      // gap to next block.
+      0x01,
+      // ack block length.
+      0x0e, 0xaf,
+      // gap to next block.
+      0xff,
+      // ack block length.
+      0x00, 0x00,
+      // gap to next block.
+      0x91,
+      // ack block length.
+      0x01, 0xea,
+      // gap to next block.
+      0x05,
+      // ack block length.
+      0x00, 0x04,
+      // num timestamps.
+      0x00,
+  };
+
+  unsigned char packet44[] = {
+      // type (short header, 4 byte packet number)
+      0x32,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (ack frame)
+      // (has ack blocks, 2 byte largest observed, 2 byte block length)
+      0x65,
+      // largest acked
+      0x12, 0x34,
+      // Zero delta time.
+      0x00, 0x00,
+      // num ack blocks ranges.
+      0x04,
+      // first ack block length.
+      0x00, 0x01,
+      // gap to next block.
+      0x01,
+      // ack block length.
+      0x0e, 0xaf,
+      // gap to next block.
+      0xff,
+      // ack block length.
+      0x00, 0x00,
+      // gap to next block.
+      0x91,
+      // ack block length.
+      0x01, 0xea,
+      // gap to next block.
+      0x05,
+      // ack block length.
+      0x00, 0x04,
+      // num timestamps.
+      0x00,
+  };
+
+  unsigned char packet46[] = {
+      // type (short header, 4 byte packet number)
+      0x43,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (ack frame)
+      // (has ack blocks, 2 byte largest observed, 2 byte block length)
+      0x65,
+      // largest acked
+      0x12, 0x34,
+      // Zero delta time.
+      0x00, 0x00,
+      // num ack blocks ranges.
+      0x04,
+      // first ack block length.
+      0x00, 0x01,
+      // gap to next block.
+      0x01,
+      // ack block length.
+      0x0e, 0xaf,
+      // gap to next block.
+      0xff,
+      // ack block length.
+      0x00, 0x00,
+      // gap to next block.
+      0x91,
+      // ack block length.
+      0x01, 0xea,
+      // gap to next block.
+      0x05,
+      // ack block length.
+      0x00, 0x04,
+      // num timestamps.
+      0x00,
+  };
+
+  unsigned char packet99[] = {
+      // type (short header, 4 byte packet number)
+      0x43,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+
+      // frame type (IETF_ACK frame)
+      0x02,
+      // largest acked
+      kVarInt62TwoBytes + 0x12, 0x34,
+      // Zero delta time.
+      kVarInt62OneByte + 0x00,
+      // num additional ack blocks.
+      kVarInt62OneByte + 0x03,
+      // first ack block length.
+      kVarInt62OneByte + 0x00,
+
+      // gap to next block.
+      kVarInt62OneByte + 0x00,
+      // ack block length.
+      kVarInt62TwoBytes + 0x0e, 0xae,
+
+      // gap to next block.
+      kVarInt62TwoBytes + 0x01, 0x8f,
+      // ack block length.
+      kVarInt62TwoBytes + 0x01, 0xe9,
+
+      // gap to next block.
+      kVarInt62OneByte + 0x04,
+      // ack block length.
+      kVarInt62OneByte + 0x03,
+  };
+  // clang-format on
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    p_size = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    p_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(p), p_size);
+}
+
+TEST_P(QuicFramerTest, BuildAckFramePacketMaxAckBlocks) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  // Use kSmallLargestObservedto make this test finished in a short time.
+  QuicAckFrame ack_frame;
+  ack_frame.largest_acked = kSmallLargestObserved;
+  ack_frame.ack_delay_time = QuicTime::Delta::Zero();
+  // 300 ack blocks.
+  for (size_t i = 2; i < 2 * 300; i += 2) {
+    ack_frame.packets.Add(QuicPacketNumber(i));
+  }
+  ack_frame.packets.AddRange(QuicPacketNumber(600), kSmallLargestObserved + 1);
+
+  QuicFrames frames = {QuicFrame(&ack_frame)};
+
+  // clang-format off
+  unsigned char packet[] = {
+      // public flags (8 byte connection_id)
+      0x28,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+      // frame type (ack frame)
+      // (has ack blocks, 2 byte largest observed, 2 byte block length)
+      0x65,
+      // largest acked
+      0x12, 0x34,
+      // Zero delta time.
+      0x00, 0x00,
+      // num ack blocks ranges.
+      0xff,
+      // first ack block length.
+      0x0f, 0xdd,
+      // 255 = 4 * 63 + 3
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      // num timestamps.
+      0x00,
+  };
+
+  unsigned char packet44[] = {
+      // type (short header, 4 byte packet number)
+      0x32,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+      // frame type (ack frame)
+      // (has ack blocks, 2 byte largest observed, 2 byte block length)
+      0x65,
+      // largest acked
+      0x12, 0x34,
+      // Zero delta time.
+      0x00, 0x00,
+      // num ack blocks ranges.
+      0xff,
+      // first ack block length.
+      0x0f, 0xdd,
+      // 255 = 4 * 63 + 3
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      // num timestamps.
+      0x00,
+  };
+
+  unsigned char packet46[] = {
+      // type (short header, 4 byte packet number)
+      0x43,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+      // frame type (ack frame)
+      // (has ack blocks, 2 byte largest observed, 2 byte block length)
+      0x65,
+      // largest acked
+      0x12, 0x34,
+      // Zero delta time.
+      0x00, 0x00,
+      // num ack blocks ranges.
+      0xff,
+      // first ack block length.
+      0x0f, 0xdd,
+      // 255 = 4 * 63 + 3
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01,
+      // num timestamps.
+      0x00,
+  };
+
+  unsigned char packet99[] = {
+      // type (short header, 4 byte packet number)
+      0x43,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+      // frame type (IETF_ACK frame)
+      0x02,
+      // largest acked
+      kVarInt62TwoBytes + 0x12, 0x34,
+      // Zero delta time.
+      kVarInt62OneByte + 0x00,
+      // num ack blocks ranges.
+      kVarInt62TwoBytes + 0x01, 0x2b,
+      // first ack block length.
+      kVarInt62TwoBytes + 0x0f, 0xdc,
+      // 255 added blocks of gap_size == 1, ack_size == 1
+#define V99AddedBLOCK kVarInt62OneByte + 0x00, kVarInt62OneByte + 0x00
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+      V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK, V99AddedBLOCK,
+
+#undef V99AddedBLOCK
+  };
+  // clang-format on
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    p_size = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    p_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(p), p_size);
+}
+
+TEST_P(QuicFramerTest, BuildNewStopWaitingPacket) {
+  if (version_.transport_version > QUIC_VERSION_43) {
+    return;
+  }
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicStopWaitingFrame stop_waiting_frame;
+  stop_waiting_frame.least_unacked = kLeastUnacked;
+
+  QuicFrames frames = {QuicFrame(stop_waiting_frame)};
+
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (stop waiting frame)
+    0x06,
+    // least packet number awaiting an ack, delta from packet number.
+    0x00, 0x00, 0x00, 0x08,
+  };
+
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet),
+                                      QUIC_ARRAYSIZE(packet));
+}
+
+TEST_P(QuicFramerTest, BuildRstFramePacketQuic) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicRstStreamFrame rst_frame;
+  rst_frame.stream_id = kStreamId;
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    rst_frame.ietf_error_code = 0x01;
+  } else {
+    rst_frame.error_code = static_cast<QuicRstStreamErrorCode>(0x05060708);
+  }
+  rst_frame.byte_offset = 0x0807060504030201;
+
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (rst stream frame)
+    0x01,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // sent byte offset
+    0x08, 0x07, 0x06, 0x05,
+    0x04, 0x03, 0x02, 0x01,
+    // error code
+    0x05, 0x06, 0x07, 0x08,
+  };
+
+  unsigned char packet44[] = {
+    // type (short packet, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (rst stream frame)
+    0x01,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // sent byte offset
+    0x08, 0x07, 0x06, 0x05,
+    0x04, 0x03, 0x02, 0x01,
+    // error code
+    0x05, 0x06, 0x07, 0x08,
+  };
+
+  unsigned char packet46[] = {
+    // type (short packet, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (rst stream frame)
+    0x01,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // sent byte offset
+    0x08, 0x07, 0x06, 0x05,
+    0x04, 0x03, 0x02, 0x01,
+    // error code
+    0x05, 0x06, 0x07, 0x08,
+  };
+
+  unsigned char packet99[] = {
+    // type (short packet, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_RST_STREAM frame)
+    0x04,
+    // stream id
+    kVarInt62FourBytes + 0x01, 0x02, 0x03, 0x04,
+    // error code (not VarInt32 encoded)
+    0x00, 0x01,
+    // sent byte offset
+    kVarInt62EightBytes + 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01
+  };
+  // clang-format on
+
+  QuicFrames frames = {QuicFrame(&rst_frame)};
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    p_size = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    p_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+  QuicEncryptedPacket encrypted(AsChars(p), p_size, false);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(p), p_size);
+}
+
+TEST_P(QuicFramerTest, BuildCloseFramePacket) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicConnectionCloseFrame close_frame;
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    close_frame.ietf_error_code =
+        static_cast<QuicIetfTransportErrorCodes>(0x11);
+    close_frame.frame_type = 0x05;
+  } else {
+    close_frame.error_code = static_cast<QuicErrorCode>(0x05060708);
+  }
+  close_frame.error_details = "because I can";
+
+  QuicFrames frames = {QuicFrame(&close_frame)};
+
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (connection close frame)
+    0x02,
+    // error code
+    0x05, 0x06, 0x07, 0x08,
+    // error details length
+    0x00, 0x0d,
+    // error details
+    'b',  'e',  'c',  'a',
+    'u',  's',  'e',  ' ',
+    'I',  ' ',  'c',  'a',
+    'n',
+  };
+
+  unsigned char packet44[] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (connection close frame)
+    0x02,
+    // error code
+    0x05, 0x06, 0x07, 0x08,
+    // error details length
+    0x00, 0x0d,
+    // error details
+    'b',  'e',  'c',  'a',
+    'u',  's',  'e',  ' ',
+    'I',  ' ',  'c',  'a',
+    'n',
+  };
+
+  unsigned char packet46[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (connection close frame)
+    0x02,
+    // error code
+    0x05, 0x06, 0x07, 0x08,
+    // error details length
+    0x00, 0x0d,
+    // error details
+    'b',  'e',  'c',  'a',
+    'u',  's',  'e',  ' ',
+    'I',  ' ',  'c',  'a',
+    'n',
+  };
+
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_CONNECTION_CLOSE frame)
+    0x1c,
+    // error code
+    0x00, 0x11,
+    // Frame type within the CONNECTION_CLOSE frame
+    kVarInt62OneByte + 0x05,
+    // error details length
+    kVarInt62OneByte + 0x0d,
+    // error details
+    'b',  'e',  'c',  'a',
+    'u',  's',  'e',  ' ',
+    'I',  ' ',  'c',  'a',
+    'n',
+  };
+  // clang-format on
+
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    p_size = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    p_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(p), p_size);
+}
+
+TEST_P(QuicFramerTest, BuildTruncatedCloseFramePacket) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicConnectionCloseFrame close_frame;
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    close_frame.ietf_error_code = PROTOCOL_VIOLATION;  // value is 0x0a
+    EXPECT_EQ(0u, close_frame.frame_type);
+  } else {
+    close_frame.error_code = static_cast<QuicErrorCode>(0x05060708);
+  }
+  close_frame.error_details = QuicString(2048, 'A');
+  QuicFrames frames = {QuicFrame(&close_frame)};
+
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (connection close frame)
+    0x02,
+    // error code
+    0x05, 0x06, 0x07, 0x08,
+    // error details length
+    0x01, 0x00,
+    // error details (truncated to 256 bytes)
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+  };
+
+  unsigned char packet44[] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (connection close frame)
+    0x02,
+    // error code
+    0x05, 0x06, 0x07, 0x08,
+    // error details length
+    0x01, 0x00,
+    // error details (truncated to 256 bytes)
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+  };
+
+  unsigned char packet46[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (connection close frame)
+    0x02,
+    // error code
+    0x05, 0x06, 0x07, 0x08,
+    // error details length
+    0x01, 0x00,
+    // error details (truncated to 256 bytes)
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+  };
+
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_CONNECTION_CLOSE frame)
+    0x1c,
+    // error code
+    0x00, 0x0a,
+    // Frame type within the CONNECTION_CLOSE frame
+    kVarInt62OneByte + 0x00,
+    // error details length
+    kVarInt62TwoBytes + 0x01, 0x00,
+    // error details (truncated to 256 bytes)
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+  };
+  // clang-format on
+
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    p_size = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    p_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(p), p_size);
+}
+
+TEST_P(QuicFramerTest, BuildApplicationCloseFramePacket) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    // Versions other than 99 do not have ApplicationClose
+    return;
+  }
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicApplicationCloseFrame app_close_frame;
+  app_close_frame.error_code = static_cast<QuicErrorCode>(0x11);
+  app_close_frame.error_details = "because I can";
+
+  QuicFrames frames = {QuicFrame(&app_close_frame)};
+
+  // clang-format off
+
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_APPLICATION_CLOSE frame)
+    0x1d,
+    // error code
+    0x00, 0x11,
+    // error details length
+    kVarInt62OneByte + 0x0d,
+    // error details
+    'b',  'e',  'c',  'a',
+    'u',  's',  'e',  ' ',
+    'I',  ' ',  'c',  'a',
+    'n',
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet99),
+                                      QUIC_ARRAYSIZE(packet99));
+}
+
+TEST_P(QuicFramerTest, BuildTruncatedApplicationCloseFramePacket) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    // Versions other than 99 do not have this frame.
+    return;
+  }
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicApplicationCloseFrame app_close_frame;
+  app_close_frame.error_code = static_cast<QuicErrorCode>(0x11);
+  app_close_frame.error_details = QuicString(2048, 'A');
+
+  QuicFrames frames = {QuicFrame(&app_close_frame)};
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_APPLICATION_CLOSE frame)
+    0x1d,
+    // error code
+    0x00, 0x11,
+    // error details length
+    kVarInt62TwoBytes + 0x01, 0x00,
+    // error details (truncated to 256 bytes)
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet99),
+                                      QUIC_ARRAYSIZE(packet99));
+}
+
+TEST_P(QuicFramerTest, BuildGoAwayPacket) {
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    // This frame type is not supported in version 99.
+    return;
+  }
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicGoAwayFrame goaway_frame;
+  goaway_frame.error_code = static_cast<QuicErrorCode>(0x05060708);
+  goaway_frame.last_good_stream_id = kStreamId;
+  goaway_frame.reason_phrase = "because I can";
+
+  QuicFrames frames = {QuicFrame(&goaway_frame)};
+
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (go away frame)
+    0x03,
+    // error code
+    0x05, 0x06, 0x07, 0x08,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // error details length
+    0x00, 0x0d,
+    // error details
+    'b',  'e',  'c',  'a',
+    'u',  's',  'e',  ' ',
+    'I',  ' ',  'c',  'a',
+    'n',
+  };
+
+  unsigned char packet44[] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (go away frame)
+    0x03,
+    // error code
+    0x05, 0x06, 0x07, 0x08,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // error details length
+    0x00, 0x0d,
+    // error details
+    'b',  'e',  'c',  'a',
+    'u',  's',  'e',  ' ',
+    'I',  ' ',  'c',  'a',
+    'n',
+  };
+
+  unsigned char packet46[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (go away frame)
+    0x03,
+    // error code
+    0x05, 0x06, 0x07, 0x08,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // error details length
+    0x00, 0x0d,
+    // error details
+    'b',  'e',  'c',  'a',
+    'u',  's',  'e',  ' ',
+    'I',  ' ',  'c',  'a',
+    'n',
+  };
+
+  // clang-format on
+
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    p_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(p), p_size);
+}
+
+TEST_P(QuicFramerTest, BuildTruncatedGoAwayPacket) {
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    // This frame type is not supported in version 99.
+    return;
+  }
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicGoAwayFrame goaway_frame;
+  goaway_frame.error_code = static_cast<QuicErrorCode>(0x05060708);
+  goaway_frame.last_good_stream_id = kStreamId;
+  goaway_frame.reason_phrase = QuicString(2048, 'A');
+
+  QuicFrames frames = {QuicFrame(&goaway_frame)};
+
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (go away frame)
+    0x03,
+    // error code
+    0x05, 0x06, 0x07, 0x08,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // error details length
+    0x01, 0x00,
+    // error details (truncated to 256 bytes)
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+  };
+
+  unsigned char packet44[] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (go away frame)
+    0x03,
+    // error code
+    0x05, 0x06, 0x07, 0x08,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // error details length
+    0x01, 0x00,
+    // error details (truncated to 256 bytes)
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+  };
+
+  unsigned char packet46[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (go away frame)
+    0x03,
+    // error code
+    0x05, 0x06, 0x07, 0x08,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // error details length
+    0x01, 0x00,
+    // error details (truncated to 256 bytes)
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+    'A',  'A',  'A',  'A',  'A',  'A',  'A',  'A',
+  };
+  // clang-format on
+
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    p_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(p), p_size);
+}
+
+TEST_P(QuicFramerTest, BuildWindowUpdatePacket) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicWindowUpdateFrame window_update_frame;
+  window_update_frame.stream_id = kStreamId;
+  window_update_frame.byte_offset = 0x1122334455667788;
+
+  QuicFrames frames = {QuicFrame(&window_update_frame)};
+
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (window update frame)
+    0x04,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // byte offset
+    0x11, 0x22, 0x33, 0x44,
+    0x55, 0x66, 0x77, 0x88,
+  };
+
+  unsigned char packet44[] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (window update frame)
+    0x04,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // byte offset
+    0x11, 0x22, 0x33, 0x44,
+    0x55, 0x66, 0x77, 0x88,
+  };
+
+  unsigned char packet46[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (window update frame)
+    0x04,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // byte offset
+    0x11, 0x22, 0x33, 0x44,
+    0x55, 0x66, 0x77, 0x88,
+  };
+
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_MAX_STREAM_DATA frame)
+    0x11,
+    // stream id
+    kVarInt62FourBytes + 0x01, 0x02, 0x03, 0x04,
+    // byte offset
+    kVarInt62EightBytes + 0x11, 0x22, 0x33, 0x44,
+    0x55, 0x66, 0x77, 0x88,
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    p_size = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    p_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(p), p_size);
+}
+
+TEST_P(QuicFramerTest, BuildMaxStreamDataPacket) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    // This frame is available only in this version.
+    return;
+  }
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicWindowUpdateFrame window_update_frame;
+  window_update_frame.stream_id = kStreamId;
+  window_update_frame.byte_offset = 0x1122334455667788;
+
+  QuicFrames frames = {QuicFrame(&window_update_frame)};
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_MAX_STREAM_DATA frame)
+    0x11,
+    // stream id
+    kVarInt62FourBytes + 0x01, 0x02, 0x03, 0x04,
+    // byte offset
+    kVarInt62EightBytes + 0x11, 0x22, 0x33, 0x44,
+    0x55, 0x66, 0x77, 0x88,
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet99),
+                                      QUIC_ARRAYSIZE(packet99));
+}
+
+TEST_P(QuicFramerTest, BuildMaxDataPacket) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    // This frame is available only in this version.
+    return;
+  }
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicWindowUpdateFrame window_update_frame;
+  window_update_frame.stream_id =
+      QuicUtils::GetInvalidStreamId(framer_.transport_version());
+  window_update_frame.byte_offset = 0x1122334455667788;
+
+  QuicFrames frames = {QuicFrame(&window_update_frame)};
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_MAX_DATA frame)
+    0x10,
+    // byte offset
+    kVarInt62EightBytes + 0x11, 0x22, 0x33, 0x44,
+    0x55, 0x66, 0x77, 0x88,
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet99),
+                                      QUIC_ARRAYSIZE(packet99));
+}
+
+TEST_P(QuicFramerTest, BuildBlockedPacket) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicBlockedFrame blocked_frame;
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    // For V99, the stream ID must be <invalid> for the frame
+    // to be a BLOCKED frame. if it's valid, it will be a
+    // STREAM_BLOCKED frame.
+    blocked_frame.stream_id =
+        QuicUtils::GetInvalidStreamId(framer_.transport_version());
+  } else {
+    blocked_frame.stream_id = kStreamId;
+  }
+  blocked_frame.offset = kStreamOffset;
+
+  QuicFrames frames = {QuicFrame(&blocked_frame)};
+
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (blocked frame)
+    0x05,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+  };
+
+  unsigned char packet44[] = {
+    // type (short packet, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (blocked frame)
+    0x05,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+  };
+
+  unsigned char packet46[] = {
+    // type (short packet, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (blocked frame)
+    0x05,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+  };
+
+  unsigned char packet99[] = {
+    // type (short packet, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_BLOCKED frame)
+    0x14,
+    // Offset
+    kVarInt62EightBytes + 0x3a, 0x98, 0xFE, 0xDC, 0x32, 0x10, 0x76, 0x54
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    p_size = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    p_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(p), p_size);
+}
+
+TEST_P(QuicFramerTest, BuildPingPacket) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicFrames frames = {QuicFrame(QuicPingFrame())};
+
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (ping frame)
+    0x07,
+  };
+
+  unsigned char packet44[] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type
+    0x07,
+  };
+
+  unsigned char packet46[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type
+    0x07,
+  };
+
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_PING frame)
+    0x01,
+  };
+  // clang-format on
+
+  unsigned char* p = packet;
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+  }
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError(
+      "constructed packet", data->data(), data->length(), AsChars(p),
+      framer_.transport_version() > QUIC_VERSION_43 ? QUIC_ARRAYSIZE(packet44)
+                                                    : QUIC_ARRAYSIZE(packet));
+}
+
+TEST_P(QuicFramerTest, BuildMessagePacket) {
+  if (framer_.transport_version() <= QUIC_VERSION_44) {
+    return;
+  }
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+  QuicMemSliceStorage storage(nullptr, 0, nullptr, 0);
+
+  QuicMessageFrame frame(1);
+  MakeSpan(&allocator_, "message", &storage).SaveMemSlicesAsMessageData(&frame);
+  QuicMessageFrame frame2(2);
+  MakeSpan(&allocator_, "message2", &storage)
+      .SaveMemSlicesAsMessageData(&frame2);
+  QuicFrames frames = {QuicFrame(&frame), QuicFrame(&frame2)};
+
+  // clang-format off
+  unsigned char packet45[] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (message frame)
+    0x21,
+    // Length
+    0x07,
+    // Message Data
+    'm', 'e', 's', 's', 'a', 'g', 'e',
+    // frame type (message frame no length)
+    0x20,
+    // Message Data
+    'm', 'e', 's', 's', 'a', 'g', 'e', '2'
+  };
+
+  unsigned char packet46[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (message frame)
+    0x21,
+    // Length
+    0x07,
+    // Message Data
+    'm', 'e', 's', 's', 'a', 'g', 'e',
+    // frame type (message frame no length)
+    0x20,
+    // Message Data
+    'm', 'e', 's', 's', 'a', 'g', 'e', '2'
+  };
+
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_MESSAGE frame)
+    0x21,
+    // Length
+    0x07,
+    // Message Data
+    'm', 'e', 's', 's', 'a', 'g', 'e',
+    // frame type (message frame no length)
+    0x20,
+    // Message Data
+    'm', 'e', 's', 's', 'a', 'g', 'e', '2'
+  };
+  // clang-format on
+
+  unsigned char* p = packet45;
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+  }
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(p),
+                                      QUIC_ARRAYSIZE(packet45));
+}
+
+// Test that the connectivity probing packet is serialized correctly as a
+// padded PING packet.
+TEST_P(QuicFramerTest, BuildConnectivityProbingPacket) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (ping frame)
+    0x07,
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  unsigned char packet44[] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type
+    0x07,
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  unsigned char packet46[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type
+    0x07,
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_PING frame)
+    0x01,
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+  // clang-format on
+
+  unsigned char* p = packet;
+  size_t packet_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    packet_size = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    packet_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    packet_size = QUIC_ARRAYSIZE(packet44);
+  }
+
+  std::unique_ptr<char[]> buffer(new char[kMaxPacketSize]);
+
+  size_t length = framer_.BuildConnectivityProbingPacket(
+      header, buffer.get(), packet_size, ENCRYPTION_NONE);
+
+  EXPECT_NE(0u, length);
+  QuicPacket data(framer_.transport_version(), buffer.release(), length, true,
+                  header);
+
+  test::CompareCharArraysWithHexError("constructed packet", data.data(),
+                                      data.length(), AsChars(p), packet_size);
+}
+
+// Test that the path challenge connectivity probing packet is serialized
+// correctly as a padded PATH CHALLENGE packet.
+TEST_P(QuicFramerTest, BuildPaddedPathChallengePacket) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+  QuicPathFrameBuffer payload;
+
+  // clang-format off
+  unsigned char packet[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // Path Challenge Frame type (IETF_PATH_CHALLENGE)
+    0x1a,
+    // 8 "random" bytes, MockRandom makes lots of r's
+    'r', 'r', 'r', 'r', 'r', 'r', 'r', 'r',
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+  // clang-format on
+
+  std::unique_ptr<char[]> buffer(new char[kMaxPacketSize]);
+  MockRandom randomizer;
+
+  size_t length = framer_.BuildPaddedPathChallengePacket(
+      header, buffer.get(), QUIC_ARRAYSIZE(packet), &payload, &randomizer,
+      ENCRYPTION_NONE);
+  EXPECT_EQ(length, QUIC_ARRAYSIZE(packet));
+
+  // Payload has the random bytes that were generated. Copy them into packet,
+  // above, before checking that the generated packet is correct.
+  EXPECT_EQ(kQuicPathFrameBufferSize, payload.size());
+
+  QuicPacket data(framer_.transport_version(), buffer.release(), length, true,
+                  header);
+
+  test::CompareCharArraysWithHexError("constructed packet", data.data(),
+                                      data.length(), AsChars(packet),
+                                      QUIC_ARRAYSIZE(packet));
+}
+
+// Several tests that the path response connectivity probing packet is
+// serialized correctly as either a padded and unpadded PATH RESPONSE
+// packet. Also generates packets with 1 and 3 PATH_RESPONSES in them to
+// exercised the single- and multiple- payload cases.
+TEST_P(QuicFramerTest, BuildPathResponsePacket1ResponseUnpadded) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+  QuicPathFrameBuffer payload0 = {
+      {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}};
+
+  // Build 1 PATH RESPONSE, not padded
+  // clang-format off
+  unsigned char packet[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // Path Response Frame type (IETF_PATH_RESPONSE)
+    0x1b,
+    // 8 "random" bytes
+    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+  };
+  // clang-format on
+  std::unique_ptr<char[]> buffer(new char[kMaxPacketSize]);
+  QuicDeque<QuicPathFrameBuffer> payloads;
+  payloads.push_back(payload0);
+  size_t length = framer_.BuildPathResponsePacket(
+      header, buffer.get(), QUIC_ARRAYSIZE(packet), payloads,
+      /*is_padded=*/false, ENCRYPTION_NONE);
+  EXPECT_EQ(length, QUIC_ARRAYSIZE(packet));
+  QuicPacket data(framer_.transport_version(), buffer.release(), length, true,
+                  header);
+
+  test::CompareCharArraysWithHexError("constructed packet", data.data(),
+                                      data.length(), AsChars(packet),
+                                      QUIC_ARRAYSIZE(packet));
+}
+
+TEST_P(QuicFramerTest, BuildPathResponsePacket1ResponsePadded) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+  QuicPathFrameBuffer payload0 = {
+      {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}};
+
+  // Build 1 PATH RESPONSE, padded
+  // clang-format off
+  unsigned char packet[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // Path Response Frame type (IETF_PATH_RESPONSE)
+    0x1b,
+    // 8 "random" bytes
+    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+    // Padding type and pad
+    0x00, 0x00, 0x00, 0x00, 0x00
+  };
+  // clang-format on
+  std::unique_ptr<char[]> buffer(new char[kMaxPacketSize]);
+  QuicDeque<QuicPathFrameBuffer> payloads;
+  payloads.push_back(payload0);
+  size_t length = framer_.BuildPathResponsePacket(
+      header, buffer.get(), QUIC_ARRAYSIZE(packet), payloads,
+      /*is_padded=*/true, ENCRYPTION_NONE);
+  EXPECT_EQ(length, QUIC_ARRAYSIZE(packet));
+  QuicPacket data(framer_.transport_version(), buffer.release(), length, true,
+                  header);
+
+  test::CompareCharArraysWithHexError("constructed packet", data.data(),
+                                      data.length(), AsChars(packet),
+                                      QUIC_ARRAYSIZE(packet));
+}
+
+TEST_P(QuicFramerTest, BuildPathResponsePacket3ResponsesUnpadded) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+  QuicPathFrameBuffer payload0 = {
+      {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}};
+  QuicPathFrameBuffer payload1 = {
+      {0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}};
+  QuicPathFrameBuffer payload2 = {
+      {0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28}};
+
+  // Build one packet with 3 PATH RESPONSES, no padding
+  // clang-format off
+  unsigned char packet[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // 3 path response frames (IETF_PATH_RESPONSE type byte and payload)
+    0x1b, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+    0x1b, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+    0x1b, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
+  };
+  // clang-format on
+
+  std::unique_ptr<char[]> buffer(new char[kMaxPacketSize]);
+  QuicDeque<QuicPathFrameBuffer> payloads;
+  payloads.push_back(payload0);
+  payloads.push_back(payload1);
+  payloads.push_back(payload2);
+  size_t length = framer_.BuildPathResponsePacket(
+      header, buffer.get(), QUIC_ARRAYSIZE(packet), payloads,
+      /*is_padded=*/false, ENCRYPTION_NONE);
+  EXPECT_EQ(length, QUIC_ARRAYSIZE(packet));
+  QuicPacket data(framer_.transport_version(), buffer.release(), length, true,
+                  header);
+
+  test::CompareCharArraysWithHexError("constructed packet", data.data(),
+                                      data.length(), AsChars(packet),
+                                      QUIC_ARRAYSIZE(packet));
+}
+
+TEST_P(QuicFramerTest, BuildPathResponsePacket3ResponsesPadded) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+  QuicPathFrameBuffer payload0 = {
+      {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}};
+  QuicPathFrameBuffer payload1 = {
+      {0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}};
+  QuicPathFrameBuffer payload2 = {
+      {0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28}};
+
+  // Build one packet with 3 PATH RESPONSES, with padding
+  // clang-format off
+  unsigned char packet[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // 3 path response frames (IETF_PATH_RESPONSE byte and payload)
+    0x1b, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+    0x1b, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+    0x1b, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
+    // Padding
+    0x00, 0x00, 0x00, 0x00, 0x00
+  };
+  // clang-format on
+
+  std::unique_ptr<char[]> buffer(new char[kMaxPacketSize]);
+  QuicDeque<QuicPathFrameBuffer> payloads;
+  payloads.push_back(payload0);
+  payloads.push_back(payload1);
+  payloads.push_back(payload2);
+  size_t length = framer_.BuildPathResponsePacket(
+      header, buffer.get(), QUIC_ARRAYSIZE(packet), payloads,
+      /*is_padded=*/true, ENCRYPTION_NONE);
+  EXPECT_EQ(length, QUIC_ARRAYSIZE(packet));
+  QuicPacket data(framer_.transport_version(), buffer.release(), length, true,
+                  header);
+
+  test::CompareCharArraysWithHexError("constructed packet", data.data(),
+                                      data.length(), AsChars(packet),
+                                      QUIC_ARRAYSIZE(packet));
+}
+
+// Test that the MTU discovery packet is serialized correctly as a PING packet.
+TEST_P(QuicFramerTest, BuildMtuDiscoveryPacket) {
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicFrames frames = {QuicFrame(QuicMtuDiscoveryFrame())};
+
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (ping frame)
+    0x07,
+  };
+
+  unsigned char packet44[] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type
+    0x07,
+  };
+
+  unsigned char packet46[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type
+    0x07,
+  };
+
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_PING frame)
+    0x01,
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  unsigned char* p = packet;
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+  }
+
+  test::CompareCharArraysWithHexError(
+      "constructed packet", data->data(), data->length(), AsChars(p),
+      framer_.transport_version() > QUIC_VERSION_43 ? QUIC_ARRAYSIZE(packet44)
+                                                    : QUIC_ARRAYSIZE(packet));
+}
+
+TEST_P(QuicFramerTest, BuildPublicResetPacket) {
+  QuicPublicResetPacket reset_packet;
+  reset_packet.connection_id = FramerTestConnectionId();
+  reset_packet.nonce_proof = kNonceProof;
+
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (public reset, 8 byte ConnectionId)
+    0x0E,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // message tag (kPRST)
+    'P', 'R', 'S', 'T',
+    // num_entries (1) + padding
+    0x01, 0x00, 0x00, 0x00,
+    // tag kRNON
+    'R', 'N', 'O', 'N',
+    // end offset 8
+    0x08, 0x00, 0x00, 0x00,
+    // nonce proof
+    0x89, 0x67, 0x45, 0x23,
+    0x01, 0xEF, 0xCD, 0xAB,
+  };
+  // clang-format on
+
+  if (framer_.transport_version() > QUIC_VERSION_43) {
+    return;
+  }
+
+  std::unique_ptr<QuicEncryptedPacket> data(
+      framer_.BuildPublicResetPacket(reset_packet));
+  ASSERT_TRUE(data != nullptr);
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet),
+                                      QUIC_ARRAYSIZE(packet));
+}
+
+TEST_P(QuicFramerTest, BuildPublicResetPacketWithClientAddress) {
+  QuicPublicResetPacket reset_packet;
+  reset_packet.connection_id = FramerTestConnectionId();
+  reset_packet.nonce_proof = kNonceProof;
+  reset_packet.client_address =
+      QuicSocketAddress(QuicIpAddress::Loopback4(), 0x1234);
+
+  // clang-format off
+  unsigned char packet[] = {
+      // public flags (public reset, 8 byte ConnectionId)
+      0x0E,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98,
+      0x76, 0x54, 0x32, 0x10,
+      // message tag (kPRST)
+      'P', 'R', 'S', 'T',
+      // num_entries (2) + padding
+      0x02, 0x00, 0x00, 0x00,
+      // tag kRNON
+      'R', 'N', 'O', 'N',
+      // end offset 8
+      0x08, 0x00, 0x00, 0x00,
+      // tag kCADR
+      'C', 'A', 'D', 'R',
+      // end offset 16
+      0x10, 0x00, 0x00, 0x00,
+      // nonce proof
+      0x89, 0x67, 0x45, 0x23,
+      0x01, 0xEF, 0xCD, 0xAB,
+      // client address
+      0x02, 0x00,
+      0x7F, 0x00, 0x00, 0x01,
+      0x34, 0x12,
+  };
+  // clang-format on
+
+  if (framer_.transport_version() > QUIC_VERSION_43) {
+    return;
+  }
+
+  std::unique_ptr<QuicEncryptedPacket> data(
+      framer_.BuildPublicResetPacket(reset_packet));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet),
+                                      QUIC_ARRAYSIZE(packet));
+}
+
+TEST_P(QuicFramerTest, BuildPublicResetPacketWithEndpointId) {
+  QuicPublicResetPacket reset_packet;
+  reset_packet.connection_id = FramerTestConnectionId();
+  reset_packet.nonce_proof = kNonceProof;
+  reset_packet.endpoint_id = "FakeServerId";
+
+  // The tag value map in CryptoHandshakeMessage is a std::map, so the two tags
+  // in the packet, kRNON and kEPID, have unspecified ordering w.r.t each other.
+  // clang-format off
+  unsigned char packet_variant1[] = {
+      // public flags (public reset, 8 byte ConnectionId)
+      0x0E,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98,
+      0x76, 0x54, 0x32, 0x10,
+      // message tag (kPRST)
+      'P', 'R', 'S', 'T',
+      // num_entries (2) + padding
+      0x02, 0x00, 0x00, 0x00,
+      // tag kRNON
+      'R', 'N', 'O', 'N',
+      // end offset 8
+      0x08, 0x00, 0x00, 0x00,
+      // tag kEPID
+      'E', 'P', 'I', 'D',
+      // end offset 20
+      0x14, 0x00, 0x00, 0x00,
+      // nonce proof
+      0x89, 0x67, 0x45, 0x23,
+      0x01, 0xEF, 0xCD, 0xAB,
+      // Endpoint ID
+      'F', 'a', 'k', 'e', 'S', 'e', 'r', 'v', 'e', 'r', 'I', 'd',
+  };
+  unsigned char packet_variant2[] = {
+      // public flags (public reset, 8 byte ConnectionId)
+      0x0E,
+      // connection_id
+      0xFE, 0xDC, 0xBA, 0x98,
+      0x76, 0x54, 0x32, 0x10,
+      // message tag (kPRST)
+      'P', 'R', 'S', 'T',
+      // num_entries (2) + padding
+      0x02, 0x00, 0x00, 0x00,
+      // tag kEPID
+      'E', 'P', 'I', 'D',
+      // end offset 12
+      0x0C, 0x00, 0x00, 0x00,
+      // tag kRNON
+      'R', 'N', 'O', 'N',
+      // end offset 20
+      0x14, 0x00, 0x00, 0x00,
+      // Endpoint ID
+      'F', 'a', 'k', 'e', 'S', 'e', 'r', 'v', 'e', 'r', 'I', 'd',
+      // nonce proof
+      0x89, 0x67, 0x45, 0x23,
+      0x01, 0xEF, 0xCD, 0xAB,
+  };
+  // clang-format on
+
+  if (framer_.transport_version() > QUIC_VERSION_43) {
+    return;
+  }
+
+  std::unique_ptr<QuicEncryptedPacket> data(
+      framer_.BuildPublicResetPacket(reset_packet));
+  ASSERT_TRUE(data != nullptr);
+
+  // Variant 1 ends with char 'd'. Variant 1 ends with char 0xAB.
+  if ('d' == data->data()[data->length() - 1]) {
+    test::CompareCharArraysWithHexError(
+        "constructed packet", data->data(), data->length(),
+        AsChars(packet_variant1), QUIC_ARRAYSIZE(packet_variant1));
+  } else {
+    test::CompareCharArraysWithHexError(
+        "constructed packet", data->data(), data->length(),
+        AsChars(packet_variant2), QUIC_ARRAYSIZE(packet_variant2));
+  }
+}
+
+TEST_P(QuicFramerTest, BuildIetfStatelessResetPacket) {
+  // clang-format off
+  unsigned char packet44[] = {
+    // type (short header, 1 byte packet number)
+    0x70,
+    // random packet number
+    0xFE,
+    // stateless reset token
+    0xB5, 0x69, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> data(
+      framer_.BuildIetfStatelessResetPacket(FramerTestConnectionId(),
+                                            kTestStatelessResetToken));
+  ASSERT_TRUE(data != nullptr);
+  // Skip packet number byte which is random in stateless reset packet.
+  test::CompareCharArraysWithHexError("constructed packet", data->data(), 1,
+                                      AsChars(packet44), 1);
+  const size_t random_bytes_length =
+      data->length() - kPacketHeaderTypeSize - sizeof(kTestStatelessResetToken);
+  EXPECT_EQ(kMinRandomBytesLengthInStatelessReset, random_bytes_length);
+  // Verify stateless reset token is correct.
+  test::CompareCharArraysWithHexError(
+      "constructed packet",
+      data->data() + data->length() - sizeof(kTestStatelessResetToken),
+      sizeof(kTestStatelessResetToken),
+      AsChars(packet44) + QUIC_ARRAYSIZE(packet44) -
+          sizeof(kTestStatelessResetToken),
+      sizeof(kTestStatelessResetToken));
+}
+
+TEST_P(QuicFramerTest, EncryptPacket) {
+  QuicPacketNumber packet_number = kPacketNumber;
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // redundancy
+    'a',  'b',  'c',  'd',
+    'e',  'f',  'g',  'h',
+    'i',  'j',  'k',  'l',
+    'm',  'n',  'o',  'p',
+  };
+
+  unsigned char packet44[] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // redundancy
+    'a',  'b',  'c',  'd',
+    'e',  'f',  'g',  'h',
+    'i',  'j',  'k',  'l',
+    'm',  'n',  'o',  'p',
+  };
+
+  unsigned char packet46[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // redundancy
+    'a',  'b',  'c',  'd',
+    'e',  'f',  'g',  'h',
+    'i',  'j',  'k',  'l',
+    'm',  'n',  'o',  'p',
+  };
+
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // redundancy
+    'a',  'b',  'c',  'd',
+    'e',  'f',  'g',  'h',
+    'i',  'j',  'k',  'l',
+    'm',  'n',  'o',  'p',
+  };
+  // clang-format on
+
+  unsigned char* p = packet;
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+  }
+
+  std::unique_ptr<QuicPacket> raw(new QuicPacket(
+      AsChars(p), QUIC_ARRAYSIZE(packet), false, PACKET_8BYTE_CONNECTION_ID,
+      PACKET_0BYTE_CONNECTION_ID, !kIncludeVersion,
+      !kIncludeDiversificationNonce, PACKET_4BYTE_PACKET_NUMBER,
+      VARIABLE_LENGTH_INTEGER_LENGTH_0, 0, VARIABLE_LENGTH_INTEGER_LENGTH_0));
+  char buffer[kMaxPacketSize];
+  size_t encrypted_length = framer_.EncryptPayload(
+      ENCRYPTION_NONE, packet_number, *raw, buffer, kMaxPacketSize);
+
+  ASSERT_NE(0u, encrypted_length);
+  EXPECT_TRUE(CheckEncryption(packet_number, raw.get()));
+}
+
+TEST_P(QuicFramerTest, EncryptPacketWithVersionFlag) {
+  QuicPacketNumber packet_number = kPacketNumber;
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (version, 8 byte connection_id)
+    0x29,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // version tag
+    'Q', '.', '1', '0',
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // redundancy
+    'a',  'b',  'c',  'd',
+    'e',  'f',  'g',  'h',
+    'i',  'j',  'k',  'l',
+    'm',  'n',  'o',  'p',
+  };
+
+  unsigned char packet44[] = {
+    // type (long header with packet type ZERO_RTT_PROTECTED)
+    0xFC,
+    // version tag
+    'Q', '.', '1', '0',
+    // connection_id length
+    0x50,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // redundancy
+    'a',  'b',  'c',  'd',
+    'e',  'f',  'g',  'h',
+    'i',  'j',  'k',  'l',
+    'm',  'n',  'o',  'p',
+  };
+
+  unsigned char packet46[] = {
+    // type (long header with packet type ZERO_RTT_PROTECTED)
+    0xD3,
+    // version tag
+    'Q', '.', '1', '0',
+    // connection_id length
+    0x50,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // redundancy
+    'a',  'b',  'c',  'd',
+    'e',  'f',  'g',  'h',
+    'i',  'j',  'k',  'l',
+    'm',  'n',  'o',  'p',
+  };
+
+  unsigned char packet99[] = {
+    // type (long header with packet type ZERO_RTT_PROTECTED)
+    0xD3,
+    // version tag
+    'Q', '.', '1', '0',
+    // connection_id length
+    0x50,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // redundancy
+    'a',  'b',  'c',  'd',
+    'e',  'f',  'g',  'h',
+    'i',  'j',  'k',  'l',
+    'm',  'n',  'o',  'p',
+  };
+  // clang-format on
+
+  unsigned char* p = packet;
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+  }
+
+  std::unique_ptr<QuicPacket> raw(new QuicPacket(
+      AsChars(p),
+      framer_.transport_version() > QUIC_VERSION_43 ? QUIC_ARRAYSIZE(packet44)
+                                                    : QUIC_ARRAYSIZE(packet),
+      false, PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID,
+      kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_4BYTE_PACKET_NUMBER, VARIABLE_LENGTH_INTEGER_LENGTH_0, 0,
+      VARIABLE_LENGTH_INTEGER_LENGTH_0));
+  char buffer[kMaxPacketSize];
+  size_t encrypted_length = framer_.EncryptPayload(
+      ENCRYPTION_NONE, packet_number, *raw, buffer, kMaxPacketSize);
+
+  ASSERT_NE(0u, encrypted_length);
+  EXPECT_TRUE(CheckEncryption(packet_number, raw.get()));
+}
+
+TEST_P(QuicFramerTest, AckTruncationLargePacket) {
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    // This test is not applicable to this version; the range count is
+    // effectively unlimited
+    return;
+  }
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicAckFrame ack_frame;
+  // Create a packet with just the ack.
+  ack_frame = MakeAckFrameWithAckBlocks(300, 0u);
+  QuicFrames frames = {QuicFrame(&ack_frame)};
+
+  // Build an ack packet with truncation due to limit in number of nack ranges.
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  std::unique_ptr<QuicPacket> raw_ack_packet(BuildDataPacket(header, frames));
+  ASSERT_TRUE(raw_ack_packet != nullptr);
+  char buffer[kMaxPacketSize];
+  size_t encrypted_length =
+      framer_.EncryptPayload(ENCRYPTION_NONE, header.packet_number,
+                             *raw_ack_packet, buffer, kMaxPacketSize);
+  ASSERT_NE(0u, encrypted_length);
+  // Now make sure we can turn our ack packet back into an ack frame.
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_SERVER);
+  ASSERT_TRUE(framer_.ProcessPacket(
+      QuicEncryptedPacket(buffer, encrypted_length, false)));
+  ASSERT_EQ(1u, visitor_.ack_frames_.size());
+  QuicAckFrame& processed_ack_frame = *visitor_.ack_frames_[0];
+  EXPECT_EQ(QuicPacketNumber(600u), LargestAcked(processed_ack_frame));
+  ASSERT_EQ(256u, processed_ack_frame.packets.NumPacketsSlow());
+  EXPECT_EQ(QuicPacketNumber(90u), processed_ack_frame.packets.Min());
+  EXPECT_EQ(QuicPacketNumber(600u), processed_ack_frame.packets.Max());
+}
+
+TEST_P(QuicFramerTest, AckTruncationSmallPacket) {
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    // This test is not applicable to this version; the range count is
+    // effectively unlimited
+    return;
+  }
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  // Create a packet with just the ack.
+  QuicAckFrame ack_frame;
+  ack_frame = MakeAckFrameWithAckBlocks(300, 0u);
+  QuicFrames frames = {QuicFrame(&ack_frame)};
+
+  // Build an ack packet with truncation due to limit in number of nack ranges.
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  std::unique_ptr<QuicPacket> raw_ack_packet(
+      BuildDataPacket(header, frames, 500));
+  ASSERT_TRUE(raw_ack_packet != nullptr);
+  char buffer[kMaxPacketSize];
+  size_t encrypted_length =
+      framer_.EncryptPayload(ENCRYPTION_NONE, header.packet_number,
+                             *raw_ack_packet, buffer, kMaxPacketSize);
+  ASSERT_NE(0u, encrypted_length);
+  // Now make sure we can turn our ack packet back into an ack frame.
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_SERVER);
+  ASSERT_TRUE(framer_.ProcessPacket(
+      QuicEncryptedPacket(buffer, encrypted_length, false)));
+  ASSERT_EQ(1u, visitor_.ack_frames_.size());
+  QuicAckFrame& processed_ack_frame = *visitor_.ack_frames_[0];
+  EXPECT_EQ(QuicPacketNumber(600u), LargestAcked(processed_ack_frame));
+  ASSERT_EQ(240u, processed_ack_frame.packets.NumPacketsSlow());
+  EXPECT_EQ(QuicPacketNumber(122u), processed_ack_frame.packets.Min());
+  EXPECT_EQ(QuicPacketNumber(600u), processed_ack_frame.packets.Max());
+}
+
+TEST_P(QuicFramerTest, CleanTruncation) {
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    // This test is not applicable to this version; the range count is
+    // effectively unlimited
+    return;
+  }
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicAckFrame ack_frame = InitAckFrame(201);
+
+  // Create a packet with just the ack.
+  QuicFrames frames = {QuicFrame(&ack_frame)};
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  std::unique_ptr<QuicPacket> raw_ack_packet(BuildDataPacket(header, frames));
+  ASSERT_TRUE(raw_ack_packet != nullptr);
+
+  char buffer[kMaxPacketSize];
+  size_t encrypted_length =
+      framer_.EncryptPayload(ENCRYPTION_NONE, header.packet_number,
+                             *raw_ack_packet, buffer, kMaxPacketSize);
+  ASSERT_NE(0u, encrypted_length);
+
+  // Now make sure we can turn our ack packet back into an ack frame.
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_SERVER);
+  ASSERT_TRUE(framer_.ProcessPacket(
+      QuicEncryptedPacket(buffer, encrypted_length, false)));
+
+  // Test for clean truncation of the ack by comparing the length of the
+  // original packets to the re-serialized packets.
+  frames.clear();
+  frames.push_back(QuicFrame(visitor_.ack_frames_[0].get()));
+
+  size_t original_raw_length = raw_ack_packet->length();
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  raw_ack_packet = BuildDataPacket(header, frames);
+  ASSERT_TRUE(raw_ack_packet != nullptr);
+  EXPECT_EQ(original_raw_length, raw_ack_packet->length());
+  ASSERT_TRUE(raw_ack_packet != nullptr);
+}
+
+TEST_P(QuicFramerTest, StopPacketProcessing) {
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (stream frame with fin)
+    0xFF,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // offset
+    0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // data length
+    0x00, 0x0c,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+
+    // frame type (ack frame)
+    0x40,
+    // least packet number awaiting an ack
+    0x12, 0x34, 0x56, 0x78,
+    0x9A, 0xA0,
+    // largest observed packet number
+    0x12, 0x34, 0x56, 0x78,
+    0x9A, 0xBF,
+    // num missing packets
+    0x01,
+    // missing packet
+    0x12, 0x34, 0x56, 0x78,
+    0x9A, 0xBE,
+  };
+
+  unsigned char packet44[] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (stream frame with fin)
+    0xFF,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // offset
+    0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // data length
+    0x00, 0x0c,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+
+    // frame type (ack frame)
+    0x40,
+    // least packet number awaiting an ack
+    0x12, 0x34, 0x56, 0x78,
+    0x9A, 0xA0,
+    // largest observed packet number
+    0x12, 0x34, 0x56, 0x78,
+    0x9A, 0xBF,
+    // num missing packets
+    0x01,
+    // missing packet
+    0x12, 0x34, 0x56, 0x78,
+    0x9A, 0xBE,
+  };
+
+  unsigned char packet46[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (stream frame with fin)
+    0xFF,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // offset
+    0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // data length
+    0x00, 0x0c,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+
+    // frame type (ack frame)
+    0x40,
+    // least packet number awaiting an ack
+    0x12, 0x34, 0x56, 0x78,
+    0x9A, 0xA0,
+    // largest observed packet number
+    0x12, 0x34, 0x56, 0x78,
+    0x9A, 0xBF,
+    // num missing packets
+    0x01,
+    // missing packet
+    0x12, 0x34, 0x56, 0x78,
+    0x9A, 0xBE,
+  };
+
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_STREAM frame with fin, length, and offset bits set)
+    0x08 | 0x01 | 0x02 | 0x04,
+    // stream id
+    kVarInt62FourBytes + 0x01, 0x02, 0x03, 0x04,
+    // offset
+    kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // data length
+    kVarInt62TwoBytes + 0x00, 0x0c,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+
+    // frame type (ack frame)
+    0x0d,
+    // largest observed packet number
+    kVarInt62FourBytes + 0x12, 0x34, 0x56, 0x78,
+    // Delta time
+    kVarInt62OneByte + 0x00,
+    // Ack Block count
+    kVarInt62OneByte + 0x01,
+    // First block size (one packet)
+    kVarInt62OneByte + 0x00,
+
+    // Next gap size & ack. Missing all preceding packets
+    kVarInt62FourBytes + 0x12, 0x34, 0x56, 0x77,
+    kVarInt62OneByte + 0x00,
+  };
+  // clang-format on
+
+  MockFramerVisitor visitor;
+  framer_.set_visitor(&visitor);
+  EXPECT_CALL(visitor, OnPacket());
+  EXPECT_CALL(visitor, OnPacketHeader(_));
+  EXPECT_CALL(visitor, OnStreamFrame(_)).WillOnce(Return(false));
+  EXPECT_CALL(visitor, OnPacketComplete());
+  EXPECT_CALL(visitor, OnUnauthenticatedPublicHeader(_)).WillOnce(Return(true));
+  EXPECT_CALL(visitor, OnUnauthenticatedHeader(_)).WillOnce(Return(true));
+  EXPECT_CALL(visitor, OnDecryptedPacket(_));
+
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    p_size = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    p_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+  QuicEncryptedPacket encrypted(AsChars(p), p_size, false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+}
+
+static char kTestString[] = "At least 20 characters.";
+static QuicStreamId kTestQuicStreamId = 1;
+static bool ExpectedStreamFrame(const QuicStreamFrame& frame) {
+  return (frame.stream_id == kTestQuicStreamId ||
+          frame.stream_id == QuicUtils::GetCryptoStreamId(QUIC_VERSION_99)) &&
+         !frame.fin && frame.offset == 0 &&
+         QuicString(frame.data_buffer, frame.data_length) == kTestString;
+  // FIN is hard-coded false in ConstructEncryptedPacket.
+  // Offset 0 is hard-coded in ConstructEncryptedPacket.
+}
+
+// Verify that the packet returned by ConstructEncryptedPacket() can be properly
+// parsed by the framer.
+TEST_P(QuicFramerTest, ConstructEncryptedPacket) {
+  // Since we are using ConstructEncryptedPacket, we have to set the framer's
+  // crypto to be Null.
+  framer_.SetDecrypter(ENCRYPTION_NONE,
+                       QuicMakeUnique<NullDecrypter>(framer_.perspective()));
+  framer_.SetEncrypter(ENCRYPTION_NONE,
+                       QuicMakeUnique<NullEncrypter>(framer_.perspective()));
+  ParsedQuicVersionVector versions;
+  versions.push_back(framer_.version());
+  std::unique_ptr<QuicEncryptedPacket> packet(ConstructEncryptedPacket(
+      TestConnectionId(), EmptyQuicConnectionId(), false, false,
+      kTestQuicStreamId, kTestString, CONNECTION_ID_PRESENT,
+      CONNECTION_ID_ABSENT, PACKET_4BYTE_PACKET_NUMBER, &versions));
+
+  MockFramerVisitor visitor;
+  framer_.set_visitor(&visitor);
+  EXPECT_CALL(visitor, OnPacket()).Times(1);
+  EXPECT_CALL(visitor, OnUnauthenticatedPublicHeader(_))
+      .Times(1)
+      .WillOnce(Return(true));
+  EXPECT_CALL(visitor, OnUnauthenticatedHeader(_))
+      .Times(1)
+      .WillOnce(Return(true));
+  EXPECT_CALL(visitor, OnPacketHeader(_)).Times(1).WillOnce(Return(true));
+  EXPECT_CALL(visitor, OnDecryptedPacket(_)).Times(1);
+  EXPECT_CALL(visitor, OnError(_)).Times(0);
+  EXPECT_CALL(visitor, OnStreamFrame(_)).Times(0);
+  if (framer_.version().transport_version < QUIC_VERSION_47) {
+    EXPECT_CALL(visitor, OnStreamFrame(Truly(ExpectedStreamFrame))).Times(1);
+  } else {
+    EXPECT_CALL(visitor, OnCryptoFrame(_)).Times(1);
+  }
+  EXPECT_CALL(visitor, OnPacketComplete()).Times(1);
+
+  EXPECT_TRUE(framer_.ProcessPacket(*packet));
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+}
+
+// Verify that the packet returned by ConstructMisFramedEncryptedPacket()
+// does cause the framer to return an error.
+TEST_P(QuicFramerTest, ConstructMisFramedEncryptedPacket) {
+  // Since we are using ConstructEncryptedPacket, we have to set the framer's
+  // crypto to be Null.
+  framer_.SetDecrypter(ENCRYPTION_NONE,
+                       QuicMakeUnique<NullDecrypter>(framer_.perspective()));
+  framer_.SetEncrypter(ENCRYPTION_NONE,
+                       QuicMakeUnique<NullEncrypter>(framer_.perspective()));
+  ParsedQuicVersionVector versions;
+  versions.push_back(framer_.version());
+  std::unique_ptr<QuicEncryptedPacket> packet(ConstructMisFramedEncryptedPacket(
+      TestConnectionId(), EmptyQuicConnectionId(), false, false,
+      kTestQuicStreamId, kTestString, CONNECTION_ID_PRESENT,
+      CONNECTION_ID_ABSENT, PACKET_4BYTE_PACKET_NUMBER, &versions,
+      Perspective::IS_CLIENT));
+
+  MockFramerVisitor visitor;
+  framer_.set_visitor(&visitor);
+  EXPECT_CALL(visitor, OnPacket()).Times(1);
+  EXPECT_CALL(visitor, OnUnauthenticatedPublicHeader(_))
+      .Times(1)
+      .WillOnce(Return(true));
+  EXPECT_CALL(visitor, OnUnauthenticatedHeader(_))
+      .Times(1)
+      .WillOnce(Return(true));
+  EXPECT_CALL(visitor, OnPacketHeader(_)).Times(1);
+  EXPECT_CALL(visitor, OnDecryptedPacket(_)).Times(1);
+  EXPECT_CALL(visitor, OnError(_)).Times(1);
+  EXPECT_CALL(visitor, OnStreamFrame(_)).Times(0);
+  EXPECT_CALL(visitor, OnPacketComplete()).Times(0);
+
+  EXPECT_FALSE(framer_.ProcessPacket(*packet));
+  EXPECT_EQ(QUIC_INVALID_FRAME_DATA, framer_.error());
+}
+
+// Tests for fuzzing with Dr. Fuzz
+// Xref http://www.chromium.org/developers/testing/dr-fuzz for more details.
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// target function to be fuzzed by Dr. Fuzz
+void QuicFramerFuzzFunc(unsigned char* data,
+                        size_t size,
+                        const ParsedQuicVersion& version) {
+  QuicFramer framer(AllSupportedVersions(), QuicTime::Zero(),
+                    Perspective::IS_SERVER, kQuicDefaultConnectionIdLength);
+  ASSERT_EQ(GetQuicFlag(FLAGS_quic_supports_tls_handshake), true);
+  const char* const packet_bytes = reinterpret_cast<const char*>(data);
+
+  // Test the CryptoFramer.
+  QuicStringPiece crypto_input(packet_bytes, size);
+  std::unique_ptr<CryptoHandshakeMessage> handshake_message(
+      CryptoFramer::ParseMessage(crypto_input));
+
+  // Test the regular QuicFramer with the same input.
+  NoOpFramerVisitor visitor;
+  framer.set_visitor(&visitor);
+  framer.set_version(version);
+  QuicEncryptedPacket packet(packet_bytes, size);
+  framer.ProcessPacket(packet);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+TEST_P(QuicFramerTest, FramerFuzzTest) {
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x2C,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+    // private flags
+    0x00,
+
+    // frame type (stream frame with fin)
+    0xFF,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // offset
+    0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // data length
+    0x00, 0x0c,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+
+  unsigned char packet44[] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (stream frame with fin, length, and offset bits set)
+    0x10 | 0x01 | 0x02 | 0x04,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // offset
+    0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // data length
+    0x00, 0x0c,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+
+  unsigned char packet46[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (stream frame with fin, length, and offset bits set)
+    0x10 | 0x01 | 0x02 | 0x04,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // offset
+    0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // data length
+    0x00, 0x0c,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_STREAM frame with fin, length, and offset bits set)
+    0x08 | 0x01 | 0x02 | 0x04,
+    // stream id
+    0x01, 0x02, 0x03, 0x04,
+    // offset
+    0x3A, 0x98, 0xFE, 0xDC,
+    0x32, 0x10, 0x76, 0x54,
+    // data length
+    0x00, 0x0c,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+  // clang-format on
+
+  unsigned char* p = packet;
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+  }
+  QuicFramerFuzzFunc(p,
+                     framer_.transport_version() > QUIC_VERSION_43
+                         ? QUIC_ARRAYSIZE(packet44)
+                         : QUIC_ARRAYSIZE(packet),
+                     framer_.version());
+}
+
+TEST_P(QuicFramerTest, StartsWithChlo) {
+  SimpleDataProducer producer;
+  framer_.set_data_producer(&producer);
+  QuicStringPiece data("CHLOCHLO");
+  struct iovec iovec;
+  iovec.iov_base = const_cast<char*>(data.data());
+  iovec.iov_len = data.length();
+  producer.SaveStreamData(
+      QuicUtils::GetCryptoStreamId(framer_.transport_version()), &iovec, 1, 0,
+      data.length());
+  for (size_t offset = 0; offset < 5; ++offset) {
+    if (offset == 0 || offset == 4) {
+      EXPECT_TRUE(framer_.StartsWithChlo(
+          QuicUtils::GetCryptoStreamId(framer_.transport_version()), offset));
+    } else {
+      EXPECT_FALSE(framer_.StartsWithChlo(
+          QuicUtils::GetCryptoStreamId(framer_.transport_version()), offset));
+    }
+  }
+}
+
+TEST_P(QuicFramerTest, IetfBlockedFrame) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (IETF_BLOCKED)
+      {"",
+       {0x14}},
+      // blocked offset
+      {"Can not read blocked offset.",
+       {kVarInt62EightBytes + 0x3a, 0x98, 0xFE, 0xDC, 0x32, 0x10, 0x76, 0x54}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(kStreamOffset, visitor_.blocked_frame_.offset);
+
+  CheckFramingBoundaries(packet99, QUIC_INVALID_BLOCKED_DATA);
+}
+
+TEST_P(QuicFramerTest, BuildIetfBlockedPacket) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicBlockedFrame frame;
+  frame.stream_id = QuicUtils::GetInvalidStreamId(framer_.transport_version());
+  frame.offset = kStreamOffset;
+  QuicFrames frames = {QuicFrame(&frame)};
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_BLOCKED)
+    0x14,
+    // Offset
+    kVarInt62EightBytes + 0x3a, 0x98, 0xFE, 0xDC, 0x32, 0x10, 0x76, 0x54
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet99),
+                                      QUIC_ARRAYSIZE(packet99));
+}
+
+TEST_P(QuicFramerTest, IetfStreamBlockedFrame) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (IETF_STREAM_BLOCKED)
+      {"",
+       {0x15}},
+      // blocked offset
+      {"Can not read stream blocked stream id.",
+       {kVarInt62FourBytes + 0x01, 0x02, 0x03, 0x04}},
+      {"Can not read stream blocked offset.",
+       {kVarInt62EightBytes + 0x3a, 0x98, 0xFE, 0xDC, 0x32, 0x10, 0x76, 0x54}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(kStreamId, visitor_.blocked_frame_.stream_id);
+  EXPECT_EQ(kStreamOffset, visitor_.blocked_frame_.offset);
+
+  CheckFramingBoundaries(packet99, QUIC_INVALID_STREAM_BLOCKED_DATA);
+}
+
+TEST_P(QuicFramerTest, BuildIetfStreamBlockedPacket) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicBlockedFrame frame;
+  frame.stream_id = kStreamId;
+  frame.offset = kStreamOffset;
+  QuicFrames frames = {QuicFrame(&frame)};
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_STREAM_BLOCKED)
+    0x15,
+    // Stream ID
+    kVarInt62FourBytes + 0x01, 0x02, 0x03, 0x04,
+    // Offset
+    kVarInt62EightBytes + 0x3a, 0x98, 0xFE, 0xDC, 0x32, 0x10, 0x76, 0x54
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet99),
+                                      QUIC_ARRAYSIZE(packet99));
+}
+
+TEST_P(QuicFramerTest, ServerBiDiMaxStreamsFrame) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (IETF_MAX_STREAMS_BIDIRECTIONAL)
+      {"",
+       {0x12}},
+      // max. streams
+      {"Can not read MAX_STREAMS stream count.",
+       {kVarInt62OneByte + 0x03}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  // This test is a server receiving a MAX_STREAMS frame. The
+  // stream ID that it generates should be a server-initiated
+  // stream ID. The expected Stream ID is
+  //                 ((0x3-1) * 4) | 0x1 = 0x9
+  //                  count-to-id      server inited, bidi
+  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_SERVER,
+                           /*bidirectional=*/true, 3),
+            visitor_.max_stream_id_frame_.max_stream_id);
+  CheckFramingBoundaries(packet99, QUIC_MAX_STREAM_ID_DATA);
+}
+
+TEST_P(QuicFramerTest, ClientBiDiMaxStreamsFrame) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // Test runs in client mode, no connection id
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (IETF_MAX_STREAMS_BIDIRECTIONAL)
+      {"",
+       {0x12}},
+      // max. streams
+      {"Can not read MAX_STREAMS stream count.",
+       {kVarInt62OneByte + 0x03}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_0BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  // This test is a client receiving a MAX_STREAMS frame. The
+  // stream ID that it generates should be a client-initiated
+  // stream ID. The expected Stream ID is
+  //                ((0x3-1) * 4)       = 0xc
+  // It is not 8 because a client-initiated, bidi stream ID's
+  // low bits are 00 - which means that the old crypto stream
+  // falls into this category, and the first stream is streamid=4,
+  // not streamid=0.
+  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_CLIENT,
+                           /*bidirectional=*/true, 3),
+            visitor_.max_stream_id_frame_.max_stream_id);
+
+  CheckFramingBoundaries(packet99, QUIC_MAX_STREAM_ID_DATA);
+}
+
+TEST_P(QuicFramerTest, ServerUniDiMaxStreamsFrame) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (IETF_MAX_STREAMS_UNIDIRECTIONAL)
+      {"",
+       {0x13}},
+      // max. streams
+      {"Can not read MAX_STREAMS stream count.",
+       {kVarInt62OneByte + 0x03}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  // This test is a server receiving a MAX_STREAMS frame. The
+  // stream ID that it generates should be a server-initiated
+  // stream ID. The expected Stream ID is
+  //      ((0x3-1) * 4) | 0x1 | 0x2 = 0xb
+  //        count-to-id      server inited, unidi
+  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_SERVER,
+                           /*bidirectional=*/false, 3),
+            visitor_.max_stream_id_frame_.max_stream_id);
+
+  CheckFramingBoundaries(packet99, QUIC_MAX_STREAM_ID_DATA);
+}
+
+TEST_P(QuicFramerTest, ClientUniDiMaxStreamsFrame) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // Test runs in client mode, no connection id
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (IETF_MAX_STREAMS_UNIDIRECTIONAL)
+      {"",
+       {0x13}},
+      // max. streams
+      {"Can not read MAX_STREAMS stream count.",
+       {kVarInt62OneByte + 0x03}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_0BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  // This test is a client receiving a MAX_STREAMS frame. The
+  // stream ID that it generates should be a client-initiated
+  // stream ID. The expected Stream ID is
+  //               ((0x3-1) * 4) | 0x02= 0xa
+  //                count-to-id      client/unidi
+  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_CLIENT,
+                           /*bidirectional=*/false, 3),
+            visitor_.max_stream_id_frame_.max_stream_id);
+
+  CheckFramingBoundaries(packet99, QUIC_MAX_STREAM_ID_DATA);
+}
+
+// The following four tests ensure that the framer can deserialize a stream
+// count that is large enough to cause the resulting stream ID to exceed the
+// current implementation limit(32 bits). The intent is that when this happens,
+// the stream limit is pegged to the maximum supported value. There are four
+// tests, for the four combinations of uni- and bi-directional, server- and
+// client- initiated.
+TEST_P(QuicFramerTest, ServerBiDiMaxStreamsFrameTooBig) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x9A, 0xBC,
+    // frame type (IETF_MAX_STREAMS_BIDIRECTIONAL)
+    0x12,
+
+    // max. streams. Max stream ID allowed is 0xffffffff
+    // This encodes a count of 0x40000000, leading to stream
+    // IDs in the range 0x1 00000000 to 0x1 00000003.
+    kVarInt62EightBytes + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00
+  };
+  // clang-format on
+
+  QuicEncryptedPacket encrypted(AsChars(packet99), QUIC_ARRAYSIZE(packet99),
+                                false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  // This test is a server receiving a MAX_STREAMS frame. The
+  // stream ID that it generates should be a server-initiated
+  // stream ID. The expected Stream ID is
+  //            0xfffffffc | 0x01  --> 0xfffffffd
+  //              maxid     server inited, bidi
+  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_SERVER,
+                           /*bidirectional=*/true, 0x40000000),
+            visitor_.max_stream_id_frame_.max_stream_id);
+}
+
+TEST_P(QuicFramerTest, ClientBiDiMaxStreamsFrameTooBig) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // Test runs in client mode, no connection id
+    // packet number
+    0x12, 0x34, 0x9A, 0xBC,
+    // frame type (IETF_MAX_STREAMS_BIDIRECTIONAL)
+    0x12,
+
+    // max. streams. Max stream ID allowed is 0xffffffff
+    // This encodes a count of 0x40000000, leading to stream
+    // IDs in the range 0x1 00000000 to 0x1 00000003.
+    kVarInt62EightBytes + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00
+  };
+  // clang-format on
+
+  QuicEncryptedPacket encrypted(AsChars(packet99), QUIC_ARRAYSIZE(packet99),
+                                false);
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_0BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  // This test is a client receiving a MAX_STREAMS frame. The
+  // stream ID that it generates should be a client-initiated
+  // stream ID. The expected Stream ID is
+  //            0xfffffffc         --> 0xfffffffc
+  //            max id       bidi/client-inited
+  // TODO(fkastenholz): Change -2 to -1 when stream id 0 is no longer
+  // special.
+  // Subtract 1 because client/bidi stream ids start counting at
+  // 4, not 0. If we didn;t subtract 1, the resulting math would wrap to stream
+  // id 0, not 0xfffffffc.
+  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_CLIENT,
+                           /*bidirectional=*/true, (0x40000000 - 1)),
+            visitor_.max_stream_id_frame_.max_stream_id);
+}
+
+TEST_P(QuicFramerTest, ServerUniDiMaxStreamsFrameTooBig) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x9A, 0xBC,
+    // frame type (IETF_MAX_STREAMS_UNIDIRECTIONAL)
+    0x13,
+
+    // max. streams. Max stream ID allowed is 0xffffffff
+    // This encodes a count of 0x40000000, leading to stream
+    // IDs in the range 0x1 00000000 to 0x1 00000003.
+    kVarInt62EightBytes + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00
+  };
+  // clang-format on
+
+  QuicEncryptedPacket encrypted(AsChars(packet99), QUIC_ARRAYSIZE(packet99),
+                                false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  // This test is a server receiving a MAX_STREAMS frame. The
+  // stream ID that it generates should be a server-initiated
+  // stream ID. The expected Stream ID is
+  //      0xfffffffc | 0x1 | 0x2 = 0xffffffff
+  //        maxid      server inited, unidi
+  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_SERVER,
+                           /*bidirectional=*/false, 0x40000000),
+            visitor_.max_stream_id_frame_.max_stream_id);
+}
+
+TEST_P(QuicFramerTest, ClientUniDiMaxStreamsFrameTooBig) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // Test runs in client mode, no connection id
+    // packet number
+    0x12, 0x34, 0x9A, 0xBC,
+    // frame type (IETF_MAX_STREAMS_UNDIRECTIONAL)
+    0x13,
+
+    // max. streams. Max stream ID allowed is 0xffffffff
+    // This encodes a count of 0x40000000, leading to stream
+    // IDs in the range 0x1 00000000 to 0x1 00000003.
+    kVarInt62EightBytes + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00
+  };
+  // clang-format on
+
+  QuicEncryptedPacket encrypted(AsChars(packet99), QUIC_ARRAYSIZE(packet99),
+                                false);
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_0BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  // This test is a client receiving a MAX_STREAMS frame. The
+  // stream ID that it generates should be a client-initiated
+  // stream ID. The expected Stream ID is
+  //               0xfffffffc | 0x02= 0xfffffffe
+  //                maxid       client/unidi
+  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_CLIENT,
+                           /*bidirectional=*/false, 0x40000000),
+            visitor_.max_stream_id_frame_.max_stream_id);
+}
+
+// Check that a stream count of 0 is rejected.
+// Directionality and intiation are not important for
+// this test.
+TEST_P(QuicFramerTest, MaxStreamsFrameZeroCount) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x9A, 0xBC,
+    // frame type (IETF_MAX_STREAMS_BIDIRECTIONAL)
+    0x12,
+    // max. streams == 0.
+    kVarInt62OneByte + 0x00
+  };
+  // clang-format on
+
+  QuicEncryptedPacket encrypted(AsChars(packet99), QUIC_ARRAYSIZE(packet99),
+                                false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_MAX_STREAM_ID_DATA, framer_.error());
+  EXPECT_EQ(framer_.detailed_error(),
+            "MAX_STREAMS stream count of 0 not supported.");
+}
+
+TEST_P(QuicFramerTest, ServerBiDiStreamsBlockedFrame) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (IETF_STREAMS_BLOCKED_BIDIRECTIONAL frame)
+      {"",
+       {0x16}},
+      // stream id
+      {"Can not read STREAMS_BLOCKED stream id.",
+       {kVarInt62OneByte + 0x03}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  // This test is a server receiving a STREAMS_BLOCKED frame. The
+  // stream ID that it generates should be a client-initiated
+  // stream ID. The expected Stream ID is
+  //                ((0x3-1) * 4)        = 0xc
+  //                 count-to-id      client inited, bidi
+  // It is not 8 because a client-initiated, bidi stream ID's
+  // low bits are 00 - which means that the old crypto stream
+  // falls into this category, and the first stream is streamid=4,
+  // not streamid=0.
+  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_CLIENT,
+                           /*bidirectional=*/true, 3),
+            visitor_.stream_id_blocked_frame_.stream_id);
+
+  CheckFramingBoundaries(packet99, QUIC_STREAM_ID_BLOCKED_DATA);
+}
+
+TEST_P(QuicFramerTest, ClientBiDiStreamsBlockedFrame) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // Test runs in client mode, no connection id
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (IETF_STREAMS_BLOCKED_BIDIRECTIONAL frame)
+      {"",
+       {0x16}},
+      // stream id
+      {"Can not read STREAMS_BLOCKED stream id.",
+       {kVarInt62OneByte + 0x03}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_0BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  // This test is a client receiving a STREAMS_BLOCKED frame. The
+  // stream ID that it generates should be a server-initiated
+  // stream ID. The expected Stream ID is
+  //                ((0x3-1) * 4) | 0x01 = 0x9
+  //                 count-to-id      server inited, bidi
+  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_SERVER,
+                           /*bidirectional=*/true, 3),
+            visitor_.stream_id_blocked_frame_.stream_id);
+
+  CheckFramingBoundaries(packet99, QUIC_STREAM_ID_BLOCKED_DATA);
+}
+
+TEST_P(QuicFramerTest, ServerUniDiStreamsBlockedFrame) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (IETF_STREAMS_BLOCKED_UNIDIRECTIONAL frame)
+      {"",
+       {0x17}},
+      // stream id
+      {"Can not read STREAMS_BLOCKED stream id.",
+       {kVarInt62OneByte + 0x03}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  // This test is a server receiving a STREAMS_BLOCKED frame. The
+  // stream ID that it generates should be a client-initiated
+  // stream ID. The expected Stream ID is
+  //                ((0x3-1) * 4)  | 0x2  = 0xa
+  //                 count-to-id      client inited, unidi
+  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_CLIENT,
+                           /*bidirectional=*/false, 3),
+            visitor_.stream_id_blocked_frame_.stream_id);
+
+  CheckFramingBoundaries(packet99, QUIC_STREAM_ID_BLOCKED_DATA);
+}
+
+TEST_P(QuicFramerTest, ClientUniDiStreamsBlockedFrame) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // Test runs in client mode, no connection id
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (IETF_STREAMS_BLOCKED_UNIDIRECTIONAL frame)
+      {"",
+       {0x17}},
+      // stream id
+      {"Can not read STREAMS_BLOCKED stream id.",
+       {kVarInt62OneByte + 0x03}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_0BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  // This test is a client receiving a STREAMS_BLOCKED frame. The
+  // stream ID that it generates should be a server-initiated
+  // stream ID. The expected Stream ID is
+  //                ((0x3-1) * 4) | 0x01 | 0x2 = 0xb
+  //                 count-to-id      server inited, bidi
+  EXPECT_EQ(GetNthStreamid(QUIC_VERSION_99, Perspective::IS_SERVER,
+                           /*bidirectional=*/false, 3),
+            visitor_.stream_id_blocked_frame_.stream_id);
+
+  CheckFramingBoundaries(packet99, QUIC_STREAM_ID_BLOCKED_DATA);
+}
+
+// Check that when we get a STREAMS_BLOCKED frame that specifies too large
+// a stream count, we reject with an appropriate error. There is no need to
+// check for different combinations of Uni/Bi directional and client/server
+// initiated; the logic does not take these into account.
+TEST_P(QuicFramerTest, StreamsBlockedFrameTooBig) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // Test runs in client mode, no connection id
+    // packet number
+    0x12, 0x34, 0x9A, 0xBC,
+    // frame type (IETF_STREAMS_BLOCKED_BIDIRECTIONAL)
+    0x17,
+
+    // max. streams. Max stream ID allowed is 0xffffffff
+    // This encodes a count of 0x40000000, leading to stream
+    // IDs in the range 0x1 00000000 to 0x1 00000003.
+    kVarInt62EightBytes + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01
+  };
+  // clang-format on
+
+  QuicEncryptedPacket encrypted(AsChars(packet99), QUIC_ARRAYSIZE(packet99),
+                                false);
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_STREAM_ID_BLOCKED_DATA, framer_.error());
+  EXPECT_EQ(framer_.detailed_error(),
+            "STREAMS_BLOCKED stream count exceeds implementation limit.");
+}
+
+// Test that count==0 is rejected.
+TEST_P(QuicFramerTest, StreamsBlockedFrameZeroCount) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // Test runs in client mode, no connection id
+    // packet number
+    0x12, 0x34, 0x9A, 0xBC,
+    // frame type (IETF_STREAMS_BLOCKED_BIDIRECTIONAL)
+    0x17,
+
+    // max. streams = 0
+    kVarInt62OneByte + 0x00
+  };
+  // clang-format on
+
+  QuicEncryptedPacket encrypted(AsChars(packet99), QUIC_ARRAYSIZE(packet99),
+                                false);
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_STREAM_ID_BLOCKED_DATA, framer_.error());
+  EXPECT_EQ(framer_.detailed_error(),
+            "STREAMS_BLOCKED stream count 0 not supported.");
+}
+
+TEST_P(QuicFramerTest, BuildServerBiDiStreamsBlockedPacket) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicStreamIdBlockedFrame frame;
+  // A server building a STREAMS_BLOCKED frame generates
+  // a server-initiated stream ID. This test is bidirectional.
+  // The low two bits of the stream ID are 01
+  // Expected value is 0x8u | 0x1u;
+  frame.stream_id = GetNthStreamid(QUIC_VERSION_99, Perspective::IS_SERVER,
+                                   /*bidirectional=*/true, 3);
+
+  QuicFrames frames = {QuicFrame(frame)};
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_STREAMS_BLOCKED_BIDIRECTIONAL frame)
+    0x16,
+    // Stream count
+    kVarInt62OneByte + 0x03
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet99),
+                                      QUIC_ARRAYSIZE(packet99));
+}
+
+TEST_P(QuicFramerTest, BuildClientBiDiStreamsBlockedPacket) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // This test runs in client mode.
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicStreamIdBlockedFrame frame;
+  // A client building a STREAMS_BLOCKED frame generates
+  // a client-initiated stream ID. This test is bidirectional.
+  // The low two bits of the stream ID are 00. Expected value is 0x8
+  frame.stream_id = GetNthStreamid(QUIC_VERSION_99, Perspective::IS_CLIENT,
+                                   /*bidirectional=*/true, 3);
+  QuicFrames frames = {QuicFrame(frame)};
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_STREAMS_BLOCKED_BIDIRECTIONAL frame)
+    0x16,
+    // Stream count
+    kVarInt62OneByte + 0x03
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet99),
+                                      QUIC_ARRAYSIZE(packet99));
+}
+
+TEST_P(QuicFramerTest, BuildServerUniStreamsBlockedPacket) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicStreamIdBlockedFrame frame;
+  // A server building a STREAMS_BLOCKED frame generates
+  // a server-initiated stream ID. This test is bidirectional.
+  // The low two bits of the stream ID are 11. Expected value is 0xb
+  frame.stream_id = GetNthStreamid(QUIC_VERSION_99, Perspective::IS_SERVER,
+                                   /*bidirectional=*/false, 3);
+  QuicFrames frames = {QuicFrame(frame)};
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_STREAMS_BLOCKED_UNIDIRECTIONAL frame)
+    0x17,
+    // Stream count
+    kVarInt62OneByte + 0x03
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet99),
+                                      QUIC_ARRAYSIZE(packet99));
+}
+
+TEST_P(QuicFramerTest, BuildClientUniDiStreamsBlockedPacket) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // This test runs in client mode.
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicStreamIdBlockedFrame frame;
+  // A client building a STREAMS_BLOCKED frame generates
+  // a client-initiated stream ID. This test is bidirectional.
+  // The low two bits of the stream ID are 10. Expected value is 0xa
+  frame.stream_id = GetNthStreamid(QUIC_VERSION_99, Perspective::IS_CLIENT,
+                                   /*bidirectional=*/false, 3);
+  QuicFrames frames = {QuicFrame(frame)};
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_STREAMS_BLOCKED_UNIDIRECTIONAL frame)
+    0x17,
+    // Stream count
+    kVarInt62OneByte + 0x03
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet99),
+                                      QUIC_ARRAYSIZE(packet99));
+}
+
+TEST_P(QuicFramerTest, BuildServerBiDiMaxStreamsPacket) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicMaxStreamIdFrame frame;
+  // A server building a MAX_STREAMS frame generates
+  // a client-initiated stream ID. This test is bidirectional.
+  // The low two bits of the stream ID are 00. Expected value is 0xc
+  // because streamid==0 is special and the first client/bidi
+  // stream is 4, not 0.
+  frame.max_stream_id = GetNthStreamid(QUIC_VERSION_99, Perspective::IS_CLIENT,
+                                       /*bidirectional=*/true, 3);
+  QuicFrames frames = {QuicFrame(frame)};
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_MAX_STREAMS_BIDIRECTIONAL frame)
+    0x12,
+    // Stream count
+    kVarInt62OneByte + 0x03
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet99),
+                                      QUIC_ARRAYSIZE(packet99));
+}
+
+TEST_P(QuicFramerTest, BuildClientBiDiMaxStreamsPacket) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // This test runs in client mode.
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicMaxStreamIdFrame frame;
+  // A client building a MAX_STREAMS frame generates
+  // a server-initiated stream ID. This test is bidirectional.
+  // The low two bits of the stream ID are 01. Expected value is 0x9
+  frame.max_stream_id = GetNthStreamid(QUIC_VERSION_99, Perspective::IS_SERVER,
+                                       /*bidirectional=*/true, 3);
+  QuicFrames frames = {QuicFrame(frame)};
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_MAX_STREAMS_BIDIRECTIONAL frame)
+    0x12,
+    // Stream count
+    kVarInt62OneByte + 0x03
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet99),
+                                      QUIC_ARRAYSIZE(packet99));
+}
+
+TEST_P(QuicFramerTest, BuildServerUniMaxStreamsPacket) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicMaxStreamIdFrame frame;
+  // A server building a MAX_STREAMS frame generates
+  // a client-initiated stream ID. This test is bidirectional.
+  // The low two bits of the stream ID are 10. Expected value is 0xa
+  frame.max_stream_id = GetNthStreamid(QUIC_VERSION_99, Perspective::IS_CLIENT,
+                                       /*bidirectional=*/false, 3);
+  QuicFrames frames = {QuicFrame(frame)};
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_MAX_STREAMS_UNIDIRECTIONAL frame)
+    0x13,
+    // Stream count
+    kVarInt62OneByte + 0x03
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet99),
+                                      QUIC_ARRAYSIZE(packet99));
+}
+
+TEST_P(QuicFramerTest, BuildClientUniDiMaxStreamsPacket) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // This test runs in client mode.
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicMaxStreamIdFrame frame;
+  // A client building a MAX_STREAMS frame generates
+  // a server-initiated stream ID. This test is bidirectional.
+  // The low two bits of the stream ID are 11. Expected value is 0xb
+  frame.max_stream_id = GetNthStreamid(QUIC_VERSION_99, Perspective::IS_SERVER,
+                                       /*bidirectional=*/false, 3);
+  QuicFrames frames = {QuicFrame(frame)};
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_MAX_STREAMS_UNIDIRECTIONAL frame)
+    0x13,
+    // Stream count
+    kVarInt62OneByte + 0x03
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet99),
+                                      QUIC_ARRAYSIZE(packet99));
+}
+
+TEST_P(QuicFramerTest, NewConnectionIdFrame) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    // This frame is only for version 99.
+    return;
+  }
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (IETF_NEW_CONNECTION_ID frame)
+      {"",
+       {0x18}},
+      // error code
+      {"Unable to read new connection ID frame sequence number.",
+       {kVarInt62OneByte + 0x11}},
+      {"Unable to read new connection ID frame connection id length.",
+       {0x08}},  // connection ID length
+      {"Unable to read new connection ID frame connection id.",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x11}},
+      {"Can not read new connection ID frame reset token.",
+       {0xb5, 0x69, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+
+  EXPECT_EQ(FramerTestConnectionIdPlusOne(),
+            visitor_.new_connection_id_.connection_id);
+  EXPECT_EQ(0x11u, visitor_.new_connection_id_.sequence_number);
+  EXPECT_EQ(kTestStatelessResetToken,
+            visitor_.new_connection_id_.stateless_reset_token);
+
+  ASSERT_EQ(0u, visitor_.ack_frames_.size());
+
+  CheckFramingBoundaries(packet99, QUIC_INVALID_NEW_CONNECTION_ID_DATA);
+}
+
+TEST_P(QuicFramerTest, BuildNewConnectionIdFramePacket) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    // This frame is only for version 99.
+    return;
+  }
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicNewConnectionIdFrame frame;
+  frame.sequence_number = 0x11;
+  // Use this value to force a 4-byte encoded variable length connection ID
+  // in the frame.
+  frame.connection_id = FramerTestConnectionIdPlusOne();
+  frame.stateless_reset_token = kTestStatelessResetToken;
+
+  QuicFrames frames = {QuicFrame(&frame)};
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_NEW_CONNECTION_ID frame)
+    0x18,
+    // sequence number
+    kVarInt62OneByte + 0x11,
+    // new connection id length
+    0x08,
+    // new connection id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x11,
+    // stateless reset token
+    0xb5, 0x69, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet99),
+                                      QUIC_ARRAYSIZE(packet99));
+}
+
+TEST_P(QuicFramerTest, NewTokenFrame) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    // This frame is only for version 99.
+    return;
+  }
+  // clang-format off
+  PacketFragments packet = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (IETF_NEW_TOKEN frame)
+      {"",
+       {0x07}},
+      // Length
+      {"Unable to read new token length.",
+       {kVarInt62OneByte + 0x08}},
+      {"Unable to read new token data.",
+       {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}}
+  };
+  // clang-format on
+  uint8_t expected_token_value[] = {0x00, 0x01, 0x02, 0x03,
+                                    0x04, 0x05, 0x06, 0x07};
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+
+  EXPECT_EQ(sizeof(expected_token_value), visitor_.new_token_.token.length());
+  EXPECT_EQ(0, memcmp(expected_token_value, visitor_.new_token_.token.data(),
+                      sizeof(expected_token_value)));
+
+  CheckFramingBoundaries(packet, QUIC_INVALID_NEW_TOKEN);
+}
+
+TEST_P(QuicFramerTest, BuildNewTokenFramePacket) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    // This frame is only for version 99.
+    return;
+  }
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  uint8_t expected_token_value[] = {0x00, 0x01, 0x02, 0x03,
+                                    0x04, 0x05, 0x06, 0x07};
+
+  QuicNewTokenFrame frame(0, QuicString((const char*)(expected_token_value),
+                                        sizeof(expected_token_value)));
+
+  QuicFrames frames = {QuicFrame(&frame)};
+
+  // clang-format off
+  unsigned char packet[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_NEW_TOKEN frame)
+    0x07,
+    // Length and token
+    kVarInt62OneByte + 0x08,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet),
+                                      QUIC_ARRAYSIZE(packet));
+}
+
+TEST_P(QuicFramerTest, IetfStopSendingFrame) {
+  // This test is only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (IETF_STOP_SENDING frame)
+      {"",
+       {0x05}},
+      // stream id
+      {"Unable to read stop sending stream id.",
+       {kVarInt62FourBytes + 0x01, 0x02, 0x03, 0x04}},
+      {"Unable to read stop sending application error code.",
+       {0x76, 0x54}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(kStreamId, visitor_.stop_sending_frame_.stream_id);
+  EXPECT_EQ(0x7654, visitor_.stop_sending_frame_.application_error_code);
+
+  CheckFramingBoundaries(packet99, QUIC_INVALID_STOP_SENDING_FRAME_DATA);
+}
+
+TEST_P(QuicFramerTest, BuildIetfStopSendingPacket) {
+  // This test is only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicStopSendingFrame frame;
+  frame.stream_id = kStreamId;
+  frame.application_error_code = 0xffff;
+  QuicFrames frames = {QuicFrame(&frame)};
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_STOP_SENDING frame)
+    0x05,
+    // Stream ID
+    kVarInt62FourBytes + 0x01, 0x02, 0x03, 0x04,
+    // Application error code
+    0xff, 0xff
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet99),
+                                      QUIC_ARRAYSIZE(packet99));
+}
+
+TEST_P(QuicFramerTest, IetfPathChallengeFrame) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (IETF_PATH_CHALLENGE)
+      {"",
+       {0x1a}},
+      // data
+      {"Can not read path challenge data.",
+       {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(QuicPathFrameBuffer({{0, 1, 2, 3, 4, 5, 6, 7}}),
+            visitor_.path_challenge_frame_.data_buffer);
+
+  CheckFramingBoundaries(packet99, QUIC_INVALID_PATH_CHALLENGE_DATA);
+}
+
+TEST_P(QuicFramerTest, BuildIetfPathChallengePacket) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicPathChallengeFrame frame;
+  frame.data_buffer = QuicPathFrameBuffer({{0, 1, 2, 3, 4, 5, 6, 7}});
+  QuicFrames frames = {QuicFrame(&frame)};
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_PATH_CHALLENGE)
+    0x1a,
+    // Data
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet99),
+                                      QUIC_ARRAYSIZE(packet99));
+}
+
+TEST_P(QuicFramerTest, IetfPathResponseFrame) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (IETF_PATH_RESPONSE)
+      {"",
+       {0x1b}},
+      // data
+      {"Can not read path response data.",
+       {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(QuicPathFrameBuffer({{0, 1, 2, 3, 4, 5, 6, 7}}),
+            visitor_.path_response_frame_.data_buffer);
+
+  CheckFramingBoundaries(packet99, QUIC_INVALID_PATH_RESPONSE_DATA);
+}
+
+TEST_P(QuicFramerTest, BuildIetfPathResponsePacket) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicPathResponseFrame frame;
+  frame.data_buffer = QuicPathFrameBuffer({{0, 1, 2, 3, 4, 5, 6, 7}});
+  QuicFrames frames = {QuicFrame(&frame)};
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_PATH_RESPONSE)
+    0x1b,
+    // Data
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet99),
+                                      QUIC_ARRAYSIZE(packet99));
+}
+
+TEST_P(QuicFramerTest, GetRetransmittableControlFrameSize) {
+  QuicRstStreamFrame rst_stream(1, 3, QUIC_STREAM_CANCELLED, 1024);
+  EXPECT_EQ(QuicFramer::GetRstStreamFrameSize(framer_.transport_version(),
+                                              rst_stream),
+            QuicFramer::GetRetransmittableControlFrameSize(
+                framer_.transport_version(), QuicFrame(&rst_stream)));
+
+  QuicString error_detail(2048, 'e');
+  QuicConnectionCloseFrame connection_close(QUIC_NETWORK_IDLE_TIMEOUT,
+                                            error_detail);
+  EXPECT_EQ(QuicFramer::GetMinConnectionCloseFrameSize(
+                framer_.transport_version(), connection_close) +
+                256,
+            QuicFramer::GetRetransmittableControlFrameSize(
+                framer_.transport_version(), QuicFrame(&connection_close)));
+
+  QuicGoAwayFrame goaway(2, QUIC_PEER_GOING_AWAY, 3, error_detail);
+  EXPECT_EQ(QuicFramer::GetMinGoAwayFrameSize() + 256,
+            QuicFramer::GetRetransmittableControlFrameSize(
+                framer_.transport_version(), QuicFrame(&goaway)));
+
+  QuicWindowUpdateFrame window_update(3, 3, 1024);
+  EXPECT_EQ(QuicFramer::GetWindowUpdateFrameSize(framer_.transport_version(),
+                                                 window_update),
+            QuicFramer::GetRetransmittableControlFrameSize(
+                framer_.transport_version(), QuicFrame(&window_update)));
+
+  QuicBlockedFrame blocked(4, 3, 1024);
+  EXPECT_EQ(
+      QuicFramer::GetBlockedFrameSize(framer_.transport_version(), blocked),
+      QuicFramer::GetRetransmittableControlFrameSize(
+          framer_.transport_version(), QuicFrame(&blocked)));
+
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+  QuicApplicationCloseFrame application_close;
+  EXPECT_EQ(QuicFramer::GetMinApplicationCloseFrameSize(
+                framer_.transport_version(), application_close),
+            QuicFramer::GetRetransmittableControlFrameSize(
+                framer_.transport_version(), QuicFrame(&application_close)));
+
+  QuicNewConnectionIdFrame new_connection_id(5, TestConnectionId(), 1, 101111);
+  EXPECT_EQ(QuicFramer::GetNewConnectionIdFrameSize(new_connection_id),
+            QuicFramer::GetRetransmittableControlFrameSize(
+                framer_.transport_version(), QuicFrame(&new_connection_id)));
+
+  QuicMaxStreamIdFrame max_stream_id(6, 3);
+  EXPECT_EQ(QuicFramer::GetMaxStreamsFrameSize(framer_.transport_version(),
+                                               max_stream_id),
+            QuicFramer::GetRetransmittableControlFrameSize(
+                framer_.transport_version(), QuicFrame(max_stream_id)));
+
+  QuicStreamIdBlockedFrame stream_id_blocked(7, 3);
+  EXPECT_EQ(QuicFramer::GetStreamsBlockedFrameSize(framer_.transport_version(),
+                                                   stream_id_blocked),
+            QuicFramer::GetRetransmittableControlFrameSize(
+                framer_.transport_version(), QuicFrame(stream_id_blocked)));
+
+  QuicPathFrameBuffer buffer = {
+      {0x80, 0x91, 0xa2, 0xb3, 0xc4, 0xd5, 0xe5, 0xf7}};
+  QuicPathResponseFrame path_response_frame(8, buffer);
+  EXPECT_EQ(QuicFramer::GetPathResponseFrameSize(path_response_frame),
+            QuicFramer::GetRetransmittableControlFrameSize(
+                framer_.transport_version(), QuicFrame(&path_response_frame)));
+
+  QuicPathChallengeFrame path_challenge_frame(9, buffer);
+  EXPECT_EQ(QuicFramer::GetPathChallengeFrameSize(path_challenge_frame),
+            QuicFramer::GetRetransmittableControlFrameSize(
+                framer_.transport_version(), QuicFrame(&path_challenge_frame)));
+
+  QuicStopSendingFrame stop_sending_frame(10, 3, 20);
+  EXPECT_EQ(QuicFramer::GetStopSendingFrameSize(stop_sending_frame),
+            QuicFramer::GetRetransmittableControlFrameSize(
+                framer_.transport_version(), QuicFrame(&stop_sending_frame)));
+}
+
+// A set of tests to ensure that bad frame-type encodings
+// are properly detected and handled.
+// First, four tests to see that unknown frame types generate
+// a QUIC_INVALID_FRAME_DATA error with detailed information
+// "Illegal frame type." This regardless of the encoding of the type
+// (1/2/4/8 bytes).
+// This only for version 99.
+TEST_P(QuicFramerTest, IetfFrameTypeEncodingErrorUnknown1Byte) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+  // clang-format off
+  PacketFragments packet = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (unknown value, single-byte encoding)
+      {"",
+       {0x38}}
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet));
+
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_INVALID_FRAME_DATA, framer_.error());
+  EXPECT_EQ("Illegal frame type.", framer_.detailed_error());
+}
+
+TEST_P(QuicFramerTest, IetfFrameTypeEncodingErrorUnknown2Bytes) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (unknown value, two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x01, 0x38}}
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet));
+
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_INVALID_FRAME_DATA, framer_.error());
+  EXPECT_EQ("Illegal frame type.", framer_.detailed_error());
+}
+
+TEST_P(QuicFramerTest, IetfFrameTypeEncodingErrorUnknown4Bytes) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (unknown value, four-byte encoding)
+      {"",
+       {kVarInt62FourBytes + 0x01, 0x00, 0x00, 0x38}}
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet));
+
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_INVALID_FRAME_DATA, framer_.error());
+  EXPECT_EQ("Illegal frame type.", framer_.detailed_error());
+}
+
+TEST_P(QuicFramerTest, IetfFrameTypeEncodingErrorUnknown8Bytes) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+  // clang-format off
+  PacketFragments packet = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (unknown value, eight-byte encoding)
+      {"",
+       {kVarInt62EightBytes + 0x01, 0x00, 0x00, 0x01, 0x02, 0x34, 0x56, 0x38}}
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet));
+
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_INVALID_FRAME_DATA, framer_.error());
+  EXPECT_EQ("Illegal frame type.", framer_.detailed_error());
+}
+
+// Three tests to check that known frame types that are not minimally
+// encoded generate IETF_QUIC_PROTOCOL_VIOLATION errors with detailed
+// information "Frame type not minimally encoded."
+// Look at the frame-type encoded in 2, 4, and 8 bytes.
+TEST_P(QuicFramerTest, IetfFrameTypeEncodingErrorKnown2Bytes) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (Blocked, two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x08}}
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet));
+
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(IETF_QUIC_PROTOCOL_VIOLATION, framer_.error());
+  EXPECT_EQ("Frame type not minimally encoded.", framer_.detailed_error());
+}
+
+TEST_P(QuicFramerTest, IetfFrameTypeEncodingErrorKnown4Bytes) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packet = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (Blocked, four-byte encoding)
+      {"",
+       {kVarInt62FourBytes + 0x00, 0x00, 0x00, 0x08}}
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet));
+
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(IETF_QUIC_PROTOCOL_VIOLATION, framer_.error());
+  EXPECT_EQ("Frame type not minimally encoded.", framer_.detailed_error());
+}
+
+TEST_P(QuicFramerTest, IetfFrameTypeEncodingErrorKnown8Bytes) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+  // clang-format off
+  PacketFragments packet = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (Blocked, eight-byte encoding)
+      {"",
+       {kVarInt62EightBytes + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08}}
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet));
+
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(IETF_QUIC_PROTOCOL_VIOLATION, framer_.error());
+  EXPECT_EQ("Frame type not minimally encoded.", framer_.detailed_error());
+}
+
+// Tests to check that all known OETF frame types that are not minimally
+// encoded generate IETF_QUIC_PROTOCOL_VIOLATION errors with detailed
+// information "Frame type not minimally encoded."
+// Just look at 2-byte encoding.
+TEST_P(QuicFramerTest, IetfFrameTypeEncodingErrorKnown2BytesAllTypes) {
+  // This test only for version 99.
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    return;
+  }
+
+  // clang-format off
+  PacketFragments packets[] = {
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x00}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x01}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x02}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x03}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x04}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x05}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x06}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x07}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x08}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x09}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x0a}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x0b}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x0c}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x0d}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x0e}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x0f}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x10}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x11}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x12}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x13}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x14}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x15}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x16}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x17}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x18}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x20}}
+    },
+    {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x9A, 0xBC}},
+      // frame type (two-byte encoding)
+      {"",
+       {kVarInt62TwoBytes + 0x00, 0x21}}
+    },
+  };
+  // clang-format on
+
+  for (PacketFragments& packet : packets) {
+    std::unique_ptr<QuicEncryptedPacket> encrypted(
+        AssemblePacketFromFragments(packet));
+
+    EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+
+    EXPECT_EQ(IETF_QUIC_PROTOCOL_VIOLATION, framer_.error());
+    EXPECT_EQ("Frame type not minimally encoded.", framer_.detailed_error());
+  }
+}
+
+TEST_P(QuicFramerTest, RetireConnectionIdFrame) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    // This frame is only for version 99.
+    return;
+  }
+  // clang-format off
+  PacketFragments packet99 = {
+      // type (short header, 4 byte packet number)
+      {"",
+       {0x43}},
+      // connection_id
+      {"",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}},
+      // packet number
+      {"",
+       {0x12, 0x34, 0x56, 0x78}},
+      // frame type (IETF_RETIRE_CONNECTION_ID frame)
+      {"",
+       {0x19}},
+      // Sequence number
+      {"Unable to read retire connection ID frame sequence number.",
+       {kVarInt62TwoBytes + 0x11, 0x22}}
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet99));
+  EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(
+      *encrypted, !kIncludeVersion, !kIncludeDiversificationNonce,
+      PACKET_8BYTE_CONNECTION_ID, PACKET_0BYTE_CONNECTION_ID));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+
+  EXPECT_EQ(0x1122u, visitor_.retire_connection_id_.sequence_number);
+
+  ASSERT_EQ(0u, visitor_.ack_frames_.size());
+
+  CheckFramingBoundaries(packet99, QUIC_INVALID_RETIRE_CONNECTION_ID_DATA);
+}
+
+TEST_P(QuicFramerTest, BuildRetireConnectionIdFramePacket) {
+  if (framer_.transport_version() != QUIC_VERSION_99) {
+    // This frame is only for version 99.
+    return;
+  }
+  QuicPacketHeader header;
+  header.destination_connection_id = FramerTestConnectionId();
+  header.reset_flag = false;
+  header.version_flag = false;
+  header.packet_number = kPacketNumber;
+
+  QuicRetireConnectionIdFrame frame;
+  frame.sequence_number = 0x1122;
+
+  QuicFrames frames = {QuicFrame(&frame)};
+
+  // clang-format off
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_RETIRE_CONNECTION_ID frame)
+    0x19,
+    // sequence number
+    kVarInt62TwoBytes + 0x11, 0x22
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet99),
+                                      QUIC_ARRAYSIZE(packet99));
+}
+
+TEST_P(QuicFramerTest, AckFrameWithInvalidLargestObserved) {
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x2C,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (ack frame)
+    0x45,
+    // largest observed
+    0x00, 0x00,
+    // Zero delta time.
+    0x00, 0x00,
+    // first ack block length.
+    0x00, 0x00,
+    // num timestamps.
+    0x00
+  };
+
+  unsigned char packet44[] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (ack frame)
+    0x45,
+    // largest observed
+    0x00, 0x00,
+    // Zero delta time.
+    0x00, 0x00,
+    // first ack block length.
+    0x00, 0x00,
+    // num timestamps.
+    0x00
+  };
+
+  unsigned char packet46[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (ack frame)
+    0x45,
+    // largest observed
+    0x00, 0x00,
+    // Zero delta time.
+    0x00, 0x00,
+    // first ack block length.
+    0x00, 0x00,
+    // num timestamps.
+    0x00
+  };
+
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_ACK frame)
+    0x02,
+    // Largest acked
+    kVarInt62OneByte + 0x00,
+    // Zero delta time.
+    kVarInt62OneByte + 0x00,
+    // Ack block count 0
+    kVarInt62OneByte + 0x00,
+    // First ack block length
+    kVarInt62OneByte + 0x00,
+  };
+  // clang-format on
+
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    p_size = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+
+  QuicEncryptedPacket encrypted(AsChars(p), p_size, false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(framer_.detailed_error(), "Largest acked is 0.");
+}
+
+TEST_P(QuicFramerTest, FirstAckBlockJustUnderFlow) {
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x2C,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (ack frame)
+    0x45,
+    // largest observed
+    0x00, 0x02,
+    // Zero delta time.
+    0x00, 0x00,
+    // first ack block length.
+    0x00, 0x03,
+    // num timestamps.
+    0x00
+  };
+
+  unsigned char packet44[] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (ack frame)
+    0x45,
+    // largest observed
+    0x00, 0x02,
+    // Zero delta time.
+    0x00, 0x00,
+    // first ack block length.
+    0x00, 0x03,
+    // num timestamps.
+    0x00
+  };
+
+  unsigned char packet46[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (ack frame)
+    0x45,
+    // largest observed
+    0x00, 0x02,
+    // Zero delta time.
+    0x00, 0x00,
+    // first ack block length.
+    0x00, 0x03,
+    // num timestamps.
+    0x00
+  };
+
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_ACK frame)
+    0x02,
+    // Largest acked
+    kVarInt62OneByte + 0x02,
+    // Zero delta time.
+    kVarInt62OneByte + 0x00,
+    // Ack block count 0
+    kVarInt62OneByte + 0x00,
+    // First ack block length
+    kVarInt62OneByte + 0x02,
+  };
+  // clang-format on
+
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    p_size = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    p_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+
+  QuicEncryptedPacket encrypted(AsChars(p), p_size, false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(framer_.detailed_error(),
+            "Underflow with first ack block length 3 largest acked is 2.");
+}
+
+TEST_P(QuicFramerTest, ThirdAckBlockJustUnderflow) {
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x2C,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (ack frame)
+    0x60,
+    // largest observed
+    0x0A,
+    // Zero delta time.
+    0x00, 0x00,
+    // Num of ack blocks
+    0x02,
+    // first ack block length.
+    0x02,
+    // gap to next block
+    0x01,
+    // ack block length
+    0x01,
+    // gap to next block
+    0x01,
+    // ack block length
+    0x06,
+    // num timestamps.
+    0x00
+  };
+
+  unsigned char packet44[] = {
+    // type (short header, 4 byte packet number)
+    0x32,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (ack frame)
+    0x60,
+    // largest observed
+    0x0A,
+    // Zero delta time.
+    0x00, 0x00,
+    // Num of ack blocks
+    0x02,
+    // first ack block length.
+    0x02,
+    // gap to next block
+    0x01,
+    // ack block length
+    0x01,
+    // gap to next block
+    0x01,
+    // ack block length
+    0x06,
+    // num timestamps.
+    0x00
+  };
+
+  unsigned char packet46[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (ack frame)
+    0x60,
+    // largest observed
+    0x0A,
+    // Zero delta time.
+    0x00, 0x00,
+    // Num of ack blocks
+    0x02,
+    // first ack block length.
+    0x02,
+    // gap to next block
+    0x01,
+    // ack block length
+    0x01,
+    // gap to next block
+    0x01,
+    // ack block length
+    0x06,
+    // num timestamps.
+    0x00
+  };
+
+  unsigned char packet99[] = {
+    // type (short header, 4 byte packet number)
+    0x43,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x78,
+
+    // frame type (IETF_ACK frame)
+    0x02,
+    // Largest acked
+    kVarInt62OneByte + 0x0A,
+    // Zero delta time.
+    kVarInt62OneByte + 0x00,
+    // Ack block count 2
+    kVarInt62OneByte + 0x02,
+    // First ack block length
+    kVarInt62OneByte + 0x01,
+    // gap to next block length
+    kVarInt62OneByte + 0x00,
+    // ack block length
+    kVarInt62OneByte + 0x00,
+    // gap to next block length
+    kVarInt62OneByte + 0x00,
+    // ack block length
+    kVarInt62OneByte + 0x05,
+  };
+  // clang-format on
+
+  unsigned char* p = packet;
+  size_t p_size = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    p_size = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() > QUIC_VERSION_44) {
+    p = packet46;
+    p_size = QUIC_ARRAYSIZE(packet46);
+  } else if (framer_.transport_version() > QUIC_VERSION_43) {
+    p = packet44;
+    p_size = QUIC_ARRAYSIZE(packet44);
+  }
+
+  QuicEncryptedPacket encrypted(AsChars(p), p_size, false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    EXPECT_EQ(framer_.detailed_error(),
+              "Underflow with ack block length 6 latest ack block end is 5.");
+  } else {
+    EXPECT_EQ(framer_.detailed_error(),
+              "Underflow with ack block length 6, end of block is 6.");
+  }
+}
+
+TEST_P(QuicFramerTest, CoalescedPacket) {
+  if (!QuicVersionHasLongHeaderLengths(framer_.transport_version())) {
+    return;
+  }
+  // clang-format off
+  unsigned char packet[] = {
+    // first coalesced packet
+      // public flags (long header with packet type ZERO_RTT_PROTECTED and
+      // 4-byte packet number)
+      0xD3,
+      // version
+      QUIC_VERSION_BYTES,
+      // destination connection ID length
+      0x50,
+      // destination connection ID
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // long header packet length
+      0x1E,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+      // frame type (IETF_STREAM frame with FIN, LEN, and OFFSET bits set)
+      0x08 | 0x01 | 0x02 | 0x04,
+      // stream id
+      kVarInt62FourBytes + 0x00, 0x02, 0x03, 0x04,
+      // offset
+      kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+      0x32, 0x10, 0x76, 0x54,
+      // data length
+      kVarInt62OneByte + 0x0c,
+      // data
+      'h',  'e',  'l',  'l',
+      'o',  ' ',  'w',  'o',
+      'r',  'l',  'd',  '!',
+    // second coalesced packet
+      // public flags (long header with packet type ZERO_RTT_PROTECTED and
+      // 4-byte packet number)
+      0xD3,
+      // version
+      QUIC_VERSION_BYTES,
+      // destination connection ID length
+      0x50,
+      // destination connection ID
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // long header packet length
+      0x1E,
+      // packet number
+      0x12, 0x34, 0x56, 0x79,
+      // frame type (IETF_STREAM frame with FIN, LEN, and OFFSET bits set)
+      0x08 | 0x01 | 0x02 | 0x04,
+      // stream id
+      kVarInt62FourBytes + 0x00, 0x02, 0x03, 0x04,
+      // offset
+      kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+      0x32, 0x10, 0x76, 0x54,
+      // data length
+      kVarInt62OneByte + 0x0c,
+      // data
+      'H',  'E',  'L',  'L',
+      'O',  '_',  'W',  'O',
+      'R',  'L',  'D',  '?',
+  };
+  // clang-format on
+
+  QuicEncryptedPacket encrypted(AsChars(packet), QUIC_ARRAYSIZE(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+
+  ASSERT_EQ(1u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+
+  // Stream ID should be the last 3 bytes of kStreamId.
+  EXPECT_EQ(0x00FFFFFF & kStreamId, visitor_.stream_frames_[0]->stream_id);
+  EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+  EXPECT_EQ(kStreamOffset, visitor_.stream_frames_[0]->offset);
+  CheckStreamFrameData("hello world!", visitor_.stream_frames_[0].get());
+
+  ASSERT_EQ(visitor_.coalesced_packets_.size(), 1u);
+  EXPECT_TRUE(framer_.ProcessPacket(*visitor_.coalesced_packets_[0].get()));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+
+  ASSERT_EQ(2u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+
+  // Stream ID should be the last 3 bytes of kStreamId.
+  EXPECT_EQ(0x00FFFFFF & kStreamId, visitor_.stream_frames_[1]->stream_id);
+  EXPECT_TRUE(visitor_.stream_frames_[1]->fin);
+  EXPECT_EQ(kStreamOffset, visitor_.stream_frames_[1]->offset);
+  CheckStreamFrameData("HELLO_WORLD?", visitor_.stream_frames_[1].get());
+}
+
+TEST_P(QuicFramerTest, MismatchedCoalescedPacket) {
+  if (!QuicVersionHasLongHeaderLengths(framer_.transport_version())) {
+    return;
+  }
+  // clang-format off
+  unsigned char packet[] = {
+    // first coalesced packet
+      // public flags (long header with packet type ZERO_RTT_PROTECTED and
+      // 4-byte packet number)
+      0xD3,
+      // version
+      QUIC_VERSION_BYTES,
+      // destination connection ID length
+      0x50,
+      // destination connection ID
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // long header packet length
+      0x1E,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+      // frame type (IETF_STREAM frame with FIN, LEN, and OFFSET bits set)
+      0x08 | 0x01 | 0x02 | 0x04,
+      // stream id
+      kVarInt62FourBytes + 0x00, 0x02, 0x03, 0x04,
+      // offset
+      kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+      0x32, 0x10, 0x76, 0x54,
+      // data length
+      kVarInt62OneByte + 0x0c,
+      // data
+      'h',  'e',  'l',  'l',
+      'o',  ' ',  'w',  'o',
+      'r',  'l',  'd',  '!',
+    // second coalesced packet
+      // public flags (long header with packet type ZERO_RTT_PROTECTED and
+      // 4-byte packet number)
+      0xD3,
+      // version
+      QUIC_VERSION_BYTES,
+      // destination connection ID length
+      0x50,
+      // destination connection ID
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x11,
+      // long header packet length
+      0x1E,
+      // packet number
+      0x12, 0x34, 0x56, 0x79,
+      // frame type (IETF_STREAM frame with FIN, LEN, and OFFSET bits set)
+      0x08 | 0x01 | 0x02 | 0x04,
+      // stream id
+      kVarInt62FourBytes + 0x00, 0x02, 0x03, 0x04,
+      // offset
+      kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+      0x32, 0x10, 0x76, 0x54,
+      // data length
+      kVarInt62OneByte + 0x0c,
+      // data
+      'H',  'E',  'L',  'L',
+      'O',  '_',  'W',  'O',
+      'R',  'L',  'D',  '?',
+  };
+  // clang-format on
+
+  QuicEncryptedPacket encrypted(AsChars(packet), QUIC_ARRAYSIZE(packet), false);
+  EXPECT_QUIC_PEER_BUG(EXPECT_TRUE(framer_.ProcessPacket(encrypted)),
+                       "Server: Received mismatched coalesced header.*");
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+
+  ASSERT_EQ(1u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+
+  // Stream ID should be the last 3 bytes of kStreamId.
+  EXPECT_EQ(0x00FFFFFF & kStreamId, visitor_.stream_frames_[0]->stream_id);
+  EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+  EXPECT_EQ(kStreamOffset, visitor_.stream_frames_[0]->offset);
+  CheckStreamFrameData("hello world!", visitor_.stream_frames_[0].get());
+
+  ASSERT_EQ(visitor_.coalesced_packets_.size(), 0u);
+}
+
+TEST_P(QuicFramerTest, InvalidCoalescedPacket) {
+  if (!QuicVersionHasLongHeaderLengths(framer_.transport_version())) {
+    return;
+  }
+  // clang-format off
+  unsigned char packet[] = {
+    // first coalesced packet
+      // public flags (long header with packet type ZERO_RTT_PROTECTED and
+      // 4-byte packet number)
+      0xD3,
+      // version
+      QUIC_VERSION_BYTES,
+      // destination connection ID length
+      0x50,
+      // destination connection ID
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // long header packet length
+      0x1E,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+      // frame type (IETF_STREAM frame with FIN, LEN, and OFFSET bits set)
+      0x08 | 0x01 | 0x02 | 0x04,
+      // stream id
+      kVarInt62FourBytes + 0x00, 0x02, 0x03, 0x04,
+      // offset
+      kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+      0x32, 0x10, 0x76, 0x54,
+      // data length
+      kVarInt62OneByte + 0x0c,
+      // data
+      'h',  'e',  'l',  'l',
+      'o',  ' ',  'w',  'o',
+      'r',  'l',  'd',  '!',
+    // second coalesced packet
+      // public flags (long header with packet type ZERO_RTT_PROTECTED and
+      // 4-byte packet number)
+      0xD3,
+      // version would be here but we cut off the invalid coalesced header.
+  };
+  // clang-format on
+
+  QuicEncryptedPacket encrypted(AsChars(packet), QUIC_ARRAYSIZE(packet), false);
+  EXPECT_QUIC_PEER_BUG(EXPECT_TRUE(framer_.ProcessPacket(encrypted)),
+                       "Server: Failed to parse received coalesced header.*");
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+
+  ASSERT_EQ(1u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+
+  // Stream ID should be the last 3 bytes of kStreamId.
+  EXPECT_EQ(0x00FFFFFF & kStreamId, visitor_.stream_frames_[0]->stream_id);
+  EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+  EXPECT_EQ(kStreamOffset, visitor_.stream_frames_[0]->offset);
+  CheckStreamFrameData("hello world!", visitor_.stream_frames_[0].get());
+
+  ASSERT_EQ(visitor_.coalesced_packets_.size(), 0u);
+}
+
+TEST_P(QuicFramerTest, PacketHeaderWithVariableLengthConnectionId) {
+  if (framer_.transport_version() < QUIC_VERSION_46) {
+    return;
+  }
+  char connection_id_bytes[9] = {0xFE, 0xDC, 0xBA, 0x98, 0x76,
+                                 0x54, 0x32, 0x10, 0x42};
+  QuicConnectionId connection_id(connection_id_bytes,
+                                 sizeof(connection_id_bytes));
+  QuicFramerPeer::SetLargestPacketNumber(&framer_, kPacketNumber - 2);
+  QuicFramerPeer::SetExpectedConnectionIDLength(&framer_,
+                                                connection_id.length());
+
+  // clang-format off
+  PacketFragments packet = {
+      // type (8 byte connection_id and 1 byte packet number)
+      {"Unable to read type.",
+       {0x40}},
+      // connection_id
+      {"Unable to read Destination ConnectionId.",
+       {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10, 0x42}},
+      // packet number
+      {"Unable to read packet number.",
+       {0x78}},
+  };
+  // clang-format on
+
+  std::unique_ptr<QuicEncryptedPacket> encrypted(
+      AssemblePacketFromFragments(packet));
+  EXPECT_FALSE(framer_.ProcessPacket(*encrypted));
+  EXPECT_EQ(QUIC_MISSING_PAYLOAD, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(connection_id, visitor_.header_->destination_connection_id);
+  EXPECT_FALSE(visitor_.header_->reset_flag);
+  EXPECT_FALSE(visitor_.header_->version_flag);
+  EXPECT_EQ(PACKET_1BYTE_PACKET_NUMBER, visitor_.header_->packet_number_length);
+  EXPECT_EQ(kPacketNumber, visitor_.header_->packet_number);
+
+  CheckFramingBoundaries(packet, QUIC_INVALID_PACKET_HEADER);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic