You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by zw...@apache.org on 2020/08/13 20:27:08 UTC

[trafficserver] branch 9.0.x updated: Squashed commit of the following: (#7110)

This is an automated email from the ASF dual-hosted git repository.

zwoop pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/9.0.x by this push:
     new 7105dcd  Squashed commit of the following: (#7110)
7105dcd is described below

commit 7105dcdb8aa8b7be15788d89cd929d1c601d7db8
Author: Masakazu Kitajo <ma...@apache.org>
AuthorDate: Fri Aug 14 05:25:44 2020 +0900

    Squashed commit of the following: (#7110)
    
    Merges -29 patches from QUIC branch to master, for inclusion with ATS 9.0.x release.
    
    (cherry picked from commit e6bd4d13d3f8cff1af8e869f02f5fc4260d3cf4a)
---
 doc/admin-guide/files/records.config.en.rst        |   4 +-
 include/ts/apidefs.h.in                            |   2 +
 include/tscore/ink_inet.h                          |   2 +
 iocore/net/P_QUICNetVConnection.h                  |  35 +-
 iocore/net/P_QUICPacketHandler.h                   |   2 +-
 iocore/net/QUICNetProcessor.cc                     |   2 +-
 iocore/net/QUICNetVConnection.cc                   | 181 +++++---
 iocore/net/QUICPacketHandler.cc                    |  85 ++--
 iocore/net/quic/Makefile.am                        |   2 +-
 iocore/net/quic/Mock.h                             | 107 ++++-
 iocore/net/quic/QUICAckFrameCreator.cc             |   8 +-
 iocore/net/quic/QUICAckFrameCreator.h              |   2 +-
 iocore/net/quic/QUICAddrVerifyState.cc             |   2 +-
 iocore/net/quic/QUICAddrVerifyState.h              |   2 +-
 iocore/net/quic/QUICBidirectionalStream.cc         |   4 +-
 iocore/net/quic/QUICConfig.cc                      |  20 +-
 iocore/net/quic/QUICConfig.h                       |   5 +-
 iocore/net/quic/QUICCongestionController.h         |  38 +-
 iocore/net/quic/QUICConnection.h                   |  24 +-
 iocore/net/quic/QUICContext.cc                     |   8 +-
 iocore/net/quic/QUICContext.h                      |   5 +-
 iocore/net/quic/QUICDebugNames.cc                  |  28 +-
 iocore/net/quic/QUICFrame.cc                       |  12 -
 iocore/net/quic/QUICFrame.h                        |  14 -
 iocore/net/quic/QUICFrameDispatcher.cc             |   8 +-
 iocore/net/quic/QUICHandshake.cc                   |  76 ++-
 iocore/net/quic/QUICHandshake.h                    |  13 +-
 iocore/net/quic/QUICHandshakeProtocol.h            |   2 +-
 iocore/net/quic/QUICKeyGenerator.cc                |  32 +-
 iocore/net/quic/QUICKeyGenerator.h                 |   6 +-
 iocore/net/quic/QUICLossDetector.cc                | 507 +++++++++++----------
 iocore/net/quic/QUICLossDetector.h                 | 144 ++----
 iocore/net/quic/QUICNewRenoCongestionController.cc | 173 +++----
 iocore/net/quic/QUICNewRenoCongestionController.h  |  81 ++++
 iocore/net/quic/QUICPacket.cc                      |  21 +-
 iocore/net/quic/QUICPacket.h                       |   4 +-
 iocore/net/quic/QUICPacketFactory.cc               |  15 +-
 iocore/net/quic/QUICPacketFactory.h                |   6 +-
 iocore/net/quic/QUICRetryIntegrityTag.cc           |  21 +-
 iocore/net/quic/QUICRetryIntegrityTag.h            |  16 +-
 iocore/net/quic/QUICStream.h                       |   3 +
 iocore/net/quic/QUICTLS.cc                         |   6 +-
 iocore/net/quic/QUICTLS.h                          |   2 +-
 iocore/net/quic/QUICTLS_boringssl.cc               |  15 +-
 iocore/net/quic/QUICTLS_openssl.cc                 |   8 +-
 iocore/net/quic/QUICTransportParameters.cc         | 105 +++--
 iocore/net/quic/QUICTransportParameters.h          |  24 +-
 iocore/net/quic/QUICTypes.cc                       |  48 +-
 iocore/net/quic/QUICTypes.h                        |  74 ++-
 iocore/net/quic/qlog/QLogListener.h                |   2 +-
 iocore/net/quic/test/test_QUICAckFrameCreator.cc   |   2 +-
 iocore/net/quic/test/test_QUICHandshakeProtocol.cc |  36 +-
 iocore/net/quic/test/test_QUICKeyGenerator.cc      |  14 +-
 iocore/net/quic/test/test_QUICLossDetector.cc      | 183 ++++----
 iocore/net/quic/test/test_QUICPacket.cc            |  27 +-
 iocore/net/quic/test/test_QUICPacketFactory.cc     |  11 +-
 .../quic/test/test_QUICPacketHeaderProtector.cc    |  18 +-
 iocore/net/quic/test/test_QUICStreamManager.cc     |  16 +-
 .../net/quic/test/test_QUICTransportParameters.cc  |  24 +-
 iocore/net/quic/test/test_QUICType.cc              |  14 +-
 iocore/net/quic/test/test_QUICVersionNegotiator.cc |  19 +-
 lib/records/RecHttp.cc                             |  75 +--
 mgmt/RecordsConfig.cc                              |   4 +-
 proxy/http/HttpProxyServerMain.cc                  |   6 +
 proxy/http3/Http3Session.cc                        |  66 ++-
 proxy/http3/Http3Session.h                         |  10 +-
 proxy/http3/Http3SessionAccept.cc                  |   8 +-
 src/traffic_quic/quic_client.cc                    |   4 +-
 src/tscore/ink_inet.cc                             |   8 +-
 69 files changed, 1551 insertions(+), 1000 deletions(-)

diff --git a/doc/admin-guide/files/records.config.en.rst b/doc/admin-guide/files/records.config.en.rst
index f964497..1a1c0b4 100644
--- a/doc/admin-guide/files/records.config.en.rst
+++ b/doc/admin-guide/files/records.config.en.rst
@@ -4064,13 +4064,13 @@ removed in the future without prior notice.
    This is just for debugging. Do not change it from the default value unless
    you really understand what this is.
 
-.. ts:cv:: CONFIG proxy.config.quic.congestion_control.initial_window_scale INT 10
+.. ts:cv:: CONFIG proxy.config.quic.congestion_control.initial_window INT 12000
    :reloadable:
 
    This is just for debugging. Do not change it from the default value unless
    you really understand what this is.
 
-.. ts:cv:: CONFIG proxy.config.quic.congestion_control.minimum_window_scale INT 2
+.. ts:cv:: CONFIG proxy.config.quic.congestion_control.minimum_window INT 2400
    :reloadable:
 
    This is just for debugging. Do not change it from the default value unless
diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in
index b2bf281..5707335 100644
--- a/include/ts/apidefs.h.in
+++ b/include/ts/apidefs.h.in
@@ -1232,7 +1232,9 @@ extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_1_0;
 extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_1_1;
 extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_2_0;
 extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_3;
+extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_3_D27;
 extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_QUIC;
+extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_QUIC_D27;
 
 extern tsapi int TS_ALPN_PROTOCOL_INDEX_HTTP_0_9;
 extern tsapi int TS_ALPN_PROTOCOL_INDEX_HTTP_1_0;
diff --git a/include/tscore/ink_inet.h b/include/tscore/ink_inet.h
index efd67c0..8c9ccaf 100644
--- a/include/tscore/ink_inet.h
+++ b/include/tscore/ink_inet.h
@@ -61,6 +61,8 @@ extern const std::string_view IP_PROTO_TAG_HTTP_1_1;
 extern const std::string_view IP_PROTO_TAG_HTTP_2_0;
 extern const std::string_view IP_PROTO_TAG_HTTP_QUIC;
 extern const std::string_view IP_PROTO_TAG_HTTP_3;
+extern const std::string_view IP_PROTO_TAG_HTTP_QUIC_D27;
+extern const std::string_view IP_PROTO_TAG_HTTP_3_D27;
 
 struct IpAddr; // forward declare.
 
diff --git a/iocore/net/P_QUICNetVConnection.h b/iocore/net/P_QUICNetVConnection.h
index 484ea45..f04f32d 100644
--- a/iocore/net/P_QUICNetVConnection.h
+++ b/iocore/net/P_QUICNetVConnection.h
@@ -147,10 +147,11 @@ class QUICNetVConnection : public UnixNetVConnection,
 public:
   QUICNetVConnection();
   ~QUICNetVConnection();
-  void init(QUICConnectionId peer_cid, QUICConnectionId original_cid, UDPConnection *, QUICPacketHandler *,
+  void init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, UDPConnection *, QUICPacketHandler *,
             QUICResetTokenTable *rtable);
-  void init(QUICConnectionId peer_cid, QUICConnectionId original_cid, QUICConnectionId first_cid, UDPConnection *,
-            QUICPacketHandler *, QUICResetTokenTable *rtable, QUICConnectionTable *ctable);
+  void init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, QUICConnectionId first_cid,
+            QUICConnectionId retry_cid, UDPConnection *, QUICPacketHandler *, QUICResetTokenTable *rtable,
+            QUICConnectionTable *ctable);
 
   // accept new conn_id
   int acceptEvent(int event, Event *e);
@@ -199,13 +200,20 @@ public:
   QUICConnectionId peer_connection_id() const override;
   QUICConnectionId original_connection_id() const override;
   QUICConnectionId first_connection_id() const override;
+  QUICConnectionId retry_source_connection_id() const override;
+  QUICConnectionId initial_source_connection_id() const override;
   QUICConnectionId connection_id() const override;
   std::string_view cids() const override;
   const QUICFiveTuple five_tuple() const override;
   uint32_t pmtu() const override;
   NetVConnectionContext_t direction() const override;
+  QUICVersion negotiated_version() const override;
   std::string_view negotiated_application_name() const override;
   bool is_closed() const override;
+  bool is_at_anti_amplification_limit() const override;
+  bool is_address_validation_completed() const override;
+  bool is_handshake_completed() const override;
+  bool has_keys_for(QUICPacketNumberSpace space) const override;
 
   // QUICConnection (QUICFrameHandler)
   std::vector<QUICFrameType> interests() override;
@@ -226,17 +234,20 @@ private:
 
   QUICConfig::scoped_config _quic_config;
 
-  QUICConnectionId _peer_quic_connection_id;     // dst cid in local
-  QUICConnectionId _peer_old_quic_connection_id; // dst previous cid in local
-  QUICConnectionId _original_quic_connection_id; // dst cid of initial packet from client
-  QUICConnectionId _first_quic_connection_id;    // dst cid of initial packet from client that doesn't have retry token
-  QUICConnectionId _quic_connection_id;          // src cid in local
+  QUICConnectionId _peer_quic_connection_id;      // dst cid in local
+  QUICConnectionId _peer_old_quic_connection_id;  // dst previous cid in local
+  QUICConnectionId _original_quic_connection_id;  // dst cid of initial packet from client
+  QUICConnectionId _first_quic_connection_id;     // dst cid of initial packet from client that doesn't have retry token
+  QUICConnectionId _retry_source_connection_id;   // src cid used for sending Retry packet
+  QUICConnectionId _initial_source_connection_id; // src cid used for Initial packet
+  QUICConnectionId _quic_connection_id;           // src cid in local
   QUICFiveTuple _five_tuple;
   bool _connection_migration_initiated = false;
 
   char _cids_data[MAX_CIDS_SIZE] = {0};
   std::string_view _cids;
 
+  QUICVersion _initial_version;
   UDPConnection *_udp_con = nullptr;
   QUICPacketProtectionKeyInfo _pp_key_info;
   QUICPacketHandler *_packet_handler = nullptr;
@@ -277,6 +288,8 @@ private:
   uint32_t _state_closing_recv_packet_window = 1;
   uint64_t _flow_control_buffer_size         = 1024;
 
+  void _init_submodules();
+
   void _schedule_packet_write_ready(bool delay = false);
   void _unschedule_packet_write_ready();
   void _close_packet_write_ready(Event *data);
@@ -304,9 +317,9 @@ private:
   uint64_t _maximum_stream_frame_data_size();
 
   Ptr<IOBufferBlock> _store_frame(Ptr<IOBufferBlock> parent_block, size_t &size_added, uint64_t &max_frame_size, QUICFrame &frame,
-                                  std::vector<QUICFrameInfo> &frames);
+                                  std::vector<QUICSentPacketInfo::FrameInfo> &frames);
   QUICPacketUPtr _packetize_frames(uint8_t *packet_buf, QUICEncryptionLevel level, uint64_t max_packet_size,
-                                   std::vector<QUICFrameInfo> &frames);
+                                   std::vector<QUICSentPacketInfo::FrameInfo> &frames);
   void _packetize_closing_frame();
   QUICPacketUPtr _build_packet(uint8_t *packet_buf, QUICEncryptionLevel level, const Ptr<IOBufferBlock> &parent_block,
                                bool retransmittable, bool probing, bool crypto);
@@ -337,7 +350,9 @@ private:
   QUICPacketUPtr _dequeue_recv_packet(uint8_t *packet_buf, QUICPacketCreationResult &result);
   void _validate_new_path(const QUICPath &path);
 
+  bool _handshake_completed = false;
   int _complete_handshake_if_possible();
+
   void _switch_to_handshake_state();
   void _switch_to_established_state();
   void _switch_to_closing_state(QUICConnectionErrorUPtr error);
diff --git a/iocore/net/P_QUICPacketHandler.h b/iocore/net/P_QUICPacketHandler.h
index 0eaae22..9428b89 100644
--- a/iocore/net/P_QUICPacketHandler.h
+++ b/iocore/net/P_QUICPacketHandler.h
@@ -87,7 +87,7 @@ protected:
 private:
   void _recv_packet(int event, UDPPacket *udp_packet) override;
   int _stateless_retry(const uint8_t *buf, uint64_t buf_len, UDPConnection *connection, IpEndpoint from, QUICConnectionId dcid,
-                       QUICConnectionId scid, QUICConnectionId *original_cid);
+                       QUICConnectionId scid, QUICConnectionId *original_cid, QUICConnectionId *retry_cid, QUICVersion version);
   bool _send_stateless_reset(QUICConnectionId dcid, uint32_t instance_id, UDPConnection *udp_con, IpEndpoint &addr,
                              size_t maximum_size);
   void _send_invalid_token_error(const uint8_t *initial_packet, uint64_t initial_packet_len, UDPConnection *connection,
diff --git a/iocore/net/QUICNetProcessor.cc b/iocore/net/QUICNetProcessor.cc
index 2075e81..5bdaa2b 100644
--- a/iocore/net/QUICNetProcessor.cc
+++ b/iocore/net/QUICNetProcessor.cc
@@ -150,7 +150,7 @@ QUICNetProcessor::connect_re(Continuation *cont, sockaddr const *remote_addr, Ne
   QUICConnectionId client_dst_cid;
   client_dst_cid.randomize();
   // vc->init set handler of vc `QUICNetVConnection::startEvent`
-  vc->init(client_dst_cid, client_dst_cid, con, packet_handler, this->_rtable);
+  vc->init(QUIC_SUPPORTED_VERSIONS[0], client_dst_cid, client_dst_cid, con, packet_handler, this->_rtable);
   packet_handler->init(vc);
 
   // Connection ID will be changed
diff --git a/iocore/net/QUICNetVConnection.cc b/iocore/net/QUICNetVConnection.cc
index faf052f..ba8cf74 100644
--- a/iocore/net/QUICNetVConnection.cc
+++ b/iocore/net/QUICNetVConnection.cc
@@ -37,6 +37,8 @@
 #include "QUICMultiCertConfigLoader.h"
 #include "QUICTLS.h"
 
+#include "QUICNewRenoCongestionController.h"
+
 #include "QUICStats.h"
 #include "QUICGlobals.h"
 #include "QUICDebugNames.h"
@@ -57,6 +59,7 @@ static constexpr uint8_t QUANTUM_TEST_VALUE[1200] = {'Q'};
 #define QUICConVVVDebug(fmt, ...) Debug("vvv_quic_net", "[%s] " fmt, this->cids().data(), ##__VA_ARGS__)
 
 #define QUICFCDebug(fmt, ...) Debug("quic_flow_ctrl", "[%s] " fmt, this->cids().data(), ##__VA_ARGS__)
+#define QUICFCVDebug(fmt, ...) Debug("v_quic_flow_ctrl", "[%s] " fmt, this->cids().data(), ##__VA_ARGS__)
 
 #define QUICError(fmt, ...)                                           \
   Debug("quic_net", "[%s] " fmt, this->cids().data(), ##__VA_ARGS__); \
@@ -236,37 +239,44 @@ QUICNetVConnection::~QUICNetVConnection()
 // XXX This might be called on ET_UDP thread
 // Initialize QUICNetVC for out going connection (NET_VCONNECTION_OUT)
 void
-QUICNetVConnection::init(QUICConnectionId peer_cid, QUICConnectionId original_cid, UDPConnection *udp_con,
+QUICNetVConnection::init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, UDPConnection *udp_con,
                          QUICPacketHandler *packet_handler, QUICResetTokenTable *rtable)
 {
   SET_HANDLER((NetVConnHandler)&QUICNetVConnection::startEvent);
+  this->_initial_version             = version;
   this->_udp_con                     = udp_con;
   this->_packet_handler              = packet_handler;
   this->_peer_quic_connection_id     = peer_cid;
   this->_original_quic_connection_id = original_cid;
   this->_rtable                      = rtable;
   this->_quic_connection_id.randomize();
+  this->_initial_source_connection_id = this->_quic_connection_id;
 
   this->_update_cids();
 
   if (is_debug_tag_set(QUIC_DEBUG_TAG.data())) {
     QUICConDebug("dcid=%s scid=%s", this->_peer_quic_connection_id.hex().c_str(), this->_quic_connection_id.hex().c_str());
   }
+
+  this->_init_submodules();
 }
 
 // Initialize QUICNetVC for in coming connection (NET_VCONNECTION_IN)
 void
-QUICNetVConnection::init(QUICConnectionId peer_cid, QUICConnectionId original_cid, QUICConnectionId first_cid,
-                         UDPConnection *udp_con, QUICPacketHandler *packet_handler, QUICResetTokenTable *rtable,
-                         QUICConnectionTable *ctable)
+QUICNetVConnection::init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, QUICConnectionId first_cid,
+                         QUICConnectionId retry_cid, UDPConnection *udp_con, QUICPacketHandler *packet_handler,
+                         QUICResetTokenTable *rtable, QUICConnectionTable *ctable)
 {
   SET_HANDLER((NetVConnHandler)&QUICNetVConnection::acceptEvent);
+  this->_initial_version             = version;
   this->_udp_con                     = udp_con;
   this->_packet_handler              = packet_handler;
   this->_peer_quic_connection_id     = peer_cid;
   this->_original_quic_connection_id = original_cid;
   this->_first_quic_connection_id    = first_cid;
+  this->_retry_source_connection_id  = retry_cid;
   this->_quic_connection_id.randomize();
+  this->_initial_source_connection_id = this->_quic_connection_id;
 
   if (ctable) {
     this->_ctable = ctable;
@@ -280,6 +290,30 @@ QUICNetVConnection::init(QUICConnectionId peer_cid, QUICConnectionId original_ci
   if (is_debug_tag_set(QUIC_DEBUG_TAG.data())) {
     QUICConDebug("dcid=%s scid=%s", this->_peer_quic_connection_id.hex().c_str(), this->_quic_connection_id.hex().c_str());
   }
+
+  this->_init_submodules();
+}
+
+void
+QUICNetVConnection::_init_submodules()
+{
+  this->_pinger                = new QUICPinger();
+  this->_padder                = new QUICPadder(this->netvc_context);
+  this->_path_validator        = new QUICPathValidator(*this, [this](bool succeeded) {
+    if (succeeded) {
+      this->_alt_con_manager->drop_cid(this->_peer_old_quic_connection_id);
+      // FIXME This is a kind of workaround for connection migration.
+      // This PING make peer to send an ACK frame so that ATS can detect packet loss.
+      // It would be better if QUICLossDetector could detect the loss in another way.
+      this->ping();
+    }
+  });
+  this->_path_manager          = new QUICPathManagerImpl(*this, *this->_path_validator);
+  this->_context               = std::make_unique<QUICContext>(&this->_rtt_measure, this, &this->_pp_key_info, this->_path_manager);
+  this->_congestion_controller = new QUICNewRenoCongestionController(*_context);
+  this->_rtt_measure.init(this->_context->ld_config());
+  this->_loss_detector =
+    new QUICLossDetector(*_context, this->_congestion_controller, &this->_rtt_measure, this->_pinger, this->_padder);
 }
 
 bool
@@ -379,17 +413,6 @@ void
 QUICNetVConnection::start()
 {
   ink_release_assert(this->thread != nullptr);
-  this->_path_validator = new QUICPathValidator(*this, [this](bool succeeded) {
-    if (succeeded) {
-      this->_alt_con_manager->drop_cid(this->_peer_old_quic_connection_id);
-      // FIXME This is a kind of workaround for connection migration.
-      // This PING make peer to send an ACK frame so that ATS can detect packet loss.
-      // It would be better if QUICLossDetector could detect the loss in another way.
-      this->ping();
-    }
-  });
-  this->_path_manager   = new QUICPathManagerImpl(*this, *this->_path_validator);
-  this->_context        = std::make_unique<QUICContext>(&this->_rtt_measure, this, &this->_pp_key_info, this->_path_manager);
   this->_five_tuple.update(this->local_addr, this->remote_addr, SOCK_DGRAM);
   QUICPath trusted_path = {{}, {}};
   // Version 0x00000001 uses stream 0 for cryptographic handshake with TLS 1.3, but newer version may not
@@ -398,10 +421,10 @@ QUICNetVConnection::start()
 
     this->_pp_key_info.set_context(QUICPacketProtectionKeyInfo::Context::SERVER);
     this->_ack_frame_manager.set_ack_delay_exponent(this->_quic_config->ack_delay_exponent_in());
-    this->_reset_token = QUICStatelessResetToken(this->_quic_connection_id, this->_quic_config->instance_id());
-    this->_hs_protocol = this->_setup_handshake_protocol(server_cert->ssl_default);
-    this->_handshake_handler =
-      new QUICHandshake(this, this->_hs_protocol, this->_reset_token, this->_quic_config->stateless_retry());
+    this->_reset_token       = QUICStatelessResetToken(this->_quic_connection_id, this->_quic_config->instance_id());
+    this->_hs_protocol       = this->_setup_handshake_protocol(server_cert->ssl_default);
+    this->_handshake_handler = new QUICHandshake(this->_initial_version, this, this->_hs_protocol, this->_reset_token,
+                                                 this->_quic_config->stateless_retry());
     this->_ack_frame_manager.set_max_ack_delay(this->_quic_config->max_ack_delay_in());
     this->_schedule_ack_manager_periodic(this->_quic_config->max_ack_delay_in());
   } else {
@@ -413,7 +436,7 @@ QUICNetVConnection::start()
     this->_pp_key_info.set_context(QUICPacketProtectionKeyInfo::Context::CLIENT);
     this->_ack_frame_manager.set_ack_delay_exponent(this->_quic_config->ack_delay_exponent_out());
     this->_hs_protocol       = this->_setup_handshake_protocol(this->_quic_config->client_ssl_ctx());
-    this->_handshake_handler = new QUICHandshake(this, this->_hs_protocol);
+    this->_handshake_handler = new QUICHandshake(this->_initial_version, this, this->_hs_protocol);
     this->_handshake_handler->start(tp_config, &this->_packet_factory, this->_quic_config->vn_exercise_enabled());
     this->_handshake_handler->do_handshake();
     this->_ack_frame_manager.set_max_ack_delay(this->_quic_config->max_ack_delay_out());
@@ -426,12 +449,6 @@ QUICNetVConnection::start()
   this->_frame_dispatcher = new QUICFrameDispatcher(this);
 
   // Create frame handlers
-  this->_pinger = new QUICPinger();
-  this->_padder = new QUICPadder(this->netvc_context);
-  this->_rtt_measure.init(this->_context->ld_config());
-  this->_congestion_controller = new QUICNewRenoCongestionController(*_context);
-  this->_loss_detector =
-    new QUICLossDetector(*_context, this->_congestion_controller, &this->_rtt_measure, this->_pinger, this->_padder);
   this->_frame_dispatcher->add_handler(this->_loss_detector);
 
   this->_remote_flow_controller = new QUICRemoteConnectionFlowController(UINT64_MAX);
@@ -597,6 +614,18 @@ QUICNetVConnection::first_connection_id() const
 }
 
 QUICConnectionId
+QUICNetVConnection::retry_source_connection_id() const
+{
+  return this->_retry_source_connection_id;
+}
+
+QUICConnectionId
+QUICNetVConnection::initial_source_connection_id() const
+{
+  return this->_initial_source_connection_id;
+}
+
+QUICConnectionId
 QUICNetVConnection::connection_id() const
 {
   return this->_quic_connection_id;
@@ -671,6 +700,7 @@ void
 QUICNetVConnection::handle_received_packet(UDPPacket *packet)
 {
   this->_packet_recv_queue.enqueue(packet);
+  this->_loss_detector->on_datagram_received();
 }
 
 void
@@ -1077,6 +1107,44 @@ QUICNetVConnection::is_closed() const
   return this->handler == reinterpret_cast<NetVConnHandler>(&QUICNetVConnection::state_connection_closed);
 }
 
+bool
+QUICNetVConnection::is_at_anti_amplification_limit() const
+{
+  return !this->_verified_state.is_verified() || this->_verified_state.windows() == 0;
+}
+
+bool
+QUICNetVConnection::is_address_validation_completed() const
+{
+  return this->_verified_state.is_verified();
+}
+
+bool
+QUICNetVConnection::is_handshake_completed() const
+{
+  return this->_handshake_completed;
+}
+
+bool
+QUICNetVConnection::has_keys_for(QUICPacketNumberSpace space) const
+{
+  switch (space) {
+  case QUICPacketNumberSpace::INITIAL:
+    return this->_pp_key_info.is_decryption_key_available(QUICKeyPhase::INITIAL) &&
+           this->_pp_key_info.is_encryption_key_available(QUICKeyPhase::INITIAL);
+  case QUICPacketNumberSpace::HANDSHAKE:
+    return this->_pp_key_info.is_decryption_key_available(QUICKeyPhase::HANDSHAKE) &&
+           this->_pp_key_info.is_encryption_key_available(QUICKeyPhase::HANDSHAKE);
+  case QUICPacketNumberSpace::APPLICATION_DATA:
+    return (this->_pp_key_info.is_decryption_key_available(QUICKeyPhase::PHASE_0) &&
+            this->_pp_key_info.is_encryption_key_available(QUICKeyPhase::PHASE_0)) ||
+           (this->_pp_key_info.is_decryption_key_available(QUICKeyPhase::PHASE_1) &&
+            this->_pp_key_info.is_encryption_key_available(QUICKeyPhase::PHASE_1));
+  default:
+    return false;
+  }
+}
+
 QUICPacketNumber
 QUICNetVConnection::_largest_acked_packet_number(QUICEncryptionLevel level) const
 {
@@ -1085,6 +1153,12 @@ QUICNetVConnection::_largest_acked_packet_number(QUICEncryptionLevel level) cons
   return this->_loss_detector->largest_acked_packet_number(index);
 }
 
+QUICVersion
+QUICNetVConnection::negotiated_version() const
+{
+  return this->_handshake_handler->negotiated_version();
+}
+
 std::string_view
 QUICNetVConnection::negotiated_application_name() const
 {
@@ -1114,6 +1188,7 @@ QUICNetVConnection::_state_handshake_process_packet(const QUICPacket &packet)
     error = this->_state_handshake_process_handshake_packet(static_cast<const QUICHandshakePacketR &>(packet));
     if (this->_pp_key_info.is_decryption_key_available(QUICKeyPhase::INITIAL) && this->netvc_context == NET_VCONNECTION_IN) {
       this->_pp_key_info.drop_keys(QUICKeyPhase::INITIAL);
+      this->_loss_detector->on_packet_number_space_discarded(QUICPacketNumberSpace::INITIAL);
       this->_minimum_encryption_level = QUICEncryptionLevel::HANDSHAKE;
     }
     break;
@@ -1208,6 +1283,8 @@ QUICNetVConnection::_state_handshake_process_initial_packet(const QUICInitialPac
       this->_frame_dispatcher->add_handler(this->_alt_con_manager);
     }
 
+    this->_handshake_handler->update(packet);
+
     // on client side, _handshake_handler is already started. Just process packet like _state_handshake_process_handshake_packet()
     error = this->_recv_and_ack(packet);
   }
@@ -1246,6 +1323,7 @@ QUICNetVConnection::_state_handshake_process_retry_packet(const QUICRetryPacketR
 
   // discard all transport state
   this->_handshake_handler->reset();
+  this->_handshake_handler->update(packet);
   this->_packet_factory.reset();
   this->_loss_detector->reset();
 
@@ -1254,7 +1332,7 @@ QUICNetVConnection::_state_handshake_process_retry_packet(const QUICRetryPacketR
 
   // Initialize Key Materials with peer CID. Because peer CID is DCID of (second) INITIAL packet from client which reply to RETRY
   // packet from server
-  this->_hs_protocol->initialize_key_materials(this->_peer_quic_connection_id);
+  this->_hs_protocol->initialize_key_materials(this->_peer_quic_connection_id, packet.version());
 
   // start handshake over
   this->_handshake_handler->do_handshake();
@@ -1447,8 +1525,8 @@ QUICNetVConnection::_state_common_send_packet()
         max_packet_size = std::min(max_packet_size, this->_verified_state.windows());
       }
 
-      QUICPacketInfoUPtr packet_info = std::make_unique<QUICPacketInfo>();
-      QUICPacketUPtr packet          = this->_packetize_frames(packet_buf, level, max_packet_size, packet_info->frames);
+      QUICSentPacketInfoUPtr packet_info = std::make_unique<QUICSentPacketInfo>();
+      QUICPacketUPtr packet              = this->_packetize_frames(packet_buf, level, max_packet_size, packet_info->frames);
 
       if (packet) {
         // trigger callback
@@ -1457,12 +1535,7 @@ QUICNetVConnection::_state_common_send_packet()
         packet_info->packet_number = packet->packet_number();
         packet_info->time_sent     = Thread::get_hrtime();
         packet_info->ack_eliciting = packet->is_ack_eliciting();
-        if (packet->type() == QUICPacketType::PROTECTED) {
-          packet_info->is_crypto_packet = false;
-        } else {
-          packet_info->is_crypto_packet = static_cast<QUICLongHeaderPacket &>(*packet).is_crypto_packet();
-        }
-        packet_info->in_flight = true;
+        packet_info->in_flight     = true;
         if (packet_info->ack_eliciting) {
           packet_info->sent_bytes = packet->size();
         } else {
@@ -1488,12 +1561,13 @@ QUICNetVConnection::_state_common_send_packet()
           ink_assert(!"failed to protect buffer");
         }
 
-        QUICConDebug("[TX] %s packet #%" PRIu64 " size=%zu", QUICDebugNames::packet_type(packet->type()), packet->packet_number(),
-                     len);
+        QUICConVDebug("[TX] %s packet #%" PRIu64 " size=%zu", QUICDebugNames::packet_type(packet->type()), packet->packet_number(),
+                      len);
 
         if (this->_pp_key_info.is_encryption_key_available(QUICKeyPhase::INITIAL) && packet->type() == QUICPacketType::HANDSHAKE &&
             this->netvc_context == NET_VCONNECTION_OUT) {
           this->_pp_key_info.drop_keys(QUICKeyPhase::INITIAL);
+          this->_loss_detector->on_packet_number_space_discarded(QUICPacketNumberSpace::INITIAL);
           this->_minimum_encryption_level = QUICEncryptionLevel::HANDSHAKE;
         }
 
@@ -1542,7 +1616,7 @@ QUICNetVConnection::_state_closing_send_packet()
 
 Ptr<IOBufferBlock>
 QUICNetVConnection::_store_frame(Ptr<IOBufferBlock> parent_block, size_t &size_added, uint64_t &max_frame_size, QUICFrame &frame,
-                                 std::vector<QUICFrameInfo> &frames)
+                                 std::vector<QUICSentPacketInfo::FrameInfo> &frames)
 {
   Ptr<IOBufferBlock> new_block = frame.to_io_buffer_block(max_frame_size);
 
@@ -1565,7 +1639,7 @@ QUICNetVConnection::_store_frame(Ptr<IOBufferBlock> parent_block, size_t &size_a
   if (is_debug_tag_set(QUIC_DEBUG_TAG.data())) {
     char msg[1024];
     frame.debug_msg(msg, sizeof(msg));
-    QUICConDebug("[TX] | %s", msg);
+    QUICConVDebug("[TX] | %s", msg);
   }
 
   frames.emplace_back(frame.id(), frame.generated_by());
@@ -1578,7 +1652,7 @@ QUICNetVConnection::_store_frame(Ptr<IOBufferBlock> parent_block, size_t &size_a
 
 QUICPacketUPtr
 QUICNetVConnection::_packetize_frames(uint8_t *packet_buf, QUICEncryptionLevel level, uint64_t max_packet_size,
-                                      std::vector<QUICFrameInfo> &frames)
+                                      std::vector<QUICSentPacketInfo::FrameInfo> &frames)
 {
   QUICPacketUPtr packet = QUICPacketFactory::create_null_packet();
   if (max_packet_size <= MAX_PACKET_OVERHEAD) {
@@ -1634,8 +1708,8 @@ QUICNetVConnection::_packetize_frames(uint8_t *packet_buf, QUICEncryptionLevel l
         probing |= frame->is_probing_frame();
         if (frame->is_flow_controlled()) {
           int ret = this->_remote_flow_controller->update(this->_stream_manager->total_offset_sent());
-          QUICFCDebug("[REMOTE] %" PRIu64 "/%" PRIu64, this->_remote_flow_controller->current_offset(),
-                      this->_remote_flow_controller->current_limit());
+          QUICFCVDebug("[REMOTE] %" PRIu64 "/%" PRIu64, this->_remote_flow_controller->current_offset(),
+                       this->_remote_flow_controller->current_limit());
           ink_assert(ret == 0);
         }
         last_block = this->_store_frame(last_block, size_added, max_frame_size, *frame, frames);
@@ -1690,7 +1764,7 @@ QUICNetVConnection::_packetize_closing_frame()
 
   size_t size_added       = 0;
   uint64_t max_frame_size = static_cast<uint64_t>(max_size);
-  std::vector<QUICFrameInfo> frames;
+  std::vector<QUICSentPacketInfo::FrameInfo> frames;
   Ptr<IOBufferBlock> first_block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
   Ptr<IOBufferBlock> last_block  = first_block;
   first_block->alloc(iobuffer_size_to_index(0, BUFFER_SIZE_INDEX_32K));
@@ -1736,16 +1810,16 @@ QUICNetVConnection::_recv_and_ack(const QUICPacketR &packet, bool *has_non_probi
 
   if (is_flow_controlled) {
     int ret = this->_local_flow_controller->update(this->_stream_manager->total_offset_received());
-    QUICFCDebug("[LOCAL] %" PRIu64 "/%" PRIu64, this->_local_flow_controller->current_offset(),
-                this->_local_flow_controller->current_limit());
+    QUICFCVDebug("[LOCAL] %" PRIu64 "/%" PRIu64, this->_local_flow_controller->current_offset(),
+                 this->_local_flow_controller->current_limit());
 
     if (ret != 0) {
       return std::make_unique<QUICConnectionError>(QUICTransErrorCode::FLOW_CONTROL_ERROR);
     }
 
     this->_local_flow_controller->forward_limit(this->_stream_manager->total_reordered_bytes() + this->_flow_control_buffer_size);
-    QUICFCDebug("[LOCAL] %" PRIu64 "/%" PRIu64, this->_local_flow_controller->current_offset(),
-                this->_local_flow_controller->current_limit());
+    QUICFCVDebug("[LOCAL] %" PRIu64 "/%" PRIu64, this->_local_flow_controller->current_offset(),
+                 this->_local_flow_controller->current_limit());
   }
 
   this->_ack_frame_manager.update(level, packet_num, size, ack_only);
@@ -1890,11 +1964,11 @@ QUICNetVConnection::_dequeue_recv_packet(uint8_t *packet_buf, QUICPacketCreation
     switch (packet->type()) {
     case QUICPacketType::VERSION_NEGOTIATION:
     case QUICPacketType::RETRY:
-      QUICConDebug("[RX] %s packet size=%u", QUICDebugNames::packet_type(packet->type()), packet->size());
+      QUICConVDebug("[RX] %s packet size=%u", QUICDebugNames::packet_type(packet->type()), packet->size());
       break;
     default:
-      QUICConDebug("[RX] %s packet #%" PRIu64 " size=%u header_len=%u payload_len=%u", QUICDebugNames::packet_type(packet->type()),
-                   packet->packet_number(), packet->size(), packet->header_size(), packet->payload_length());
+      QUICConVDebug("[RX] %s packet #%" PRIu64 " size=%u header_len=%u payload_len=%u", QUICDebugNames::packet_type(packet->type()),
+                    packet->packet_number(), packet->size(), packet->header_size(), packet->payload_length());
       break;
     }
     break;
@@ -2022,11 +2096,14 @@ QUICNetVConnection::_complete_handshake_if_possible()
   this->_init_flow_control_params(this->_handshake_handler->local_transport_parameters(),
                                   this->_handshake_handler->remote_transport_parameters());
 
-  // PN space doesn't matter but seems like this is the way to pick the LossDetector for 0-RTT and Short packet
   uint64_t ack_delay_exponent =
     this->_handshake_handler->remote_transport_parameters()->getAsUInt(QUICTransportParameterId::ACK_DELAY_EXPONENT);
   this->_loss_detector->update_ack_delay_exponent(ack_delay_exponent);
 
+  uint64_t max_ack_delay =
+    this->_handshake_handler->remote_transport_parameters()->getAsUInt(QUICTransportParameterId::MAX_ACK_DELAY);
+  this->_rtt_measure.set_max_ack_delay(max_ack_delay);
+
   const uint8_t *reset_token;
   uint16_t reset_token_len;
   reset_token = this->_handshake_handler->remote_transport_parameters()->getAsBytes(QUICTransportParameterId::STATELESS_RESET_TOKEN,
@@ -2037,6 +2114,8 @@ QUICNetVConnection::_complete_handshake_if_possible()
 
   this->_start_application();
 
+  this->_handshake_completed = true;
+
   return 0;
 }
 
diff --git a/iocore/net/QUICPacketHandler.cc b/iocore/net/QUICPacketHandler.cc
index 7c91351..4a69065 100644
--- a/iocore/net/QUICPacketHandler.cc
+++ b/iocore/net/QUICPacketHandler.cc
@@ -37,14 +37,17 @@
 #include "QUICMultiCertConfigLoader.h"
 #include "QUICTLS.h"
 
-static constexpr char debug_tag[] = "quic_sec";
+static constexpr char debug_tag[]   = "quic_sec";
+static constexpr char v_debug_tag[] = "v_quic_sec";
 
 #define QUICDebug(fmt, ...) Debug(debug_tag, fmt, ##__VA_ARGS__)
-#define QUICDebugQC(qc, fmt, ...) Debug(debug_tag, "[%s] " fmt, qc->cids().data(), ##__VA_ARGS__)
+#define QUICQCDebug(qc, fmt, ...) Debug(debug_tag, "[%s] " fmt, qc->cids().data(), ##__VA_ARGS__)
 
 // ["local dcid" - "local scid"]
-#define QUICDebugDS(dcid, scid, fmt, ...) \
+#define QUICPHDebug(dcid, scid, fmt, ...) \
   Debug(debug_tag, "[%08" PRIx32 "-%08" PRIx32 "] " fmt, dcid.h32(), scid.h32(), ##__VA_ARGS__)
+#define QUICVPHDebug(dcid, scid, fmt, ...) \
+  Debug(v_debug_tag, "[%08" PRIx32 "-%08" PRIx32 "] " fmt, dcid.h32(), scid.h32(), ##__VA_ARGS__)
 
 //
 // QUICPacketHandler
@@ -99,7 +102,7 @@ QUICPacketHandler::_send_packet(UDPConnection *udp_con, IpEndpoint &addr, Ptr<IO
 {
   UDPPacket *udp_packet = new_UDPPacket(addr, 0, udp_payload);
 
-  if (is_debug_tag_set(debug_tag)) {
+  if (is_debug_tag_set(v_debug_tag)) {
     ip_port_text_buffer ipb;
     QUICConnectionId dcid = QUICConnectionId::ZERO();
     QUICConnectionId scid = QUICConnectionId::ZERO();
@@ -117,8 +120,8 @@ QUICPacketHandler::_send_packet(UDPConnection *udp_con, IpEndpoint &addr, Ptr<IO
       }
     }
 
-    QUICDebugDS(dcid, scid, "send %s packet to %s from port %u size=%" PRId64, (QUICInvariants::is_long_header(buf) ? "LH" : "SH"),
-                ats_ip_nptop(&addr, ipb, sizeof(ipb)), udp_con->getPortNum(), buf_len);
+    QUICVPHDebug(dcid, scid, "send %s packet to %s from port %u size=%" PRId64, (QUICInvariants::is_long_header(buf) ? "LH" : "SH"),
+                 ats_ip_nptop(&addr, ipb, sizeof(ipb)), udp_con->getPortNum(), buf_len);
   }
 
   udp_con->send(this->_get_continuation(), udp_packet);
@@ -215,6 +218,7 @@ QUICPacketHandlerIn::_recv_packet(int event, UDPPacket *udp_packet)
   IOBufferBlock *block = udp_packet->getIOBlockChain();
   const uint8_t *buf   = reinterpret_cast<uint8_t *>(block->buf());
   uint64_t buf_len     = block->size();
+  QUICVersion version;
 
   if (buf_len == 0) {
     QUICDebug("Ignore packet - payload is too small");
@@ -238,25 +242,24 @@ QUICPacketHandlerIn::_recv_packet(int event, UDPPacket *udp_packet)
       return;
     }
 
-    if (is_debug_tag_set(debug_tag)) {
+    if (is_debug_tag_set(v_debug_tag)) {
       ip_port_text_buffer ipb_from;
       ip_port_text_buffer ipb_to;
-      QUICDebugDS(scid, dcid, "recv LH packet from %s to %s size=%" PRId64,
-                  ats_ip_nptop(&udp_packet->from.sa, ipb_from, sizeof(ipb_from)),
-                  ats_ip_nptop(&udp_packet->to.sa, ipb_to, sizeof(ipb_to)), udp_packet->getPktLength());
+      QUICVPHDebug(scid, dcid, "recv LH packet from %s to %s size=%" PRId64,
+                   ats_ip_nptop(&udp_packet->from.sa, ipb_from, sizeof(ipb_from)),
+                   ats_ip_nptop(&udp_packet->to.sa, ipb_to, sizeof(ipb_to)), udp_packet->getPktLength());
     }
 
-    QUICVersion v;
-    if (unlikely(!QUICInvariants::version(v, buf, buf_len))) {
+    if (unlikely(!QUICInvariants::version(version, buf, buf_len))) {
       QUICDebug("Ignore packet - payload is too small");
       udp_packet->free();
       return;
     }
 
-    if (!QUICInvariants::is_version_negotiation(v) && !QUICTypeUtil::is_supported_version(v)) {
-      QUICDebugDS(scid, dcid, "Unsupported version: 0x%x", v);
+    if (!QUICInvariants::is_version_negotiation(version) && !QUICTypeUtil::is_supported_version(version)) {
+      QUICPHDebug(scid, dcid, "Unsupported version: 0x%x", version);
 
-      QUICPacketUPtr vn = QUICPacketFactory::create_version_negotiation_packet(scid, dcid);
+      QUICPacketUPtr vn = QUICPacketFactory::create_version_negotiation_packet(scid, dcid, version);
       this->_send_packet(*vn, udp_packet->getConnection(), udp_packet->from, 1200, nullptr, 0);
       udp_packet->free();
       return;
@@ -281,12 +284,12 @@ QUICPacketHandlerIn::_recv_packet(int event, UDPPacket *udp_packet)
     }
   } else {
     // TODO: lookup DCID by 5-tuple when ATS omits SCID
-    if (is_debug_tag_set(debug_tag)) {
+    if (is_debug_tag_set(v_debug_tag)) {
       ip_port_text_buffer ipb_from;
       ip_port_text_buffer ipb_to;
-      QUICDebugDS(scid, dcid, "recv SH packet from %s to %s size=%" PRId64,
-                  ats_ip_nptop(&udp_packet->from.sa, ipb_from, sizeof(ipb_from)),
-                  ats_ip_nptop(&udp_packet->to.sa, ipb_to, sizeof(ipb_to)), udp_packet->getPktLength());
+      QUICVPHDebug(scid, dcid, "recv SH packet from %s to %s size=%" PRId64,
+                   ats_ip_nptop(&udp_packet->from.sa, ipb_from, sizeof(ipb_from)),
+                   ats_ip_nptop(&udp_packet->to.sa, ipb_to, sizeof(ipb_to)), udp_packet->getPktLength());
     }
   }
 
@@ -295,9 +298,11 @@ QUICPacketHandlerIn::_recv_packet(int event, UDPPacket *udp_packet)
 
   // Server Stateless Retry
   QUICConfig::scoped_config params;
-  QUICConnectionId cid_in_retry_token = QUICConnectionId::ZERO();
+  QUICConnectionId ocid_in_retry_token = QUICConnectionId::ZERO();
+  QUICConnectionId rcid_in_retry_token = QUICConnectionId::ZERO();
   if (!vc && params->stateless_retry() && QUICInvariants::is_long_header(buf)) {
-    int ret = this->_stateless_retry(buf, buf_len, udp_packet->getConnection(), udp_packet->from, dcid, scid, &cid_in_retry_token);
+    int ret = this->_stateless_retry(buf, buf_len, udp_packet->getConnection(), udp_packet->from, dcid, scid, &ocid_in_retry_token,
+                                     &rcid_in_retry_token, version);
     if (ret < 0) {
       udp_packet->free();
       return;
@@ -320,7 +325,7 @@ QUICPacketHandlerIn::_recv_packet(int event, UDPPacket *udp_packet)
     udp_packet->free();
 
     if (is_debug_tag_set(debug_tag) && sent) {
-      QUICDebugDS(scid, dcid, "sent Stateless Reset : connection not found, dcid=%s", dcid.hex().c_str());
+      QUICPHDebug(scid, dcid, "sent Stateless Reset : connection not found, dcid=%s", dcid.hex().c_str());
     }
 
     return;
@@ -331,7 +336,7 @@ QUICPacketHandlerIn::_recv_packet(int event, UDPPacket *udp_packet)
     udp_packet->free();
 
     if (is_debug_tag_set(debug_tag) && sent) {
-      QUICDebugDS(scid, dcid, "sent Stateless Reset : connection is already closed, dcid=%s", dcid.hex().c_str());
+      QUICPHDebug(scid, dcid, "sent Stateless Reset : connection is already closed, dcid=%s", dcid.hex().c_str());
     }
 
     return;
@@ -348,11 +353,12 @@ QUICPacketHandlerIn::_recv_packet(int event, UDPPacket *udp_packet)
     QUICConnectionId peer_cid     = scid;
 
     if (is_debug_tag_set("quic_sec")) {
-      QUICDebugDS(peer_cid, original_cid, "client initial dcid=%s", original_cid.hex().c_str());
+      QUICPHDebug(peer_cid, original_cid, "client initial dcid=%s", original_cid.hex().c_str());
     }
 
     vc = static_cast<QUICNetVConnection *>(getNetProcessor()->allocate_vc(nullptr));
-    vc->init(peer_cid, original_cid, cid_in_retry_token, udp_packet->getConnection(), this, &this->_rtable, &this->_ctable);
+    vc->init(version, peer_cid, original_cid, ocid_in_retry_token, rcid_in_retry_token, udp_packet->getConnection(), this,
+             &this->_rtable, &this->_ctable);
     vc->id = net_next_connection_number();
     vc->con.move(con);
     vc->submit_time = Thread::get_hrtime();
@@ -393,7 +399,8 @@ QUICPacketHandler::send_packet(QUICNetVConnection *vc, const Ptr<IOBufferBlock>
 
 int
 QUICPacketHandlerIn::_stateless_retry(const uint8_t *buf, uint64_t buf_len, UDPConnection *connection, IpEndpoint from,
-                                      QUICConnectionId dcid, QUICConnectionId scid, QUICConnectionId *original_cid)
+                                      QUICConnectionId dcid, QUICConnectionId scid, QUICConnectionId *original_cid,
+                                      QUICConnectionId *retry_cid, QUICVersion version)
 {
   QUICPacketType type = QUICPacketType::UNINITIALIZED;
   QUICPacketR::type(type, buf, buf_len);
@@ -411,14 +418,14 @@ QUICPacketHandlerIn::_stateless_retry(const uint8_t *buf, uint64_t buf_len, UDPC
   }
 
   if (token_length == 0) {
-    QUICRetryToken token(from, dcid);
     QUICConnectionId local_cid;
     local_cid.randomize();
-    QUICPacketUPtr retry_packet = QUICPacketFactory::create_retry_packet(scid, local_cid, token);
+    QUICRetryToken token(from, dcid, local_cid);
+    QUICPacketUPtr retry_packet = QUICPacketFactory::create_retry_packet(version, scid, local_cid, token);
 
-    QUICDebug("[TX] %s packet ODCID=%" PRIx64 " token_length=%u token=%02x%02x%02x%02x...",
-              QUICDebugNames::packet_type(retry_packet->type()), static_cast<uint64_t>(token.original_dcid()), token.length(),
-              token.buf()[0], token.buf()[1], token.buf()[2], token.buf()[3]);
+    QUICDebug("[TX] %s packet ODCID=%" PRIx64 " RCID=%" PRIx64 " token_length=%u token=%02x%02x%02x%02x...",
+              QUICDebugNames::packet_type(retry_packet->type()), static_cast<uint64_t>(token.original_dcid()),
+              static_cast<uint64_t>(token.scid()), token.length(), token.buf()[0], token.buf()[1], token.buf()[2], token.buf()[3]);
     this->_send_packet(*retry_packet, connection, from, 1200, nullptr, 0);
 
     return -2;
@@ -429,12 +436,14 @@ QUICPacketHandlerIn::_stateless_retry(const uint8_t *buf, uint64_t buf_len, UDPC
       QUICRetryToken token(buf + token_offset, token_length);
       if (token.is_valid(from)) {
         *original_cid = token.original_dcid();
-        QUICDebug("Retry Token is valid. ODCID=%" PRIx64, static_cast<uint64_t>(*original_cid));
+        *retry_cid    = token.scid();
+        QUICDebug("Retry Token is valid. ODCID=%" PRIx64 " RCID=%" PRIx64, static_cast<uint64_t>(*original_cid),
+                  static_cast<uint64_t>(*retry_cid));
         return 0;
       } else {
-        QUICDebug("Retry token is invalid: ODCID=%" PRIx64 "token_length=%u token=%02x%02x%02x%02x...",
-                  static_cast<uint64_t>(token.original_dcid()), token.length(), token.buf()[0], token.buf()[1], token.buf()[2],
-                  token.buf()[3]);
+        QUICDebug("Retry token is invalid: ODCID=%" PRIx64 " RCID=%" PRIx64 " token_length=%u token=%02x%02x%02x%02x...",
+                  static_cast<uint64_t>(token.original_dcid()), static_cast<uint64_t>(*retry_cid), token.length(), token.buf()[0],
+                  token.buf()[1], token.buf()[2], token.buf()[3]);
         this->_send_invalid_token_error(buf, buf_len, connection, from);
         return -3;
       }
@@ -468,6 +477,8 @@ QUICPacketHandlerIn::_send_invalid_token_error(const uint8_t *initial_packet, ui
   QUICConnectionId dcid_in_initial;
   QUICInvariants::scid(scid_in_initial, initial_packet, initial_packet_len);
   QUICInvariants::dcid(dcid_in_initial, initial_packet, initial_packet_len);
+  QUICVersion version_in_initial;
+  QUICLongHeaderPacketR::version(version_in_initial, initial_packet, initial_packet_len);
 
   // Create CONNECTION_CLOSE frame
   auto error = std::make_unique<QUICConnectionError>(QUICTransErrorCode::INVALID_TOKEN);
@@ -487,7 +498,7 @@ QUICPacketHandlerIn::_send_invalid_token_error(const uint8_t *initial_packet, ui
   QUICPacketHeaderProtector php(ppki);
   QUICCertConfig::scoped_config server_cert;
   QUICTLS tls(ppki, server_cert->ssl_default.get(), NET_VCONNECTION_IN, {}, "", "");
-  tls.initialize_key_materials(dcid_in_initial);
+  tls.initialize_key_materials(dcid_in_initial, version_in_initial);
 
   // Create INITIAL packet
   QUICConnectionId scid;
@@ -553,7 +564,7 @@ QUICPacketHandlerOut::_recv_packet(int event, UDPPacket *udp_packet)
   if (is_debug_tag_set(debug_tag)) {
     ip_port_text_buffer ipb_from;
     ip_port_text_buffer ipb_to;
-    QUICDebugQC(this->_vc, "recv %s packet from %s to %s size=%" PRId64, (QUICInvariants::is_long_header(buf) ? "LH" : "SH"),
+    QUICQCDebug(this->_vc, "recv %s packet from %s to %s size=%" PRId64, (QUICInvariants::is_long_header(buf) ? "LH" : "SH"),
                 ats_ip_nptop(&udp_packet->from.sa, ipb_from, sizeof(ipb_from)),
                 ats_ip_nptop(&udp_packet->to.sa, ipb_to, sizeof(ipb_to)), udp_packet->getPktLength());
   }
diff --git a/iocore/net/quic/Makefile.am b/iocore/net/quic/Makefile.am
index 75c4daf..fc02e6d 100644
--- a/iocore/net/quic/Makefile.am
+++ b/iocore/net/quic/Makefile.am
@@ -142,7 +142,7 @@ TESTS = $(check_PROGRAMS)
 
 test_CPPFLAGS = \
   $(AM_CPPFLAGS) \
-  -I$(abs_top_srcdir)/tests/include
+  -I$(abs_top_srcdir)/tests/include -O0
 
 test_LDADD = \
   libquic.a \
diff --git a/iocore/net/quic/Mock.h b/iocore/net/quic/Mock.h
index 68e421a..c0d28a3 100644
--- a/iocore/net/quic/Mock.h
+++ b/iocore/net/quic/Mock.h
@@ -28,6 +28,9 @@
 #include "QUICApplication.h"
 #include "QUICStreamManager.h"
 #include "QUICLossDetector.h"
+#include "QUICPacketProtectionKeyInfo.h"
+#include "QUICPinger.h"
+#include "QUICPadder.h"
 #include "QUICEvents.h"
 #include "QUICPacketProtectionKeyInfo.h"
 #include "QUICPinger.h"
@@ -37,7 +40,7 @@
 class MockQUICContext;
 
 using namespace std::literals;
-std::string_view negotiated_application_name_sv = "h3-27"sv;
+std::string_view negotiated_application_name_sv = "h3-29"sv;
 
 class MockQUICLDConfig : public QUICLDConfig
 {
@@ -154,6 +157,18 @@ class MockQUICConnectionInfoProvider : public QUICConnectionInfoProvider
     return {reinterpret_cast<const uint8_t *>("\x00"), 1};
   }
 
+  QUICConnectionId
+  retry_source_connection_id() const override
+  {
+    return {reinterpret_cast<const uint8_t *>("\x00"), 1};
+  }
+
+  QUICConnectionId
+  initial_source_connection_id() const override
+  {
+    return {reinterpret_cast<const uint8_t *>("\x00"), 1};
+  }
+
   const QUICFiveTuple
   five_tuple() const override
   {
@@ -192,6 +207,36 @@ class MockQUICConnectionInfoProvider : public QUICConnectionInfoProvider
     return false;
   }
 
+  bool
+  is_at_anti_amplification_limit() const override
+  {
+    return false;
+  }
+
+  bool
+  is_address_validation_completed() const override
+  {
+    return true;
+  }
+
+  bool
+  is_handshake_completed() const override
+  {
+    return true;
+  }
+
+  bool
+  has_keys_for(QUICPacketNumberSpace space) const override
+  {
+    return true;
+  }
+
+  QUICVersion
+  negotiated_version() const override
+  {
+    return QUIC_SUPPORTED_VERSIONS[0];
+  }
+
   std::string_view
   negotiated_application_name() const override
   {
@@ -341,6 +386,18 @@ public:
     return {reinterpret_cast<const uint8_t *>("\x00"), 1};
   }
 
+  QUICConnectionId
+  retry_source_connection_id() const override
+  {
+    return {reinterpret_cast<const uint8_t *>("\x00"), 1};
+  }
+
+  QUICConnectionId
+  initial_source_connection_id() const override
+  {
+    return {reinterpret_cast<const uint8_t *>("\x00"), 1};
+  }
+
   const QUICFiveTuple
   five_tuple() const override
   {
@@ -409,6 +466,30 @@ public:
     return false;
   }
 
+  bool
+  is_at_anti_amplification_limit() const override
+  {
+    return false;
+  }
+
+  bool
+  is_address_validation_completed() const override
+  {
+    return true;
+  }
+
+  bool
+  is_handshake_completed() const override
+  {
+    return true;
+  }
+
+  bool
+  has_keys_for(QUICPacketNumberSpace space) const override
+  {
+    return true;
+  }
+
   void
   handle_received_packet(UDPPacket *) override
   {
@@ -419,6 +500,12 @@ public:
   {
   }
 
+  QUICVersion
+  negotiated_version() const override
+  {
+    return QUIC_SUPPORTED_VERSIONS[0];
+  }
+
   std::string_view
   negotiated_application_name() const override
   {
@@ -448,7 +535,7 @@ public:
   MockQUICCongestionController() {}
   // Override
   virtual void
-  on_packets_lost(const std::map<QUICPacketNumber, QUICPacketInfo *> &packets) override
+  on_packets_lost(const std::map<QUICPacketNumber, QUICSentPacketInfoUPtr> &packets) override
   {
     for (auto &p : packets) {
       lost_packets.insert(p.first);
@@ -460,11 +547,15 @@ public:
   {
   }
   virtual void
-  on_packet_acked(const QUICPacketInfo &acked_packet) override
+  on_packets_acked(const std::vector<QUICSentPacketInfoUPtr> &packets) override
+  {
+  }
+  void
+  on_packet_number_space_discarded(size_t bytes_in_flight) override
   {
   }
   virtual void
-  process_ecn(const QUICPacketInfo &acked_largest_packet, const QUICAckFrame::EcnSection *ecn_section) override
+  process_ecn(const QUICAckFrame &ack_frame, QUICPacketNumberSpace pn_space, ink_hrtime largest_acked_time_sent) override
   {
   }
   virtual void
@@ -627,6 +718,12 @@ public:
   {
   }
 
+  void
+  on_packet_number_space_discarded(QUICPacketNumberSpace pn_space)
+  {
+    this->_cc.on_packet_number_space_discarded(0);
+  }
+
 private:
   QUICPinger _pinger;
   QUICPadder _padder;
@@ -740,7 +837,7 @@ public:
   }
 
   int
-  initialize_key_materials(QUICConnectionId cid) override
+  initialize_key_materials(QUICConnectionId cid, QUICVersion version) override
   {
     return 0;
   }
diff --git a/iocore/net/quic/QUICAckFrameCreator.cc b/iocore/net/quic/QUICAckFrameCreator.cc
index dd2e2f9..bee2b76 100644
--- a/iocore/net/quic/QUICAckFrameCreator.cc
+++ b/iocore/net/quic/QUICAckFrameCreator.cc
@@ -28,7 +28,7 @@
 
 QUICAckFrameManager::QUICAckFrameManager()
 {
-  for (auto i = 0; i < kPacketNumberSpace; i++) {
+  for (auto i = 0; i < QUIC_N_PACKET_SPACES; i++) {
     this->_ack_creator[i] = std::make_unique<QUICAckFrameCreator>(static_cast<QUICPacketNumberSpace>(i), this);
   }
 }
@@ -132,7 +132,7 @@ QUICAckFrameManager::ack_delay_exponent() const
 void
 QUICAckFrameManager::set_max_ack_delay(uint16_t delay)
 {
-  for (auto i = 0; i < kPacketNumberSpace; i++) {
+  for (auto i = 0; i < QUIC_N_PACKET_SPACES; i++) {
     this->_ack_creator[i]->set_max_ack_delay(delay);
   }
 }
@@ -193,7 +193,7 @@ QUICAckFrameManager::QUICAckFrameCreator::push_back(QUICPacketNumber packet_numb
   }
 
   // can not delay handshake packet
-  if ((this->_pn_space == QUICPacketNumberSpace::Initial || this->_pn_space == QUICPacketNumberSpace::Handshake) && !ack_only) {
+  if ((this->_pn_space == QUICPacketNumberSpace::INITIAL || this->_pn_space == QUICPacketNumberSpace::HANDSHAKE) && !ack_only) {
     this->_should_send = true;
   }
 
@@ -335,7 +335,7 @@ QUICAckFrameManager::QUICAckFrameCreator::_calculate_delay()
   ink_hrtime now             = Thread::get_hrtime();
   uint64_t delay             = (now - this->_largest_ack_received_time) / 1000;
   uint8_t ack_delay_exponent = 3;
-  if (this->_pn_space != QUICPacketNumberSpace::Initial && this->_pn_space != QUICPacketNumberSpace::Handshake) {
+  if (this->_pn_space != QUICPacketNumberSpace::INITIAL && this->_pn_space != QUICPacketNumberSpace::HANDSHAKE) {
     ack_delay_exponent = this->_ack_manager->ack_delay_exponent();
   }
   return delay >> ack_delay_exponent;
diff --git a/iocore/net/quic/QUICAckFrameCreator.h b/iocore/net/quic/QUICAckFrameCreator.h
index 4fc89ba..55e20f2 100644
--- a/iocore/net/quic/QUICAckFrameCreator.h
+++ b/iocore/net/quic/QUICAckFrameCreator.h
@@ -78,7 +78,7 @@ public:
 
     QUICAckFrameManager *_ack_manager = nullptr;
 
-    QUICPacketNumberSpace _pn_space = QUICPacketNumberSpace::Initial;
+    QUICPacketNumberSpace _pn_space = QUICPacketNumberSpace::INITIAL;
   };
 
   static constexpr int MAXIMUM_PACKET_COUNT = 256;
diff --git a/iocore/net/quic/QUICAddrVerifyState.cc b/iocore/net/quic/QUICAddrVerifyState.cc
index 67281b5..6db5019 100644
--- a/iocore/net/quic/QUICAddrVerifyState.cc
+++ b/iocore/net/quic/QUICAddrVerifyState.cc
@@ -48,7 +48,7 @@ QUICAddrVerifyState::consume(uint32_t windows)
 }
 
 uint32_t
-QUICAddrVerifyState::windows()
+QUICAddrVerifyState::windows() const
 {
   return this->_windows;
 }
diff --git a/iocore/net/quic/QUICAddrVerifyState.h b/iocore/net/quic/QUICAddrVerifyState.h
index d6d6c7c..6b454e4 100644
--- a/iocore/net/quic/QUICAddrVerifyState.h
+++ b/iocore/net/quic/QUICAddrVerifyState.h
@@ -34,7 +34,7 @@ public:
   void fill(uint32_t windows);
   void consume(uint32_t windows);
   void set_addr_verifed();
-  uint32_t windows();
+  uint32_t windows() const;
   bool is_verified() const;
 
 private:
diff --git a/iocore/net/quic/QUICBidirectionalStream.cc b/iocore/net/quic/QUICBidirectionalStream.cc
index 9565b0a..a5d2f71 100644
--- a/iocore/net/quic/QUICBidirectionalStream.cc
+++ b/iocore/net/quic/QUICBidirectionalStream.cc
@@ -469,8 +469,8 @@ QUICBidirectionalStream::generate_frame(uint8_t *buf, QUICEncryptionLevel level,
     // Calling update always success, because len is always less than stream_credit
     ink_assert(ret == 0);
 
-    QUICStreamFCDebug("[REMOTE] %" PRIu64 "/%" PRIu64, this->_remote_flow_controller.current_offset(),
-                      this->_remote_flow_controller.current_limit());
+    QUICVStreamFCDebug("[REMOTE] %" PRIu64 "/%" PRIu64, this->_remote_flow_controller.current_offset(),
+                       this->_remote_flow_controller.current_limit());
     if (this->_remote_flow_controller.current_offset() == this->_remote_flow_controller.current_limit()) {
       QUICStreamDebug("Flow Controller will block sending a STREAM frame");
     }
diff --git a/iocore/net/quic/QUICConfig.cc b/iocore/net/quic/QUICConfig.cc
index 8e39794..aec4f13 100644
--- a/iocore/net/quic/QUICConfig.cc
+++ b/iocore/net/quic/QUICConfig.cc
@@ -179,9 +179,8 @@ QUICConfigParams::initialize()
   this->_ld_initial_rtt = HRTIME_MSECONDS(timeout);
 
   // Congestion Control
-  REC_EstablishStaticConfigInt32U(this->_cc_max_datagram_size, "proxy.config.quic.congestion_control.max_datagram_size");
-  REC_EstablishStaticConfigInt32U(this->_cc_initial_window_scale, "proxy.config.quic.congestion_control.initial_window_scale");
-  REC_EstablishStaticConfigInt32U(this->_cc_minimum_window_scale, "proxy.config.quic.congestion_control.minimum_window_scale");
+  REC_EstablishStaticConfigInt32U(this->_cc_initial_window, "proxy.config.quic.congestion_control.initial_window");
+  REC_EstablishStaticConfigInt32U(this->_cc_minimum_window, "proxy.config.quic.congestion_control.minimum_window");
   REC_EstablishStaticConfigFloat(this->_cc_loss_reduction_factor, "proxy.config.quic.congestion_control.loss_reduction_factor");
   REC_EstablishStaticConfigInt32U(this->_cc_persistent_congestion_threshold,
                                   "proxy.config.quic.congestion_control.persistent_congestion_threshold");
@@ -420,26 +419,15 @@ QUICConfigParams::ld_initial_rtt() const
 }
 
 uint32_t
-QUICConfigParams::cc_max_datagram_size() const
-{
-  return _cc_max_datagram_size;
-}
-
-uint32_t
 QUICConfigParams::cc_initial_window() const
 {
-  // kInitialWindow:  Default limit on the initial amount of data in
-  // flight, in bytes.  Taken from [RFC6928].  The RECOMMENDED value is
-  // the minimum of 10 * kMaxDatagramSize and max(2* kMaxDatagramSize,
-  // 14600)).
-  return std::min(_cc_initial_window_scale * _cc_max_datagram_size,
-                  std::max(2 * _cc_max_datagram_size, static_cast<uint32_t>(14600)));
+  return _cc_initial_window;
 }
 
 uint32_t
 QUICConfigParams::cc_minimum_window() const
 {
-  return _cc_minimum_window_scale * _cc_max_datagram_size;
+  return _cc_minimum_window;
 }
 
 float
diff --git a/iocore/net/quic/QUICConfig.h b/iocore/net/quic/QUICConfig.h
index 38a655a..0ce045a 100644
--- a/iocore/net/quic/QUICConfig.h
+++ b/iocore/net/quic/QUICConfig.h
@@ -146,9 +146,8 @@ private:
   ink_hrtime _ld_initial_rtt    = HRTIME_MSECONDS(500);
 
   // [draft-11 recovery] 4.7.1.  Constants of interest
-  uint32_t _cc_max_datagram_size               = 1200;
-  uint32_t _cc_initial_window_scale            = 10; // Actual initial window size is this value multiplied by the _cc_default_mss
-  uint32_t _cc_minimum_window_scale            = 2;  // Actual minimum window size is this value multiplied by the _cc_default_mss
+  uint32_t _cc_initial_window                  = 1200 * 10;
+  uint32_t _cc_minimum_window                  = 1200 * 2;
   float _cc_loss_reduction_factor              = 0.5;
   uint32_t _cc_persistent_congestion_threshold = 3;
 };
diff --git a/iocore/net/quic/QUICCongestionController.h b/iocore/net/quic/QUICCongestionController.h
index 42b4a8b..7417ac8 100644
--- a/iocore/net/quic/QUICCongestionController.h
+++ b/iocore/net/quic/QUICCongestionController.h
@@ -25,22 +25,6 @@
 
 #include "QUICFrame.h"
 
-struct QUICPacketInfo {
-  // 6.3.1.  Sent Packet Fields
-  QUICPacketNumber packet_number;
-  ink_hrtime time_sent;
-  bool ack_eliciting;
-  bool is_crypto_packet;
-  bool in_flight;
-  size_t sent_bytes;
-
-  // addition
-  QUICPacketType type;
-  std::vector<QUICFrameInfo> frames;
-  QUICPacketNumberSpace pn_space;
-  // end
-};
-
 class QUICCongestionController
 {
 public:
@@ -52,15 +36,21 @@ public:
   };
 
   virtual ~QUICCongestionController() {}
-  virtual void on_packet_sent(size_t bytes_sent)                                                                    = 0;
-  virtual void on_packet_acked(const QUICPacketInfo &acked_packet)                                                  = 0;
-  virtual void process_ecn(const QUICPacketInfo &acked_largest_packet, const QUICAckFrame::EcnSection *ecn_section) = 0;
-  virtual void on_packets_lost(const std::map<QUICPacketNumber, QUICPacketInfo *> &packets)                         = 0;
-  virtual void add_extra_credit()                                                                                   = 0;
-  virtual void reset()                                                                                              = 0;
-  virtual uint32_t credit() const                                                                                   = 0;
+  // Appendix B.  Congestion Control Pseudocode
+  virtual void on_packet_sent(size_t bytes_sent)                                                                               = 0;
+  virtual void on_packets_acked(const std::vector<QUICSentPacketInfoUPtr> &packets)                                            = 0;
+  virtual void process_ecn(const QUICAckFrame &ack, QUICPacketNumberSpace pn_space, ink_hrtime largest_acked_packet_time_sent) = 0;
+  virtual void on_packets_lost(const std::map<QUICPacketNumber, QUICSentPacketInfoUPtr> &packets)                              = 0;
+  // The function signature is different from the pseudo code because LD takes care of most of the processes
+  virtual void on_packet_number_space_discarded(size_t bytes_in_flight) = 0;
+
+  // These are additional and not on the spec
+  virtual void add_extra_credit()          = 0;
+  virtual void reset()                     = 0;
+  virtual uint32_t credit() const          = 0;
+  virtual uint32_t bytes_in_flight() const = 0;
+
   // Debug
-  virtual uint32_t bytes_in_flight() const   = 0;
   virtual uint32_t congestion_window() const = 0;
   virtual uint32_t current_ssthresh() const  = 0;
 };
diff --git a/iocore/net/quic/QUICConnection.h b/iocore/net/quic/QUICConnection.h
index 57d64ba..02cbbb0 100644
--- a/iocore/net/quic/QUICConnection.h
+++ b/iocore/net/quic/QUICConnection.h
@@ -37,16 +37,32 @@ public:
   virtual ~QUICConnectionInfoProvider() {}
   virtual QUICConnectionId peer_connection_id() const     = 0;
   virtual QUICConnectionId original_connection_id() const = 0;
-  virtual QUICConnectionId first_connection_id() const    = 0;
-  virtual QUICConnectionId connection_id() const          = 0;
-  virtual std::string_view cids() const                   = 0;
-  virtual const QUICFiveTuple five_tuple() const          = 0;
+  /**
+   * This is S1 on 7.3.Authenticating Connection IDs
+   */
+  virtual QUICConnectionId first_connection_id() const = 0;
+  /**
+   * This is S2 on 7.3.Authenticating Connection IDs
+   */
+  virtual QUICConnectionId retry_source_connection_id() const = 0;
+  /**
+   * This is C1 or S3 on 7.3.Authenticating Connection IDs
+   */
+  virtual QUICConnectionId initial_source_connection_id() const = 0;
+  virtual QUICConnectionId connection_id() const                = 0;
+  virtual std::string_view cids() const                         = 0;
+  virtual const QUICFiveTuple five_tuple() const                = 0;
 
   virtual uint32_t pmtu() const                                = 0;
   virtual NetVConnectionContext_t direction() const            = 0;
   virtual int select_next_protocol(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in,
                                    unsigned inlen) const       = 0;
   virtual bool is_closed() const                               = 0;
+  virtual bool is_at_anti_amplification_limit() const          = 0;
+  virtual bool is_address_validation_completed() const         = 0;
+  virtual bool is_handshake_completed() const                  = 0;
+  virtual bool has_keys_for(QUICPacketNumberSpace space) const = 0;
+  virtual QUICVersion negotiated_version() const               = 0;
   virtual std::string_view negotiated_application_name() const = 0;
 };
 
diff --git a/iocore/net/quic/QUICContext.cc b/iocore/net/quic/QUICContext.cc
index 4d0d501..b065f56 100644
--- a/iocore/net/quic/QUICContext.cc
+++ b/iocore/net/quic/QUICContext.cc
@@ -33,12 +33,6 @@ public:
   QUICCCConfigQCP(const QUICConfigParams *params) : _params(params) {}
 
   uint32_t
-  max_datagram_size() const override
-  {
-    return this->_params->cc_max_datagram_size();
-  }
-
-  uint32_t
   initial_window() const override
   {
     return this->_params->cc_initial_window();
@@ -151,4 +145,4 @@ QUICPathManager *
 QUICContext::path_manager() const
 {
   return _path_manager;
-}
\ No newline at end of file
+}
diff --git a/iocore/net/quic/QUICContext.h b/iocore/net/quic/QUICContext.h
index 13f8935..ab2dae9 100644
--- a/iocore/net/quic/QUICContext.h
+++ b/iocore/net/quic/QUICContext.h
@@ -23,6 +23,7 @@
 
 #pragma once
 
+#include "QUICTypes.h"
 #include "QUICConnection.h"
 #include "QUICConfig.h"
 #include "QUICEvents.h"
@@ -54,7 +55,7 @@ public:
   // callback on packet send event
   virtual void packet_send_callback(QUICCallbackContext &, const QUICPacket &p){};
   // callback on packet lost event
-  virtual void packet_lost_callback(QUICCallbackContext &, const QUICPacketInfo &p){};
+  virtual void packet_lost_callback(QUICCallbackContext &, const QUICSentPacketInfo &p){};
   // callback on packet receive event
   virtual void packet_recv_callback(QUICCallbackContext &, const QUICPacket &p){};
   // callback on packet acked event
@@ -128,7 +129,7 @@ public:
   }
 
   void
-  trigger(CallbackEvent e, const QUICPacketInfo &p)
+  trigger(CallbackEvent e, const QUICSentPacketInfo &p)
   {
     QUICCallbackContext ctx;
     for (auto &&it : this->_callbacks) {
diff --git a/iocore/net/quic/QUICDebugNames.cc b/iocore/net/quic/QUICDebugNames.cc
index 6a75032..79d2797 100644
--- a/iocore/net/quic/QUICDebugNames.cc
+++ b/iocore/net/quic/QUICDebugNames.cc
@@ -121,6 +121,8 @@ QUICDebugNames::error_code(uint16_t code)
     return "NO_ERROR";
   case static_cast<uint16_t>(QUICTransErrorCode::INTERNAL_ERROR):
     return "INTERNAL_ERROR";
+  case static_cast<uint16_t>(QUICTransErrorCode::CONNECTION_REFUSED):
+    return "CONNECTION_REFUSED";
   case static_cast<uint16_t>(QUICTransErrorCode::FLOW_CONTROL_ERROR):
     return "FLOW_CONTROL_ERROR";
   case static_cast<uint16_t>(QUICTransErrorCode::STREAM_LIMIT_ERROR):
@@ -139,6 +141,8 @@ QUICDebugNames::error_code(uint16_t code)
     return "PROTOCOL_VIOLATION";
   case static_cast<uint16_t>(QUICTransErrorCode::INVALID_TOKEN):
     return "INVALID_TOKEN";
+  case static_cast<uint16_t>(QUICTransErrorCode::APPLICATION_ERROR):
+    return "APPLICATION_ERROR";
   case static_cast<uint16_t>(QUICTransErrorCode::CRYPTO_BUFFER_EXCEEDED):
     return "CRYPTO_BUFFER_EXCEEDED";
   default:
@@ -189,8 +193,8 @@ QUICDebugNames::transport_parameter_id(QUICTransportParameterId id)
     return "MAX_IDLE_TIMEOUT";
   case QUICTransportParameterId::PREFERRED_ADDRESS:
     return "PREFERRED_ADDRESS";
-  case QUICTransportParameterId::MAX_PACKET_SIZE:
-    return "MAX_PACKET_SIZE";
+  case QUICTransportParameterId::MAX_UDP_PAYLOAD_SIZE:
+    return "MAX_UDP_PAYLOAD_SIZE";
   case QUICTransportParameterId::STATELESS_RESET_TOKEN:
     return "STATELESS_RESET_TOKEN";
   case QUICTransportParameterId::ACK_DELAY_EXPONENT:
@@ -205,10 +209,14 @@ QUICDebugNames::transport_parameter_id(QUICTransportParameterId id)
     return "INITIAL_MAX_STREAM_DATA_UNI";
   case QUICTransportParameterId::MAX_ACK_DELAY:
     return "INITIAL_MAX_ACK_DELAY";
-  case QUICTransportParameterId::ORIGINAL_CONNECTION_ID:
-    return "INITIAL_ORIGINAL_CONNECTION_ID";
+  case QUICTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID:
+    return "INITIAL_ORIGINAL_DESTINATION_CONNECTION_ID";
   case QUICTransportParameterId::ACTIVE_CONNECTION_ID_LIMIT:
     return "ACTIVE_CONNECTION_ID_LIMIT";
+  case QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID:
+    return "INITIAL_SOURCE_CONNECTION_ID";
+  case QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID:
+    return "RETRY_SOURCE_CONNECTION_ID";
   default:
     return "UNKNOWN";
   }
@@ -323,12 +331,12 @@ const char *
 QUICDebugNames::pn_space(QUICPacketNumberSpace pn_space)
 {
   switch (pn_space) {
-  case QUICPacketNumberSpace::Initial:
-    return "QUICPacketNumberSpace::Initial";
-  case QUICPacketNumberSpace::Handshake:
-    return "QUICPacketNumberSpace::Handshake";
-  case QUICPacketNumberSpace::ApplicationData:
-    return "QUICPacketNumberSpace::ApplicationData";
+  case QUICPacketNumberSpace::INITIAL:
+    return "INITIAL";
+  case QUICPacketNumberSpace::HANDSHAKE:
+    return "HANDSHAKE";
+  case QUICPacketNumberSpace::APPLICATION_DATA:
+    return "APPLCIATION_DATA";
   default:
     return "UNKNOWN";
   }
diff --git a/iocore/net/quic/QUICFrame.cc b/iocore/net/quic/QUICFrame.cc
index 7af9a7a..35c14b6 100644
--- a/iocore/net/quic/QUICFrame.cc
+++ b/iocore/net/quic/QUICFrame.cc
@@ -3104,15 +3104,3 @@ QUICFrameFactory::create_handshake_done_frame(uint8_t *buf, QUICFrameId id, QUIC
   new (buf) QUICHandshakeDoneFrame(id, owner);
   return reinterpret_cast<QUICHandshakeDoneFrame *>(buf);
 }
-
-QUICFrameId
-QUICFrameInfo::id() const
-{
-  return this->_id;
-}
-
-QUICFrameGenerator *
-QUICFrameInfo::generated_by() const
-{
-  return this->_generator;
-}
diff --git a/iocore/net/quic/QUICFrame.h b/iocore/net/quic/QUICFrame.h
index 14b87d3..93cd692 100644
--- a/iocore/net/quic/QUICFrame.h
+++ b/iocore/net/quic/QUICFrame.h
@@ -40,8 +40,6 @@ class QUICCryptoFrame;
 class QUICPacketR;
 class QUICFrameGenerator;
 
-using QUICFrameId = uint64_t;
-
 class QUICFrame
 {
 public:
@@ -908,15 +906,3 @@ private:
   uint8_t _buf_for_fast_create[256 * QUICFrame::MAX_INSTANCE_SIZE];
   QUICUnknownFrame _unknown_frame;
 };
-
-class QUICFrameInfo
-{
-public:
-  QUICFrameInfo(QUICFrameId id, QUICFrameGenerator *generator) : _id(id), _generator(generator) {}
-  QUICFrameId id() const;
-  QUICFrameGenerator *generated_by() const;
-
-private:
-  QUICFrameId _id = 0;
-  QUICFrameGenerator *_generator;
-};
diff --git a/iocore/net/quic/QUICFrameDispatcher.cc b/iocore/net/quic/QUICFrameDispatcher.cc
index 9509933..131bc40 100644
--- a/iocore/net/quic/QUICFrameDispatcher.cc
+++ b/iocore/net/quic/QUICFrameDispatcher.cc
@@ -24,9 +24,11 @@
 #include "QUICFrameDispatcher.h"
 #include "QUICDebugNames.h"
 
-static constexpr char tag[] = "quic_net";
+static constexpr char tag[]   = "quic_net";
+static constexpr char v_tag[] = "v_quic_net";
 
 #define QUICDebug(fmt, ...) Debug(tag, "[%s] " fmt, this->_info->cids().data(), ##__VA_ARGS__)
+#define QUICVDebug(fmt, ...) Debug(v_tag, "[%s] " fmt, this->_info->cids().data(), ##__VA_ARGS__)
 
 //
 // Frame Dispatcher
@@ -70,10 +72,10 @@ QUICFrameDispatcher::receive_frames(QUICContext &ctx, QUICEncryptionLevel level,
       is_flow_controlled = true;
     }
 
-    if (is_debug_tag_set(tag) && type != QUICFrameType::PADDING) {
+    if (is_debug_tag_set(v_tag) && type != QUICFrameType::PADDING) {
       char msg[1024];
       frame.debug_msg(msg, sizeof(msg));
-      QUICDebug("[RX] | %s", msg);
+      QUICVDebug("[RX] | %s", msg);
     }
 
     if (type != QUICFrameType::PADDING && type != QUICFrameType::ACK) {
diff --git a/iocore/net/quic/QUICHandshake.cc b/iocore/net/quic/QUICHandshake.cc
index c1a530f..f64f222 100644
--- a/iocore/net/quic/QUICHandshake.cc
+++ b/iocore/net/quic/QUICHandshake.cc
@@ -82,16 +82,20 @@
 
 static constexpr int UDP_MAXIMUM_PAYLOAD_SIZE = 65527;
 
-QUICHandshake::QUICHandshake(QUICConnection *qc, QUICHandshakeProtocol *hsp) : QUICHandshake(qc, hsp, {}, false) {}
+QUICHandshake::QUICHandshake(QUICVersion version, QUICConnection *qc, QUICHandshakeProtocol *hsp)
+  : QUICHandshake(version, qc, hsp, {}, false)
+{
+}
 
-QUICHandshake::QUICHandshake(QUICConnection *qc, QUICHandshakeProtocol *hsp, QUICStatelessResetToken token, bool stateless_retry)
+QUICHandshake::QUICHandshake(QUICVersion version, QUICConnection *qc, QUICHandshakeProtocol *hsp, QUICStatelessResetToken token,
+                             bool stateless_retry)
   : _qc(qc),
     _hs_protocol(hsp),
     _version_negotiator(new QUICVersionNegotiator()),
     _reset_token(token),
     _stateless_retry(stateless_retry)
 {
-  this->_hs_protocol->initialize_key_materials(this->_qc->original_connection_id());
+  this->_hs_protocol->initialize_key_materials(this->_qc->original_connection_id(), version);
 
   if (this->_qc->direction() == NET_VCONNECTION_OUT) {
     this->_client_initial = true;
@@ -108,7 +112,7 @@ QUICHandshake::start(const QUICTPConfig &tp_config, QUICPacketFactory *packet_fa
 {
   QUICVersion initial_version = QUIC_SUPPORTED_VERSIONS[0];
   if (vn_exercise_enabled) {
-    initial_version = QUIC_EXERCISE_VERSION;
+    initial_version = QUIC_EXERCISE_VERSION1;
   }
 
   this->_load_local_client_transport_parameters(tp_config);
@@ -137,6 +141,7 @@ QUICHandshake::start(const QUICTPConfig &tp_config, const QUICInitialPacketR &in
     } else {
       return std::make_unique<QUICConnectionError>(QUICTransErrorCode::PROTOCOL_VIOLATION);
     }
+    this->_initial_source_cid_received = initial_packet.source_cid();
   }
   return nullptr;
 }
@@ -253,6 +258,17 @@ QUICHandshake::_check_remote_transport_parameters(std::shared_ptr<const QUICTran
     return false;
   }
 
+  // Check if CIDs in TP match with the ones in packets
+  if (this->negotiated_version() == QUIC_SUPPORTED_VERSIONS[0]) { // draft-28
+    uint16_t cid_buf_len;
+    const uint8_t *cid_buf = tp->getAsBytes(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID, cid_buf_len);
+    QUICConnectionId cid_in_tp(cid_buf, cid_buf_len);
+    if (cid_in_tp != this->_initial_source_cid_received) {
+      this->_abort_handshake(QUICTransErrorCode::PROTOCOL_VIOLATION);
+      return false;
+    }
+  }
+
   this->_remote_transport_parameters = tp;
 
   return true;
@@ -268,6 +284,26 @@ QUICHandshake::_check_remote_transport_parameters(std::shared_ptr<const QUICTran
     return false;
   }
 
+  // Check if CIDs in TP match with the ones in packets
+  if (this->negotiated_version() == QUIC_SUPPORTED_VERSIONS[0]) { // draft-28
+    uint16_t cid_buf_len;
+    const uint8_t *cid_buf = tp->getAsBytes(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID, cid_buf_len);
+    QUICConnectionId cid_in_tp(cid_buf, cid_buf_len);
+    if (cid_in_tp != this->_initial_source_cid_received) {
+      this->_abort_handshake(QUICTransErrorCode::PROTOCOL_VIOLATION);
+      return false;
+    }
+
+    if (!this->_retry_source_cid_received.is_zero()) {
+      cid_buf = tp->getAsBytes(QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID, cid_buf_len);
+      QUICConnectionId cid_in_tp(cid_buf, cid_buf_len);
+      if (cid_in_tp != this->_retry_source_cid_received) {
+        this->_abort_handshake(QUICTransErrorCode::PROTOCOL_VIOLATION);
+        return false;
+      }
+    }
+  }
+
   this->_remote_transport_parameters = tp;
 
   return true;
@@ -302,6 +338,18 @@ QUICHandshake::reset()
   }
 }
 
+void
+QUICHandshake::update(const QUICInitialPacketR &packet)
+{
+  this->_initial_source_cid_received = packet.source_cid();
+}
+
+void
+QUICHandshake::update(const QUICRetryPacketR &packet)
+{
+  this->_retry_source_cid_received = packet.source_cid();
+}
+
 std::vector<QUICFrameType>
 QUICHandshake::interests()
 {
@@ -386,8 +434,19 @@ QUICHandshake::_load_local_server_transport_parameters(const QUICTPConfig &tp_co
   // MUSTs
   tp->set(QUICTransportParameterId::MAX_IDLE_TIMEOUT, static_cast<uint16_t>(tp_config.no_activity_timeout()));
   if (this->_stateless_retry) {
-    tp->set(QUICTransportParameterId::ORIGINAL_CONNECTION_ID, this->_qc->first_connection_id(),
+    tp->set(QUICTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID, this->_qc->first_connection_id(),
             this->_qc->first_connection_id().length());
+    tp->set(QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID, this->_qc->retry_source_connection_id(),
+            this->_qc->retry_source_connection_id().length());
+  } else {
+    if (this->negotiated_version() == QUIC_SUPPORTED_VERSIONS[0]) { // draft-28
+      tp->set(QUICTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID, this->_qc->original_connection_id(),
+              this->_qc->original_connection_id().length());
+    }
+  }
+  if (this->negotiated_version() == QUIC_SUPPORTED_VERSIONS[0]) { // draft-28
+    tp->set(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID, this->_qc->initial_source_connection_id(),
+            this->_qc->initial_source_connection_id().length());
   }
 
   // MAYs
@@ -426,7 +485,10 @@ QUICHandshake::_load_local_server_transport_parameters(const QUICTPConfig &tp_co
   tp->set(QUICTransportParameterId::STATELESS_RESET_TOKEN, this->_reset_token.buf(), QUICStatelessResetToken::LEN);
   tp->set(QUICTransportParameterId::ACK_DELAY_EXPONENT, tp_config.ack_delay_exponent());
 
-  tp->add_version(QUIC_SUPPORTED_VERSIONS[0]);
+  // Additional parameters
+  for (auto &&param : tp_config.additional_tp()) {
+    tp->set(param.first, param.second.first, param.second.second);
+  }
 
   // Additional parameters
   for (auto &&param : tp_config.additional_tp()) {
@@ -444,6 +506,8 @@ QUICHandshake::_load_local_client_transport_parameters(const QUICTPConfig &tp_co
 
   // MUSTs
   tp->set(QUICTransportParameterId::MAX_IDLE_TIMEOUT, static_cast<uint16_t>(tp_config.no_activity_timeout()));
+  tp->set(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID, this->_qc->initial_source_connection_id(),
+          this->_qc->initial_source_connection_id().length());
 
   // MAYs
   if (tp_config.initial_max_data() != 0) {
diff --git a/iocore/net/quic/QUICHandshake.h b/iocore/net/quic/QUICHandshake.h
index 017c96d..4f55985 100644
--- a/iocore/net/quic/QUICHandshake.h
+++ b/iocore/net/quic/QUICHandshake.h
@@ -42,9 +42,10 @@ class QUICHandshake : public QUICFrameHandler, public QUICFrameGenerator
 {
 public:
   // Constructor for client side
-  QUICHandshake(QUICConnection *qc, QUICHandshakeProtocol *hsp);
+  QUICHandshake(QUICVersion version, QUICConnection *qc, QUICHandshakeProtocol *hsp);
   // Constructor for server side
-  QUICHandshake(QUICConnection *qc, QUICHandshakeProtocol *hsp, QUICStatelessResetToken token, bool stateless_retry);
+  QUICHandshake(QUICVersion version, QUICConnection *qc, QUICHandshakeProtocol *hsp, QUICStatelessResetToken token,
+                bool stateless_retry);
   ~QUICHandshake();
 
   // QUICFrameHandler
@@ -60,6 +61,8 @@ public:
   QUICConnectionErrorUPtr start(const QUICTPConfig &tp_config, QUICPacketFactory *packet_factory, bool vn_exercise_enabled);
   QUICConnectionErrorUPtr negotiate_version(const QUICVersionNegotiationPacketR &packet, QUICPacketFactory *packet_factory);
   void reset();
+  void update(const QUICInitialPacketR &initial_packet);
+  void update(const QUICRetryPacketR &retry_packet);
 
   // for server side
   QUICConnectionErrorUPtr start(const QUICTPConfig &tp_config, const QUICInitialPacketR &initial_packet,
@@ -91,8 +94,10 @@ private:
 
   QUICVersionNegotiator *_version_negotiator = nullptr;
   QUICStatelessResetToken _reset_token;
-  bool _client_initial  = false;
-  bool _stateless_retry = false;
+  bool _client_initial                          = false;
+  bool _stateless_retry                         = false;
+  QUICConnectionId _initial_source_cid_received = QUICConnectionId::ZERO();
+  QUICConnectionId _retry_source_cid_received   = QUICConnectionId::ZERO();
 
   QUICCryptoStream _crypto_streams[4];
 
diff --git a/iocore/net/quic/QUICHandshakeProtocol.h b/iocore/net/quic/QUICHandshakeProtocol.h
index 0c7c09b..8bc6e77 100644
--- a/iocore/net/quic/QUICHandshakeProtocol.h
+++ b/iocore/net/quic/QUICHandshakeProtocol.h
@@ -47,7 +47,7 @@ public:
   virtual void reset()                                                                    = 0;
   virtual bool is_handshake_finished() const                                              = 0;
   virtual bool is_ready_to_derive() const                                                 = 0;
-  virtual int initialize_key_materials(QUICConnectionId cid)                              = 0;
+  virtual int initialize_key_materials(QUICConnectionId cid, QUICVersion)                 = 0;
   virtual const char *negotiated_cipher_suite() const                                     = 0;
   virtual void negotiated_application_name(const uint8_t **name, unsigned int *len) const = 0;
 
diff --git a/iocore/net/quic/QUICKeyGenerator.cc b/iocore/net/quic/QUICKeyGenerator.cc
index 24c2b84..7ac32ba 100644
--- a/iocore/net/quic/QUICKeyGenerator.cc
+++ b/iocore/net/quic/QUICKeyGenerator.cc
@@ -34,6 +34,9 @@
 using namespace std::literals;
 
 constexpr static uint8_t QUIC_VERSION_1_SALT[] = {
+  0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99,
+};
+constexpr static uint8_t QUIC_VERSION_1_D27_SALT[] = {
   0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, 0xd2, 0x43, 0x2b, 0xb4, 0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02,
 };
 constexpr static std::string_view LABEL_FOR_CLIENT_INITIAL_SECRET("client in"sv);
@@ -43,7 +46,7 @@ constexpr static std::string_view LABEL_FOR_IV("quic iv"sv);
 constexpr static std::string_view LABEL_FOR_HP("quic hp"sv);
 
 void
-QUICKeyGenerator::generate(uint8_t *hp_key, uint8_t *pp_key, uint8_t *iv, size_t *iv_len, QUICConnectionId cid)
+QUICKeyGenerator::generate(QUICVersion version, uint8_t *hp_key, uint8_t *pp_key, uint8_t *iv, size_t *iv_len, QUICConnectionId cid)
 {
   const EVP_CIPHER *cipher = this->_get_cipher_for_initial();
   const EVP_MD *md         = EVP_sha256();
@@ -53,7 +56,7 @@ QUICKeyGenerator::generate(uint8_t *hp_key, uint8_t *pp_key, uint8_t *iv, size_t
 
   switch (this->_ctx) {
   case Context::CLIENT:
-    this->_generate_initial_secret(secret, &secret_len, hkdf, cid, LABEL_FOR_CLIENT_INITIAL_SECRET.data(),
+    this->_generate_initial_secret(version, secret, &secret_len, hkdf, cid, LABEL_FOR_CLIENT_INITIAL_SECRET.data(),
                                    LABEL_FOR_CLIENT_INITIAL_SECRET.length(), EVP_MD_size(md));
     if (is_debug_tag_set("vv_quic_crypto")) {
       uint8_t print_buf[1024 + 1];
@@ -63,7 +66,7 @@ QUICKeyGenerator::generate(uint8_t *hp_key, uint8_t *pp_key, uint8_t *iv, size_t
 
     break;
   case Context::SERVER:
-    this->_generate_initial_secret(secret, &secret_len, hkdf, cid, LABEL_FOR_SERVER_INITIAL_SECRET.data(),
+    this->_generate_initial_secret(version, secret, &secret_len, hkdf, cid, LABEL_FOR_SERVER_INITIAL_SECRET.data(),
                                    LABEL_FOR_SERVER_INITIAL_SECRET.length(), EVP_MD_size(md));
     if (is_debug_tag_set("vv_quic_crypto")) {
       uint8_t print_buf[1024 + 1];
@@ -101,18 +104,33 @@ QUICKeyGenerator::_generate(uint8_t *hp_key, uint8_t *pp_key, uint8_t *iv, size_
 }
 
 int
-QUICKeyGenerator::_generate_initial_secret(uint8_t *out, size_t *out_len, QUICHKDF &hkdf, QUICConnectionId cid, const char *label,
-                                           size_t label_len, size_t length)
+QUICKeyGenerator::_generate_initial_secret(QUICVersion version, uint8_t *out, size_t *out_len, QUICHKDF &hkdf, QUICConnectionId cid,
+                                           const char *label, size_t label_len, size_t length)
 {
   uint8_t client_connection_id[QUICConnectionId::MAX_LENGTH];
   size_t cid_len = 0;
   uint8_t initial_secret[512];
   size_t initial_secret_len = sizeof(initial_secret);
+  const uint8_t *salt;
+  size_t salt_len;
 
   // TODO: do not extract initial secret twice
   QUICTypeUtil::write_QUICConnectionId(cid, client_connection_id, &cid_len);
-  if (hkdf.extract(initial_secret, &initial_secret_len, QUIC_VERSION_1_SALT, sizeof(QUIC_VERSION_1_SALT), client_connection_id,
-                   cid.length()) != 1) {
+  switch (version) {
+  case 0xff00001d: // Draft-29
+    salt     = QUIC_VERSION_1_SALT;
+    salt_len = sizeof(QUIC_VERSION_1_SALT);
+    break;
+  case 0xff00001b: // Draft-27
+    salt     = QUIC_VERSION_1_D27_SALT;
+    salt_len = sizeof(QUIC_VERSION_1_D27_SALT);
+    break;
+  default:
+    salt     = QUIC_VERSION_1_SALT;
+    salt_len = sizeof(QUIC_VERSION_1_SALT);
+    break;
+  }
+  if (hkdf.extract(initial_secret, &initial_secret_len, salt, salt_len, client_connection_id, cid.length()) != 1) {
     return -1;
   }
 
diff --git a/iocore/net/quic/QUICKeyGenerator.h b/iocore/net/quic/QUICKeyGenerator.h
index c2ff89f..db2924c 100644
--- a/iocore/net/quic/QUICKeyGenerator.h
+++ b/iocore/net/quic/QUICKeyGenerator.h
@@ -43,7 +43,7 @@ public:
    * Generate keys for Initial encryption level
    * The keys for the remaining encryption level are derived by TLS stack with "quic " prefix
    */
-  void generate(uint8_t *hp_key, uint8_t *pp_key, uint8_t *iv, size_t *iv_len, QUICConnectionId cid);
+  void generate(QUICVersion version, uint8_t *hp_key, uint8_t *pp_key, uint8_t *iv, size_t *iv_len, QUICConnectionId cid);
 
   void regenerate(uint8_t *hp_key, uint8_t *pp_key, uint8_t *iv, size_t *iv_len, const uint8_t *secret, size_t secret_len,
                   const EVP_CIPHER *cipher, QUICHKDF &hkdf);
@@ -56,8 +56,8 @@ private:
 
   int _generate(uint8_t *hp_key, uint8_t *pp_key, uint8_t *iv, size_t *iv_len, QUICHKDF &hkdf, const uint8_t *secret,
                 size_t secret_len, const EVP_CIPHER *cipher);
-  int _generate_initial_secret(uint8_t *out, size_t *out_len, QUICHKDF &hkdf, QUICConnectionId cid, const char *label,
-                               size_t label_len, size_t length);
+  int _generate_initial_secret(QUICVersion version, uint8_t *out, size_t *out_len, QUICHKDF &hkdf, QUICConnectionId cid,
+                               const char *label, size_t label_len, size_t length);
   int _generate_key(uint8_t *out, size_t *out_len, QUICHKDF &hkdf, const uint8_t *secret, size_t secret_len,
                     size_t key_length) const;
   int _generate_iv(uint8_t *out, size_t *out_len, QUICHKDF &hkdf, const uint8_t *secret, size_t secret_len, size_t iv_length) const;
diff --git a/iocore/net/quic/QUICLossDetector.cc b/iocore/net/quic/QUICLossDetector.cc
index 3d381b2..a52ec68 100644
--- a/iocore/net/quic/QUICLossDetector.cc
+++ b/iocore/net/quic/QUICLossDetector.cc
@@ -61,7 +61,7 @@ QUICLossDetector::~QUICLossDetector()
     this->_loss_detection_timer = nullptr;
   }
 
-  for (auto i = 0; i < kPacketNumberSpace; i++) {
+  for (auto i = 0; i < QUIC_N_PACKET_SPACES; i++) {
     this->_sent_packets[i].clear();
   }
 }
@@ -125,32 +125,30 @@ QUICLossDetector::largest_acked_packet_number(QUICPacketNumberSpace pn_space) co
 }
 
 void
-QUICLossDetector::on_packet_sent(QUICPacketInfoUPtr packet_info, bool in_flight)
+QUICLossDetector::on_packet_sent(QUICSentPacketInfoUPtr packet_info, bool in_flight)
 {
+  // ADDITIONAL CODE
   if (packet_info->type == QUICPacketType::VERSION_NEGOTIATION) {
     return;
   }
+  // END OF ADDITIONAL CODE
 
   SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread());
 
   QUICPacketNumber packet_number = packet_info->packet_number;
   bool ack_eliciting             = packet_info->ack_eliciting;
-  bool is_crypto_packet          = packet_info->is_crypto_packet;
   ink_hrtime now                 = packet_info->time_sent;
   size_t sent_bytes              = packet_info->sent_bytes;
+  QUICPacketNumberSpace pn_space = packet_info->pn_space;
 
-  QUICLDDebug("%s packet sent : %" PRIu64 " bytes: %lu ack_eliciting: %d", QUICDebugNames::pn_space(packet_info->pn_space),
-              packet_number, sent_bytes, ack_eliciting);
+  QUICLDVDebug("%s packet sent : %" PRIu64 " bytes: %lu ack_eliciting: %d", QUICDebugNames::pn_space(packet_info->pn_space),
+               packet_number, sent_bytes, ack_eliciting);
 
   this->_add_to_sent_packet_list(packet_number, std::move(packet_info));
 
   if (in_flight) {
-    if (is_crypto_packet) {
-      this->_time_of_last_sent_crypto_packet = now;
-    }
-
     if (ack_eliciting) {
-      this->_time_of_last_sent_ack_eliciting_packet = now;
+      this->_time_of_last_ack_eliciting_packet[static_cast<int>(pn_space)] = now;
     }
     this->_cc->on_packet_sent(sent_bytes);
     this->_set_loss_detection_timer();
@@ -158,27 +156,60 @@ QUICLossDetector::on_packet_sent(QUICPacketInfoUPtr packet_info, bool in_flight)
 }
 
 void
+QUICLossDetector::on_datagram_received()
+{
+  if (this->_context.connection_info()->is_at_anti_amplification_limit()) {
+    this->_set_loss_detection_timer();
+  }
+}
+
+void
+QUICLossDetector::on_packet_number_space_discarded(QUICPacketNumberSpace pn_space)
+{
+  ink_assert(pn_space != QUICPacketNumberSpace::APPLICATION_DATA);
+  size_t bytes_in_flight = 0;
+  for (auto it = this->_sent_packets[static_cast<int>(pn_space)].begin();
+       it != this->_sent_packets[static_cast<int>(pn_space)].end();) {
+    auto ret = this->_remove_from_sent_packet_list(it, pn_space);
+    auto &pi = ret.first;
+    if (pi->in_flight) {
+      bytes_in_flight += pi->sent_bytes;
+    }
+    it = ret.second;
+  }
+  this->_cc->on_packet_number_space_discarded(bytes_in_flight);
+  // Reset the loss detection and PTO timer
+  this->_time_of_last_ack_eliciting_packet[static_cast<int>(pn_space)] = 0;
+  this->_loss_time[static_cast<int>(pn_space)]                         = 0;
+  this->_rtt_measure->set_pto_count(0);
+  this->_set_loss_detection_timer();
+  QUICLDDebug("[%s] Packets have been discarded because keys for the space are discarded", QUICDebugNames::pn_space(pn_space));
+}
+
+void
 QUICLossDetector::reset()
 {
   SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread());
+
+  // A.4.  Initialization
   if (this->_loss_detection_timer) {
     this->_loss_detection_timer->cancel();
     this->_loss_detection_timer = nullptr;
   }
-
-  this->_ack_eliciting_outstanding = 0;
-  this->_crypto_outstanding        = 0;
-
-  // [draft-17 recovery] 6.4.3.  Initialization
-  this->_time_of_last_sent_ack_eliciting_packet = 0;
-  this->_time_of_last_sent_crypto_packet        = 0;
-  for (auto i = 0; i < kPacketNumberSpace; i++) {
-    this->_largest_acked_packet[i] = 0;
-    this->_loss_time[i]            = 0;
+  this->_rtt_measure->reset();
+  for (auto i = 0; i < QUIC_N_PACKET_SPACES; i++) {
+    this->_largest_acked_packet[i]              = UINT64_MAX;
+    this->_time_of_last_ack_eliciting_packet[i] = 0;
+    this->_loss_time[i]                         = 0;
     this->_sent_packets[i].clear();
+    //  ADDITIONAL CODE
+    this->_num_packets_in_flight[i] = 0;
+    // END OF ADDITIONAL CODE
   }
 
-  this->_rtt_measure->reset();
+  //  ADDITIONAL CODE
+  this->_ack_eliciting_outstanding = 0;
+  // END OF ADDITIONAL CODE
 }
 
 void
@@ -188,12 +219,12 @@ QUICLossDetector::update_ack_delay_exponent(uint8_t ack_delay_exponent)
 }
 
 bool
-QUICLossDetector::_include_ack_eliciting(const std::vector<QUICPacketInfo *> &acked_packets, int index) const
+QUICLossDetector::_include_ack_eliciting(const std::vector<QUICSentPacketInfoUPtr> &acked_packets) const
 {
   // Find out ack_elicting packet.
   // FIXME: this loop is the same as _on_ack_received's loop it would better
   // to combine it.
-  for (auto packet : acked_packets) {
+  for (const auto &packet : acked_packets) {
     if (packet->ack_eliciting) {
       return true;
     }
@@ -207,65 +238,68 @@ QUICLossDetector::_on_ack_received(const QUICAckFrame &ack_frame, QUICPacketNumb
 {
   SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread());
 
-  int index                          = static_cast<int>(pn_space);
-  this->_largest_acked_packet[index] = std::max(this->_largest_acked_packet[index], ack_frame.largest_acknowledged());
+  int index = static_cast<int>(pn_space);
+  if (this->_largest_acked_packet[index] == UINT64_MAX) {
+    this->_largest_acked_packet[index] = ack_frame.largest_acknowledged();
+  } else {
+    this->_largest_acked_packet[index] = std::max(this->_largest_acked_packet[index], ack_frame.largest_acknowledged());
+  }
 
-  auto newly_acked_packets = this->_determine_newly_acked_packets(ack_frame, index);
+  auto newly_acked_packets = this->_detect_and_remove_acked_packets(ack_frame, pn_space);
   if (newly_acked_packets.empty()) {
     return;
   }
 
   // If the largest acknowledged is newly acked and
   //  ack-eliciting, update the RTT.
-  auto pi = this->_sent_packets[index].find(ack_frame.largest_acknowledged());
-  if (pi != this->_sent_packets[index].end() &&
-      (pi->second->ack_eliciting || this->_include_ack_eliciting(newly_acked_packets, index))) {
-    ink_hrtime latest_rtt = Thread::get_hrtime() - pi->second->time_sent;
+  const auto &largest_acked = newly_acked_packets[0];
+  if (largest_acked->packet_number == ack_frame.largest_acknowledged() && this->_include_ack_eliciting(newly_acked_packets)) {
+    ink_hrtime latest_rtt = Thread::get_hrtime() - largest_acked->time_sent;
     // _latest_rtt is nanosecond but ack_frame.ack_delay is microsecond and scaled
-    ink_hrtime delay = HRTIME_USECONDS(ack_frame.ack_delay() << this->_ack_delay_exponent);
-    this->_rtt_measure->update_rtt(latest_rtt, delay);
+    ink_hrtime ack_delay = 0;
+    if (pn_space == QUICPacketNumberSpace::APPLICATION_DATA) {
+      ack_delay = HRTIME_USECONDS(ack_frame.ack_delay() << this->_ack_delay_exponent);
+    }
+    this->_rtt_measure->update_rtt(latest_rtt, ack_delay);
   }
 
-  QUICLDVDebug("[%s] Unacked packets %lu (retransmittable %u, includes %u handshake packets)", QUICDebugNames::pn_space(pn_space),
-               this->_sent_packets[index].size(), this->_ack_eliciting_outstanding.load(), this->_crypto_outstanding.load());
-
   // if (ACK frame contains ECN information):
   //   ProcessECN(ack)
-  if (ack_frame.ecn_section() != nullptr && pi != this->_sent_packets[index].end()) {
-    this->_cc->process_ecn(*pi->second, ack_frame.ecn_section());
+  if (ack_frame.ecn_section() != nullptr) {
+    this->_cc->process_ecn(ack_frame, pn_space, largest_acked->time_sent);
   }
 
+  // ADDITIONAL CODE
   // Find all newly acked packets.
-  for (auto info : newly_acked_packets) {
+  for (const auto &info : newly_acked_packets) {
     this->_on_packet_acked(*info);
   }
+  // END OF ADDITIONAL CODE
 
-  QUICLDVDebug("[%s] Unacked packets %lu (retransmittable %u, includes %u handshake packets)", QUICDebugNames::pn_space(pn_space),
-               this->_sent_packets[index].size(), this->_ack_eliciting_outstanding.load(), this->_crypto_outstanding.load());
-
-  this->_detect_lost_packets(pn_space);
-
-  this->_rtt_measure->set_crypto_count(0);
-  this->_rtt_measure->set_pto_count(0);
+  auto lost_packets = this->_detect_and_remove_lost_packets(pn_space);
+  if (!lost_packets.empty()) {
+    this->_cc->on_packets_lost(lost_packets);
+  }
+  this->_cc->on_packets_acked(newly_acked_packets);
 
-  QUICLDDebug("[%s] Unacked packets %lu (retransmittable %u, includes %u handshake packets)", QUICDebugNames::pn_space(pn_space),
-              this->_sent_packets[index].size(), this->_ack_eliciting_outstanding.load(), this->_crypto_outstanding.load());
+  QUICLDVDebug("[%s] Newly acked:%lu Lost:%lu Unacked packets:%lu (%u ack eliciting)", QUICDebugNames::pn_space(pn_space),
+               newly_acked_packets.size(), lost_packets.size(), this->_sent_packets[index].size(),
+               this->_ack_eliciting_outstanding.load());
 
+  if (this->_peer_completed_address_validation()) {
+    this->_rtt_measure->set_pto_count(0);
+  }
   this->_set_loss_detection_timer();
 }
 
 void
-QUICLossDetector::_on_packet_acked(const QUICPacketInfo &acked_packet)
+QUICLossDetector::_on_packet_acked(const QUICSentPacketInfo &acked_packet)
 {
   SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread());
-  QUICLDDebug("[%s] Packet number %" PRIu64 " has been acked", QUICDebugNames::pn_space(acked_packet.pn_space),
-              acked_packet.packet_number);
-
-  if (acked_packet.in_flight) {
-    this->_cc->on_packet_acked(acked_packet);
-  }
+  QUICLDVDebug("[%s] Packet number %" PRIu64 " has been acked", QUICDebugNames::pn_space(acked_packet.pn_space),
+               acked_packet.packet_number);
 
-  for (const QUICFrameInfo &frame_info : acked_packet.frames) {
+  for (const QUICSentPacketInfo::FrameInfo &frame_info : acked_packet.frames) {
     auto reactor = frame_info.generated_by();
     if (reactor == nullptr) {
       continue;
@@ -273,17 +307,15 @@ QUICLossDetector::_on_packet_acked(const QUICPacketInfo &acked_packet)
 
     reactor->on_frame_acked(frame_info.id());
   }
-
-  this->_remove_from_sent_packet_list(acked_packet.packet_number, acked_packet.pn_space);
 }
 
 ink_hrtime
-QUICLossDetector::_get_earliest_loss_time(QUICPacketNumberSpace &pn_space)
+QUICLossDetector::_get_loss_time_and_space(QUICPacketNumberSpace &pn_space)
 {
-  ink_hrtime time = this->_loss_time[static_cast<int>(QUICPacketNumberSpace::Initial)];
-  pn_space        = QUICPacketNumberSpace::Initial;
-  for (auto i = 1; i < kPacketNumberSpace; i++) {
-    if (this->_loss_time[i] != 0 && (time != 0 || this->_loss_time[i] < time)) {
+  ink_hrtime time = this->_loss_time[static_cast<int>(QUICPacketNumberSpace::INITIAL)];
+  pn_space        = QUICPacketNumberSpace::INITIAL;
+  for (auto i = 1; i < QUIC_N_PACKET_SPACES; i++) {
+    if (time == 0 || this->_loss_time[i] < time) {
       time     = this->_loss_time[i];
       pn_space = static_cast<QUICPacketNumberSpace>(i);
     }
@@ -292,6 +324,59 @@ QUICLossDetector::_get_earliest_loss_time(QUICPacketNumberSpace &pn_space)
   return time;
 }
 
+ink_hrtime
+QUICLossDetector::_get_pto_time_and_space(QUICPacketNumberSpace &space)
+{
+  ink_hrtime duration =
+    (this->_rtt_measure->smoothed_rtt() + std::max(4 * this->_rtt_measure->rttvar(), this->_rtt_measure->k_granularity())) *
+    (1 << this->_rtt_measure->pto_count());
+
+  // Arm PTO from now when there are no inflight packets.
+  if (this->_num_packets_in_flight[static_cast<int>(QUICPacketNumberSpace::INITIAL)].load() == 0 &&
+      this->_num_packets_in_flight[static_cast<int>(QUICPacketNumberSpace::HANDSHAKE)].load() == 0 &&
+      this->_num_packets_in_flight[static_cast<int>(QUICPacketNumberSpace::APPLICATION_DATA)].load() == 0) {
+    ink_assert(!this->_peer_completed_address_validation());
+    if (this->_context.connection_info()->has_keys_for(QUICPacketNumberSpace::HANDSHAKE)) {
+      space = QUICPacketNumberSpace::HANDSHAKE;
+      return Thread::get_hrtime() + duration;
+    } else {
+      space = QUICPacketNumberSpace::INITIAL;
+      return Thread::get_hrtime() + duration;
+    }
+  }
+  ink_hrtime pto_timeout          = INT64_MAX;
+  QUICPacketNumberSpace pto_space = QUICPacketNumberSpace::INITIAL;
+  for (int i = 0; i < QUIC_N_PACKET_SPACES; ++i) {
+    if (this->_num_packets_in_flight[i].load() == 0) {
+      continue;
+    }
+    if (i == static_cast<int>(QUICPacketNumberSpace::APPLICATION_DATA)) {
+      // Skip ApplicationData until handshake complete.
+      if (!this->_context.connection_info()->is_address_validation_completed()) {
+        space = pto_space;
+        return pto_timeout;
+      }
+      // Include max_ack_delay and backoff for ApplicationData.
+      // FIXME should be set by transport parameters
+      duration += this->_rtt_measure->max_ack_delay() * (1 << this->_rtt_measure->pto_count());
+    }
+
+    ink_hrtime t = this->_time_of_last_ack_eliciting_packet[i] + duration;
+    if (t < pto_timeout) {
+      pto_timeout = t;
+      pto_space   = QUICPacketNumberSpace(i);
+    }
+  }
+  space = pto_space;
+  return pto_timeout;
+}
+
+bool
+QUICLossDetector::_peer_completed_address_validation() const
+{
+  return this->_context.connection_info()->is_address_validation_completed();
+}
+
 void
 QUICLossDetector::_set_loss_detection_timer()
 {
@@ -302,112 +387,115 @@ QUICLossDetector::_set_loss_detection_timer()
     }
   };
 
+  std::function<void(void)> cancel_timer = [this]() {
+    this->_loss_detection_alarm_at = 0;
+    this->_loss_detection_timer->cancel();
+    this->_loss_detection_timer = nullptr;
+  };
+
   QUICPacketNumberSpace pn_space;
-  ink_hrtime alarm = this->_get_earliest_loss_time(pn_space);
-  if (alarm != 0) {
-    update_timer(alarm);
+  ink_hrtime earliest_loss_time = this->_get_loss_time_and_space(pn_space);
+  if (earliest_loss_time != 0) {
+    update_timer(earliest_loss_time);
     QUICLDDebug("[%s] time threshold loss detection timer: %" PRId64 "ms", QUICDebugNames::pn_space(pn_space),
                 (this->_loss_detection_alarm_at - Thread::get_hrtime()) / HRTIME_MSECOND);
     return;
   }
 
-  if (this->_crypto_outstanding > 0 || this->_is_client_without_one_rtt_key()) {
-    // Crypto retransmission timer.
-    alarm = this->_time_of_last_sent_crypto_packet + this->_rtt_measure->handshake_retransmit_timeout();
-    update_timer(alarm);
-    QUICLDDebug("%s crypto packet alarm will be set: %" PRId64 "ms", QUICDebugNames::pn_space(pn_space),
-                (alarm - this->_time_of_last_sent_crypto_packet) / HRTIME_MSECOND);
-    return;
+  if (this->_context.connection_info()->is_at_anti_amplification_limit()) {
+    if (this->_loss_detection_timer) {
+      cancel_timer();
+      QUICLDDebug("Loss detection alarm has been unset because of anti-amplification limit");
+      return;
+    }
   }
 
   // Don't arm the alarm if there are no packets with retransmittable data in flight.
-  // -- MODIFIED CODE --
-  // In pseudocode, `bytes_in_flight` is used, but we're tracking "retransmittable data in flight" by `_ack_eliciting_outstanding`
-  if (this->_ack_eliciting_outstanding == 0) {
+  if (this->_ack_eliciting_outstanding == 0 && this->_peer_completed_address_validation()) {
     if (this->_loss_detection_timer) {
-      this->_loss_detection_alarm_at = 0;
-      this->_loss_detection_timer->cancel();
-      this->_loss_detection_timer = nullptr;
-      QUICLDDebug("Loss detection alarm has been unset");
+      cancel_timer();
+      QUICLDDebug("Loss detection alarm has been unset because of no ack eliciting packets outstanding");
     }
-
     return;
   }
-  // -- END OF MODIFIED CODE --
 
   // PTO Duration
-  alarm = this->_time_of_last_sent_ack_eliciting_packet + this->_rtt_measure->current_pto_period();
-  update_timer(alarm);
-  QUICLDDebug("[%s] PTO timeout will be set: %" PRId64 "ms", QUICDebugNames::pn_space(pn_space),
-              (alarm - this->_time_of_last_sent_ack_eliciting_packet) / HRTIME_MSECOND);
+  ink_hrtime timeout = this->_get_pto_time_and_space(pn_space);
+  update_timer(timeout);
+  QUICLDVDebug("[%s] PTO timeout has been set: %" PRId64 "ms", QUICDebugNames::pn_space(pn_space),
+               (timeout - this->_time_of_last_ack_eliciting_packet[static_cast<int>(pn_space)]) / HRTIME_MSECOND);
 }
 
 void
 QUICLossDetector::_on_loss_detection_timeout()
 {
   QUICPacketNumberSpace pn_space;
-  ink_hrtime loss_time = this->_get_earliest_loss_time(pn_space);
-  if (loss_time != 0) {
+  ink_hrtime earliest_loss_time = this->_get_loss_time_and_space(pn_space);
+  if (earliest_loss_time != 0) {
     // Time threshold loss Detection
-    this->_detect_lost_packets(pn_space);
-  } else if (this->_crypto_outstanding) {
-    // Handshake retransmission alarm.
-    QUICLDVDebug("Crypto Retranmission");
-    this->_retransmit_all_unacked_crypto_data();
-    this->_rtt_measure->set_crypto_count(this->_rtt_measure->crypto_count() + 1);
-  } else if (this->_is_client_without_one_rtt_key()) {
+    auto lost_packets = this->_detect_and_remove_lost_packets(pn_space);
+    ink_assert(!lost_packets.empty());
+    this->_cc->on_packets_lost(lost_packets);
+    this->_set_loss_detection_timer();
+    return;
+  }
+
+  if (this->_cc->bytes_in_flight() > 0) {
+    // PTO. Send new data if available, else retransmit old data.
+    // If neither is available, send a single PING frame.
+    QUICPacketNumberSpace pns;
+    this->_get_pto_time_and_space(pns);
+    this->_send_one_or_two_ack_eliciting_packet(pns);
+  } else {
+    ink_assert(this->_is_client_without_one_rtt_key());
     // Client sends an anti-deadlock packet: Initial is padded
     // to earn more anti-amplification credit,
     // a Handshake packet proves address ownership.
     if (this->_context.key_info()->is_encryption_key_available(QUICKeyPhase::HANDSHAKE)) {
-      this->_send_one_handshake_packets();
+      this->_send_one_ack_eliciting_handshake_packet();
     } else {
-      this->_send_one_padded_packets();
+      this->_send_one_ack_eliciting_padded_initial_packet();
     }
-
-    this->_rtt_measure->set_crypto_count(this->_rtt_measure->crypto_count() + 1);
-  } else {
-    QUICLDVDebug("PTO");
-    this->_send_one_or_two_packet();
-    this->_rtt_measure->set_pto_count(this->_rtt_measure->pto_count() + 1);
   }
 
-  QUICLDDebug("[%s] Unacked packets %lu (retransmittable %u, includes %u handshake packets)", QUICDebugNames::pn_space(pn_space),
-              this->_sent_packets[static_cast<int>(pn_space)].size(), this->_ack_eliciting_outstanding.load(),
-              this->_crypto_outstanding.load());
+  this->_rtt_measure->set_pto_count(this->_rtt_measure->pto_count() + 1);
+  this->_set_loss_detection_timer();
+
+  QUICLDDebug("[%s] Unacked packets %lu (ack_eliciting %u)", QUICDebugNames::pn_space(pn_space),
+              this->_sent_packets[static_cast<int>(pn_space)].size(), this->_ack_eliciting_outstanding.load());
 
   if (is_debug_tag_set("v_quic_loss_detector")) {
     for (auto i = 0; i < 3; i++) {
       for (auto &unacked : this->_sent_packets[i]) {
-        QUICLDVDebug("[%s] #%" PRIu64 " is_crypto=%i ack_eliciting=%i size=%zu %u %u",
-                     QUICDebugNames::pn_space(static_cast<QUICPacketNumberSpace>(i)), unacked.first,
-                     unacked.second->is_crypto_packet, unacked.second->ack_eliciting, unacked.second->sent_bytes,
-                     this->_ack_eliciting_outstanding.load(), this->_crypto_outstanding.load());
+        QUICLDVDebug("[%s] #%" PRIu64 " ack_eliciting=%i size=%zu %u",
+                     QUICDebugNames::pn_space(static_cast<QUICPacketNumberSpace>(i)), unacked.first, unacked.second->ack_eliciting,
+                     unacked.second->sent_bytes, this->_ack_eliciting_outstanding.load());
       }
     }
   }
-
-  this->_set_loss_detection_timer();
 }
 
-void
-QUICLossDetector::_detect_lost_packets(QUICPacketNumberSpace pn_space)
+std::map<QUICPacketNumber, QUICSentPacketInfoUPtr>
+QUICLossDetector::_detect_and_remove_lost_packets(QUICPacketNumberSpace pn_space)
 {
   SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread());
+  ink_assert(this->_largest_acked_packet[static_cast<int>(pn_space)] != UINT64_MAX);
+
   this->_loss_time[static_cast<int>(pn_space)] = 0;
+  std::map<QUICPacketNumber, QUICSentPacketInfoUPtr> lost_packets;
   ink_hrtime loss_delay = this->_k_time_threshold * std::max(this->_rtt_measure->latest_rtt(), this->_rtt_measure->smoothed_rtt());
-  loss_delay            = std::min(loss_delay, this->_rtt_measure->k_granularity());
 
-  std::map<QUICPacketNumber, QUICPacketInfo *> lost_packets;
+  // Minimum time of kGranularity before packets are deemed lost.
+  loss_delay = std::max(loss_delay, this->_rtt_measure->k_granularity());
 
   // Packets sent before this time are deemed lost.
   ink_hrtime lost_send_time = Thread::get_hrtime() - loss_delay;
 
   // Packets with packet numbers before this are deemed lost.
-  QUICPacketNumber lost_pn = this->_largest_acked_packet[static_cast<int>(pn_space)] - this->_k_packet_threshold;
+  //  QUICPacketNumber lost_pn = this->_largest_acked_packet[static_cast<int>(pn_space)] - this->_k_packet_threshold;
 
   for (auto it = this->_sent_packets[static_cast<int>(pn_space)].begin();
-       it != this->_sent_packets[static_cast<int>(pn_space)].end(); ++it) {
+       it != this->_sent_packets[static_cast<int>(pn_space)].end();) {
     if (it->first > this->_largest_acked_packet[static_cast<int>(pn_space)]) {
       // the spec uses continue but we can break here because the _sent_packets is sorted by packet_number.
       break;
@@ -416,8 +504,9 @@ QUICLossDetector::_detect_lost_packets(QUICPacketNumberSpace pn_space)
     auto &unacked = it->second;
 
     // Mark packet as lost, or set time when it should be marked.
-    if (unacked->time_sent < lost_send_time || unacked->packet_number < lost_pn) {
-      if (unacked->time_sent < lost_send_time) {
+    if (unacked->time_sent <= lost_send_time ||
+        this->_largest_acked_packet[static_cast<int>(pn_space)] >= unacked->packet_number + this->_k_packet_threshold) {
+      if (unacked->time_sent <= lost_send_time) {
         QUICLDDebug("[%s] Lost: time since sent is too long (#%" PRId64 " sent=%" PRId64 ", delay=%" PRId64
                     ", fraction=%lf, lrtt=%" PRId64 ", srtt=%" PRId64 ")",
                     QUICDebugNames::pn_space(pn_space), it->first, unacked->time_sent, lost_send_time, this->_k_time_threshold,
@@ -428,61 +517,42 @@ QUICLossDetector::_detect_lost_packets(QUICPacketNumberSpace pn_space)
                     this->_k_packet_threshold);
       }
 
-      if (unacked->in_flight) {
-        lost_packets.insert({it->first, it->second.get()});
+      auto ret = this->_remove_from_sent_packet_list(it, pn_space);
+      auto pi  = std::move(ret.first);
+      it       = ret.second;
+      if (pi->in_flight) {
+        this->_context.trigger(QUICContext::CallbackEvent::PACKET_LOST, *pi);
+        lost_packets.emplace(pi->packet_number, std::move(pi));
       }
-    } else if (this->_loss_time[static_cast<int>(pn_space)] == 0) {
-      this->_loss_time[static_cast<int>(pn_space)] = unacked->time_sent + loss_delay;
+
     } else {
-      this->_loss_time[static_cast<int>(pn_space)] =
-        std::min(this->_loss_time[static_cast<int>(pn_space)], unacked->time_sent + loss_delay);
+      if (this->_loss_time[static_cast<int>(pn_space)] == 0) {
+        this->_loss_time[static_cast<int>(pn_space)] = unacked->time_sent + loss_delay;
+      } else {
+        this->_loss_time[static_cast<int>(pn_space)] =
+          std::min(this->_loss_time[static_cast<int>(pn_space)], unacked->time_sent + loss_delay);
+      }
+      ++it;
     }
   }
 
-  // Inform the congestion controller of lost packets and
-  // lets it decide whether to retransmit immediately.
+  // -- ADDITIONAL CODE --
+  // Not sure how we can get feedback from congestion control and when we should retransmit the lost packets but we need to send
+  // them somewhere.
+  // I couldn't find the place so just send them here for now.
   if (!lost_packets.empty()) {
-    this->_cc->on_packets_lost(lost_packets);
-    for (auto lost_packet : lost_packets) {
-      this->_context.trigger(QUICContext::CallbackEvent::PACKET_LOST, *lost_packet.second);
-      // -- ADDITIONAL CODE --
-      // Not sure how we can get feedback from congestion control and when we should retransmit the lost packets but we need to send
-      // them somewhere.
-      // I couldn't find the place so just send them here for now.
+    for (const auto &lost_packet : lost_packets) {
       this->_retransmit_lost_packet(*lost_packet.second);
-      // -- END OF ADDITIONAL CODE --
-      // -- ADDITIONAL CODE --
-      this->_remove_from_sent_packet_list(lost_packet.first, pn_space);
-      // -- END OF ADDITIONAL CODE --
     }
   }
+  // -- END OF ADDITIONAL CODE --
+
+  return lost_packets;
 }
 
 // ===== Functions below are used on the spec but there're no pseudo code  =====
 
 void
-QUICLossDetector::_retransmit_all_unacked_crypto_data()
-{
-  SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread());
-  for (auto i = 0; i < kPacketNumberSpace; i++) {
-    std::set<QUICPacketNumber> retransmitted_crypto_packets;
-    std::map<QUICPacketNumber, QUICPacketInfo *> lost_packets;
-    for (auto &info : this->_sent_packets[i]) {
-      if (info.second->is_crypto_packet) {
-        retransmitted_crypto_packets.insert(info.first);
-        this->_retransmit_lost_packet(*info.second);
-        lost_packets.insert({info.first, info.second.get()});
-      }
-    }
-
-    this->_cc->on_packets_lost(lost_packets);
-    for (auto packet_number : retransmitted_crypto_packets) {
-      this->_remove_from_sent_packet_list(packet_number, static_cast<QUICPacketNumberSpace>(i));
-    }
-  }
-}
-
-void
 QUICLossDetector::_send_packet(QUICEncryptionLevel level, bool padded)
 {
   if (padded) {
@@ -494,7 +564,7 @@ QUICLossDetector::_send_packet(QUICEncryptionLevel level, bool padded)
 }
 
 void
-QUICLossDetector::_send_one_or_two_packet()
+QUICLossDetector::_send_one_or_two_ack_eliciting_packet(QUICPacketNumberSpace pn_space)
 {
   this->_send_packet(QUICEncryptionLevel::ONE_RTT);
   this->_send_packet(QUICEncryptionLevel::ONE_RTT);
@@ -504,7 +574,7 @@ QUICLossDetector::_send_one_or_two_packet()
 }
 
 void
-QUICLossDetector::_send_one_handshake_packets()
+QUICLossDetector::_send_one_ack_eliciting_handshake_packet()
 {
   this->_send_packet(QUICEncryptionLevel::HANDSHAKE);
   QUICLDDebug("[%s] send handshake packet: ping count=%" PRIu64, QUICDebugNames::encryption_level(QUICEncryptionLevel::HANDSHAKE),
@@ -512,7 +582,7 @@ QUICLossDetector::_send_one_handshake_packets()
 }
 
 void
-QUICLossDetector::_send_one_padded_packets()
+QUICLossDetector::_send_one_ack_eliciting_padded_initial_packet()
 {
   this->_send_packet(QUICEncryptionLevel::INITIAL, true);
   QUICLDDebug("[%s] send PADDING frame: ping count=%" PRIu64, QUICDebugNames::encryption_level(QUICEncryptionLevel::INITIAL),
@@ -522,12 +592,12 @@ QUICLossDetector::_send_one_padded_packets()
 // ===== Functions below are helper functions =====
 
 void
-QUICLossDetector::_retransmit_lost_packet(const QUICPacketInfo &packet_info)
+QUICLossDetector::_retransmit_lost_packet(const QUICSentPacketInfo &packet_info)
 {
   SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread());
 
   QUICLDDebug("Retransmit %s packet #%" PRIu64, QUICDebugNames::packet_type(packet_info.type), packet_info.packet_number);
-  for (const QUICFrameInfo &frame_info : packet_info.frames) {
+  for (const QUICSentPacketInfo::FrameInfo &frame_info : packet_info.frames) {
     auto reactor = frame_info.generated_by();
     if (reactor == nullptr) {
       continue;
@@ -537,11 +607,13 @@ QUICLossDetector::_retransmit_lost_packet(const QUICPacketInfo &packet_info)
   }
 }
 
-std::vector<QUICPacketInfo *>
-QUICLossDetector::_determine_newly_acked_packets(const QUICAckFrame &ack_frame, int pn_space)
+std::vector<QUICSentPacketInfoUPtr>
+QUICLossDetector::_detect_and_remove_acked_packets(const QUICAckFrame &ack_frame, QUICPacketNumberSpace pn_space)
 {
-  std::vector<QUICPacketInfo *> packets;
+  std::vector<QUICSentPacketInfoUPtr> packets;
   std::set<QUICAckFrame::PacketNumberRange> numbers;
+  int index = static_cast<int>(pn_space);
+
   QUICPacketNumber x = ack_frame.largest_acknowledged();
   numbers.insert({x, static_cast<uint64_t>(x) - ack_frame.ack_block_section()->first_ack_block()});
   x -= ack_frame.ack_block_section()->first_ack_block() + 1;
@@ -552,9 +624,13 @@ QUICLossDetector::_determine_newly_acked_packets(const QUICAckFrame &ack_frame,
   }
 
   for (auto &&range : numbers) {
-    for (auto ite = this->_sent_packets[pn_space].rbegin(); ite != this->_sent_packets[pn_space].rend(); ite++) {
+    for (auto ite = this->_sent_packets[index].begin(); ite != this->_sent_packets[index].end();) {
       if (range.contains(ite->first)) {
-        packets.push_back(ite->second.get());
+        auto ret = this->_remove_from_sent_packet_list(ite, pn_space);
+        packets.push_back(std::move(ret.first));
+        ite = ret.second;
+      } else {
+        ++ite;
       }
     }
   }
@@ -563,62 +639,48 @@ QUICLossDetector::_determine_newly_acked_packets(const QUICAckFrame &ack_frame,
 }
 
 void
-QUICLossDetector::_add_to_sent_packet_list(QUICPacketNumber packet_number, QUICPacketInfoUPtr packet_info)
+QUICLossDetector::_add_to_sent_packet_list(QUICPacketNumber packet_number, QUICSentPacketInfoUPtr packet_info)
 {
   SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread());
 
   // Add to the list
   int index = static_cast<int>(packet_info->pn_space);
-  this->_sent_packets[index].insert(std::pair<QUICPacketNumber, QUICPacketInfoUPtr>(packet_number, std::move(packet_info)));
+  this->_sent_packets[index].insert(std::pair<QUICPacketNumber, QUICSentPacketInfoUPtr>(packet_number, std::move(packet_info)));
 
   // Increment counters
   auto ite = this->_sent_packets[index].find(packet_number);
   if (ite != this->_sent_packets[index].end()) {
-    if (ite->second->is_crypto_packet) {
-      ++this->_crypto_outstanding;
-      ink_assert(this->_crypto_outstanding.load() > 0);
-    }
     if (ite->second->ack_eliciting) {
       ++this->_ack_eliciting_outstanding;
       ink_assert(this->_ack_eliciting_outstanding.load() > 0);
     }
+    if (ite->second->in_flight) {
+      ++this->_num_packets_in_flight[index];
+    }
   }
 }
 
-void
-QUICLossDetector::_remove_from_sent_packet_list(QUICPacketNumber packet_number, QUICPacketNumberSpace pn_space)
-{
-  SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread());
-
-  auto ite = this->_sent_packets[static_cast<int>(pn_space)].find(packet_number);
-  this->_decrement_outstanding_counters(ite, pn_space);
-  this->_sent_packets[static_cast<int>(pn_space)].erase(packet_number);
-}
-
-std::map<QUICPacketNumber, QUICPacketInfoUPtr>::iterator
-QUICLossDetector::_remove_from_sent_packet_list(std::map<QUICPacketNumber, QUICPacketInfoUPtr>::iterator it,
+std::pair<QUICSentPacketInfoUPtr, std::map<QUICPacketNumber, QUICSentPacketInfoUPtr>::iterator>
+QUICLossDetector::_remove_from_sent_packet_list(std::map<QUICPacketNumber, QUICSentPacketInfoUPtr>::iterator it,
                                                 QUICPacketNumberSpace pn_space)
 {
   SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread());
 
-  this->_decrement_outstanding_counters(it, pn_space);
-  return this->_sent_packets[static_cast<int>(pn_space)].erase(it);
+  this->_decrement_counters(it, pn_space);
+  auto pi = std::move(it->second);
+  return {std::move(pi), this->_sent_packets[static_cast<int>(pn_space)].erase(it)};
 }
 
 void
-QUICLossDetector::_decrement_outstanding_counters(std::map<QUICPacketNumber, QUICPacketInfoUPtr>::iterator it,
-                                                  QUICPacketNumberSpace pn_space)
+QUICLossDetector::_decrement_counters(std::map<QUICPacketNumber, QUICSentPacketInfoUPtr>::iterator it,
+                                      QUICPacketNumberSpace pn_space)
 {
   if (it != this->_sent_packets[static_cast<int>(pn_space)].end()) {
-    // Decrement counters
-    if (it->second->is_crypto_packet) {
-      ink_assert(this->_crypto_outstanding.load() > 0);
-      --this->_crypto_outstanding;
-    }
     if (it->second->ack_eliciting) {
       ink_assert(this->_ack_eliciting_outstanding.load() > 0);
       --this->_ack_eliciting_outstanding;
     }
+    --this->_num_packets_in_flight[static_cast<int>(pn_space)];
   }
 }
 
@@ -658,10 +720,11 @@ QUICRTTMeasure::update_rtt(ink_hrtime latest_rtt, ink_hrtime ack_delay)
 {
   this->_latest_rtt = latest_rtt;
 
-  if (this->_smoothed_rtt == 0) {
-    this->_min_rtt      = 0;
-    this->_smoothed_rtt = this->_latest_rtt;
-    this->_rttvar       = this->_latest_rtt / 2;
+  if (this->_is_first_sample) {
+    this->_min_rtt         = this->_latest_rtt;
+    this->_smoothed_rtt    = this->_latest_rtt;
+    this->_rttvar          = this->_latest_rtt / 2;
+    this->_is_first_sample = false;
     return;
   }
 
@@ -698,32 +761,16 @@ QUICRTTMeasure::congestion_period(uint32_t threshold) const
   return pto * threshold;
 }
 
-ink_hrtime
-QUICRTTMeasure::handshake_retransmit_timeout() const
-{
-  // Handshake retransmission alarm.
-  ink_hrtime timeout = 0;
-  if (this->_smoothed_rtt == 0) {
-    timeout = 2 * this->_k_initial_rtt;
-  } else {
-    timeout = 2 * this->_smoothed_rtt;
-  }
-  timeout = std::max(timeout, this->_k_granularity);
-  timeout = timeout * (1 << this->_crypto_count);
-
-  return timeout;
-}
-
 void
-QUICRTTMeasure::set_crypto_count(uint32_t count)
+QUICRTTMeasure::set_pto_count(uint32_t count)
 {
-  this->_crypto_count = count;
+  this->_pto_count = count;
 }
 
 void
-QUICRTTMeasure::set_pto_count(uint32_t count)
+QUICRTTMeasure::set_max_ack_delay(ink_hrtime max_ack_delay)
 {
-  this->_pto_count = count;
+  this->_max_ack_delay = max_ack_delay;
 }
 
 ink_hrtime
@@ -739,15 +786,15 @@ QUICRTTMeasure::latest_rtt() const
 }
 
 uint32_t
-QUICRTTMeasure::crypto_count() const
+QUICRTTMeasure::pto_count() const
 {
-  return this->_crypto_count;
+  return this->_pto_count;
 }
 
-uint32_t
-QUICRTTMeasure::pto_count() const
+ink_hrtime
+QUICRTTMeasure::max_ack_delay() const
 {
-  return this->_pto_count;
+  return this->_max_ack_delay;
 }
 
 ink_hrtime
@@ -759,10 +806,10 @@ QUICRTTMeasure::k_granularity() const
 void
 QUICRTTMeasure::reset()
 {
-  this->_crypto_count = 0;
+  // A.4.  Initialization
   this->_pto_count    = 0;
-  this->_smoothed_rtt = 0;
-  this->_rttvar       = 0;
-  this->_min_rtt      = 0;
   this->_latest_rtt   = 0;
+  this->_smoothed_rtt = this->_k_initial_rtt;
+  this->_rttvar       = this->_k_initial_rtt / 2.0;
+  this->_min_rtt      = 0;
 }
diff --git a/iocore/net/quic/QUICLossDetector.h b/iocore/net/quic/QUICLossDetector.h
index 891556e..5ed1c22 100644
--- a/iocore/net/quic/QUICLossDetector.h
+++ b/iocore/net/quic/QUICLossDetector.h
@@ -44,70 +44,6 @@ class QUICPinger;
 class QUICLossDetector;
 class QUICRTTMeasure;
 
-using QUICPacketInfoUPtr = std::unique_ptr<QUICPacketInfo>;
-
-class QUICRTTProvider
-{
-public:
-  virtual ink_hrtime smoothed_rtt() const = 0;
-  virtual ink_hrtime rttvar() const       = 0;
-  virtual ink_hrtime latest_rtt() const   = 0;
-
-  virtual ink_hrtime congestion_period(uint32_t threshold) const = 0;
-};
-
-class QUICNewRenoCongestionController : public QUICCongestionController
-{
-public:
-  QUICNewRenoCongestionController(QUICContext &context);
-  virtual ~QUICNewRenoCongestionController() {}
-  void on_packet_sent(size_t bytes_sent) override;
-  void on_packet_acked(const QUICPacketInfo &acked_packet) override;
-  virtual void on_packets_lost(const std::map<QUICPacketNumber, QUICPacketInfo *> &packets) override;
-  void process_ecn(const QUICPacketInfo &acked_largest_packet, const QUICAckFrame::EcnSection *ecn_section) override;
-  bool check_credit() const;
-  uint32_t credit() const override;
-  void reset() override;
-  bool is_app_limited();
-
-  // Debug
-  uint32_t bytes_in_flight() const override;
-  uint32_t congestion_window() const override;
-  uint32_t current_ssthresh() const override;
-
-  void add_extra_credit() override;
-
-private:
-  Ptr<ProxyMutex> _cc_mutex;
-
-  void _congestion_event(ink_hrtime sent_time);
-  bool _in_persistent_congestion(const std::map<QUICPacketNumber, QUICPacketInfo *> &lost_packets,
-                                 QUICPacketInfo *largest_lost_packet);
-  bool _in_window_lost(const std::map<QUICPacketNumber, QUICPacketInfo *> &lost_packets, QUICPacketInfo *largest_lost_packet,
-                       ink_hrtime period) const;
-
-  uint32_t _extra_packets_count = 0;
-
-  // [draft-17 recovery] 7.9.1. Constants of interest
-  // Values will be loaded from records.config via QUICConfig at constructor
-  uint32_t _k_max_datagram_size               = 0;
-  uint32_t _k_initial_window                  = 0;
-  uint32_t _k_minimum_window                  = 0;
-  float _k_loss_reduction_factor              = 0.0;
-  uint32_t _k_persistent_congestion_threshold = 3;
-
-  // [draft-17 recovery] 7.9.2. Variables of interest
-  uint32_t _ecn_ce_counter                   = 0;
-  uint32_t _bytes_in_flight                  = 0;
-  uint32_t _congestion_window                = 0;
-  ink_hrtime _congestion_recovery_start_time = 0;
-  uint32_t _ssthresh                         = UINT32_MAX;
-
-  bool _in_congestion_recovery(ink_hrtime sent_time) const;
-
-  QUICContext &_context;
-};
-
 class QUICLossDetector : public Continuation, public QUICFrameHandler
 {
 public:
@@ -117,9 +53,15 @@ public:
 
   int event_handler(int event, Event *edata);
 
+  // QUICFrameHandler interface
   std::vector<QUICFrameType> interests() override;
   virtual QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override;
-  void on_packet_sent(QUICPacketInfoUPtr packet_info, bool in_flight = true);
+
+  void on_packet_sent(QUICSentPacketInfoUPtr packet_info, bool in_flight = true);
+  void on_datagram_received();
+  // OnPacketNumberSpaceDiscarded is on Congestion Control section but having it here makes more sense because most processes are
+  // for LD.
+  void on_packet_number_space_discarded(QUICPacketNumberSpace pn_space);
   QUICPacketNumber largest_acked_packet_number(QUICPacketNumberSpace pn_space) const;
   void update_ack_delay_exponent(uint8_t ack_delay_exponent);
   void reset();
@@ -129,29 +71,32 @@ private:
 
   uint8_t _ack_delay_exponent = 3;
 
-  // [draft-17 recovery] 6.4.1.  Constants of interest
+  // Recovery A.2. Constants of Interest
   // Values will be loaded from records.config via QUICConfig at constructor
   uint32_t _k_packet_threshold = 0;
   float _k_time_threshold      = 0.0;
+  // kGranularity, kInitialRtt are defined in QUICRTTMeasure
+  QUICRTTMeasure *_rtt_measure = nullptr;
+  // kPacketNumberSpace is defined as QUICPacketNumberSpace on QUICTypes.h
 
-  // [draft-11 recovery] 3.5.2.  Variables of interest
+  // Recovery A.3. Variables of interest
   // Keep the order as the same as the spec so that we can see the difference easily.
-  Action *_loss_detection_timer                              = nullptr;
-  ink_hrtime _time_of_last_sent_ack_eliciting_packet         = 0;
-  ink_hrtime _time_of_last_sent_crypto_packet                = 0;
-  ink_hrtime _loss_time[kPacketNumberSpace]                  = {0};
-  QUICPacketNumber _largest_acked_packet[kPacketNumberSpace] = {0};
-  std::map<QUICPacketNumber, QUICPacketInfoUPtr> _sent_packets[kPacketNumberSpace];
+  // latest_rtt, smoothed_rtt, rttvar, min_rtt and max_ack_delay are defined in QUICRttMeasure
+  Action *_loss_detection_timer = nullptr;
+  // pto_count is defined in QUICRttMeasure
+  ink_hrtime _time_of_last_ack_eliciting_packet[QUIC_N_PACKET_SPACES] = {0};
+  QUICPacketNumber _largest_acked_packet[QUIC_N_PACKET_SPACES]        = {0};
+  ink_hrtime _loss_time[QUIC_N_PACKET_SPACES]                         = {0};
+  std::map<QUICPacketNumber, QUICSentPacketInfoUPtr> _sent_packets[QUIC_N_PACKET_SPACES];
 
   // These are not defined on the spec but expected to be count
   // These counter have to be updated when inserting / erasing packets from _sent_packets with following functions.
-  std::atomic<uint32_t> _crypto_outstanding;
   std::atomic<uint32_t> _ack_eliciting_outstanding;
-  void _add_to_sent_packet_list(QUICPacketNumber packet_number, std::unique_ptr<QUICPacketInfo> packet_info);
-  void _remove_from_sent_packet_list(QUICPacketNumber packet_number, QUICPacketNumberSpace pn_space);
-  std::map<QUICPacketNumber, QUICPacketInfoUPtr>::iterator _remove_from_sent_packet_list(
-    std::map<QUICPacketNumber, QUICPacketInfoUPtr>::iterator it, QUICPacketNumberSpace pn_space);
-  void _decrement_outstanding_counters(std::map<QUICPacketNumber, QUICPacketInfoUPtr>::iterator it, QUICPacketNumberSpace pn_space);
+  std::atomic<uint32_t> _num_packets_in_flight[QUIC_N_PACKET_SPACES];
+  void _add_to_sent_packet_list(QUICPacketNumber packet_number, std::unique_ptr<QUICSentPacketInfo> packet_info);
+  std::pair<QUICSentPacketInfoUPtr, std::map<QUICPacketNumber, QUICSentPacketInfoUPtr>::iterator> _remove_from_sent_packet_list(
+    std::map<QUICPacketNumber, QUICSentPacketInfoUPtr>::iterator it, QUICPacketNumberSpace pn_space);
+  void _decrement_counters(std::map<QUICPacketNumber, QUICSentPacketInfoUPtr>::iterator it, QUICPacketNumberSpace pn_space);
 
   /*
    * Because this alarm will be reset on every packet transmission, to reduce number of events,
@@ -160,27 +105,28 @@ private:
   ink_hrtime _loss_detection_alarm_at = 0;
 
   void _on_ack_received(const QUICAckFrame &ack_frame, QUICPacketNumberSpace pn_space);
-  void _on_packet_acked(const QUICPacketInfo &acked_packet);
-  void _detect_lost_packets(QUICPacketNumberSpace pn_space);
+  void _on_packet_acked(const QUICSentPacketInfo &acked_packet);
+  std::map<QUICPacketNumber, QUICSentPacketInfoUPtr> _detect_and_remove_lost_packets(QUICPacketNumberSpace pn_space);
   void _set_loss_detection_timer();
   void _on_loss_detection_timeout();
-  void _retransmit_lost_packet(const QUICPacketInfo &packet_info);
+  void _retransmit_lost_packet(const QUICSentPacketInfo &packet_info);
 
-  ink_hrtime _get_earliest_loss_time(QUICPacketNumberSpace &pn_space);
+  ink_hrtime _get_loss_time_and_space(QUICPacketNumberSpace &space);
+  ink_hrtime _get_pto_time_and_space(QUICPacketNumberSpace &space);
+  bool _peer_completed_address_validation() const;
 
-  std::vector<QUICPacketInfo *> _determine_newly_acked_packets(const QUICAckFrame &ack_frame, int pn_space);
-  bool _include_ack_eliciting(const std::vector<QUICPacketInfo *> &acked_packets, int index) const;
+  std::vector<QUICSentPacketInfoUPtr> _detect_and_remove_acked_packets(const QUICAckFrame &ack_frame,
+                                                                       QUICPacketNumberSpace pn_space);
+  bool _include_ack_eliciting(const std::vector<QUICSentPacketInfoUPtr> &acked_packets) const;
 
-  void _retransmit_all_unacked_crypto_data();
-  void _send_one_or_two_packet();
-  void _send_one_handshake_packets();
-  void _send_one_padded_packets();
+  void _send_one_or_two_ack_eliciting_packet(QUICPacketNumberSpace pn_space);
+  void _send_one_ack_eliciting_handshake_packet();
+  void _send_one_ack_eliciting_padded_initial_packet();
 
   void _send_packet(QUICEncryptionLevel level, bool padded = false);
 
   bool _is_client_without_one_rtt_key() const;
 
-  QUICRTTMeasure *_rtt_measure  = nullptr;
   QUICPinger *_pinger           = nullptr;
   QUICPadder *_padder           = nullptr;
   QUICCongestionController *_cc = nullptr;
@@ -200,7 +146,6 @@ public:
   void init(const QUICLDConfig &ld_config);
 
   // period
-  ink_hrtime handshake_retransmit_timeout() const;
   ink_hrtime current_pto_period() const;
   ink_hrtime congestion_period(uint32_t threshold) const override;
 
@@ -210,10 +155,10 @@ public:
   ink_hrtime latest_rtt() const override;
 
   uint32_t pto_count() const;
-  uint32_t crypto_count() const;
+  ink_hrtime max_ack_delay() const;
 
-  void set_crypto_count(uint32_t count);
   void set_pto_count(uint32_t count);
+  void set_max_ack_delay(ink_hrtime max_ack_delay);
 
   void update_rtt(ink_hrtime latest_rtt, ink_hrtime ack_delay);
   void reset();
@@ -221,19 +166,18 @@ public:
   ink_hrtime k_granularity() const;
 
 private:
-  // related to rtt calculate
-  uint32_t _crypto_count = 0;
-  uint32_t _pto_count    = 0;
-  // FIXME should be set by transport parameters
-  ink_hrtime _max_ack_delay = HRTIME_MSECONDS(25);
+  bool _is_first_sample = false;
 
-  // rtt vars
+  // A.3. Variables of interest
   ink_hrtime _latest_rtt   = 0;
   ink_hrtime _smoothed_rtt = 0;
   ink_hrtime _rttvar       = 0;
   ink_hrtime _min_rtt      = INT64_MAX;
+  // FIXME should be set by transport parameters
+  ink_hrtime _max_ack_delay = HRTIME_MSECONDS(25);
+  uint32_t _pto_count       = 0;
 
-  // config
+  // Recovery A.2.  Constants of Interest
   ink_hrtime _k_granularity = 0;
   ink_hrtime _k_initial_rtt = HRTIME_MSECONDS(500);
 };
diff --git a/iocore/net/quic/QUICNewRenoCongestionController.cc b/iocore/net/quic/QUICNewRenoCongestionController.cc
index 2658443..7745670 100644
--- a/iocore/net/quic/QUICNewRenoCongestionController.cc
+++ b/iocore/net/quic/QUICNewRenoCongestionController.cc
@@ -22,19 +22,26 @@
  */
 
 #include <tscore/Diags.h>
-#include <QUICLossDetector.h>
+#include <QUICCongestionController.h>
+#include <QUICNewRenoCongestionController.h>
 
 #define QUICCCDebug(fmt, ...)                                                                                               \
   Debug("quic_cc",                                                                                                          \
         "[%s] "                                                                                                             \
-        "window: %" PRIu32 " bytes: %" PRIu32 " ssthresh: %" PRIu32 " extra: %" PRIu32 " " fmt,                             \
+        "window:%" PRIu32 " in-flight:%" PRIu32 " ssthresh:%" PRIu32 " extra:%" PRIu32 " " fmt,                             \
+        this->_context.connection_info()->cids().data(), this->_congestion_window, this->_bytes_in_flight, this->_ssthresh, \
+        this->_extra_packets_count, ##__VA_ARGS__)
+#define QUICCCVDebug(fmt, ...)                                                                                              \
+  Debug("v_quic_cc",                                                                                                        \
+        "[%s] "                                                                                                             \
+        "window:%" PRIu32 " in-flight:%" PRIu32 " ssthresh:%" PRIu32 " extra:%" PRIu32 " " fmt,                             \
         this->_context.connection_info()->cids().data(), this->_congestion_window, this->_bytes_in_flight, this->_ssthresh, \
         this->_extra_packets_count, ##__VA_ARGS__)
 
 #define QUICCCError(fmt, ...)                                                                                               \
   Error("quic_cc",                                                                                                          \
         "[%s] "                                                                                                             \
-        "window: %" PRIu32 " bytes: %" PRIu32 " ssthresh: %" PRIu32 " extra %" PRIu32 " " fmt,                              \
+        "window:%" PRIu32 " in-flight:%" PRIu32 " ssthresh:%" PRIu32 " extra:%" PRIu32 " " fmt,                             \
         this->_context.connection_info()->cids().data(), this->_congestion_window, this->_bytes_in_flight, this->_ssthresh, \
         this->_extra_packets_count, ##__VA_ARGS__)
 
@@ -42,7 +49,6 @@ QUICNewRenoCongestionController::QUICNewRenoCongestionController(QUICContext &co
   : _cc_mutex(new_ProxyMutex()), _context(context)
 {
   auto &cc_config                          = context.cc_config();
-  this->_k_max_datagram_size               = cc_config.max_datagram_size();
   this->_k_initial_window                  = cc_config.initial_window();
   this->_k_minimum_window                  = cc_config.minimum_window();
   this->_k_loss_reduction_factor           = cc_config.loss_reduction_factor();
@@ -69,46 +75,42 @@ QUICNewRenoCongestionController::_in_congestion_recovery(ink_hrtime sent_time) c
 }
 
 bool
-QUICNewRenoCongestionController::is_app_limited()
+QUICNewRenoCongestionController::_is_app_or_flow_control_limited()
 {
   // FIXME : don't known how does app worked here
   return false;
 }
 
 void
-QUICNewRenoCongestionController::on_packet_acked(const QUICPacketInfo &acked_packet)
+QUICNewRenoCongestionController::_maybe_send_one_packet()
 {
-  // Remove from bytes_in_flight.
-  SCOPED_MUTEX_LOCK(lock, this->_cc_mutex, this_ethread());
-  this->_bytes_in_flight -= acked_packet.sent_bytes;
-  if (this->_in_congestion_recovery(acked_packet.time_sent)) {
-    // Do not increase congestion window in recovery period.
-    return;
-  }
+  // TODO Implement _maybe_send_one_packet
+}
 
-  if (this->is_app_limited()) {
-    // Do not increase congestion_window if application
-    // limited.
-    return;
-  }
+bool
+QUICNewRenoCongestionController::_are_all_packets_lost(const std::map<QUICPacketNumber, QUICSentPacketInfoUPtr> &lost_packets,
+                                                       const QUICSentPacketInfoUPtr &largest_lost_packet, ink_hrtime period) const
+{
+  // check whether packets are continuous. return true if all continuous packets are in period
+  QUICPacketNumber next_expected = UINT64_MAX;
+  for (auto &it : lost_packets) {
+    if (it.second->time_sent >= largest_lost_packet->time_sent - period) {
+      if (next_expected == UINT64_MAX) {
+        next_expected = it.second->packet_number + 1;
+        continue;
+      }
 
-  if (this->_congestion_window < this->_ssthresh) {
-    // Slow start.
-    this->_context.trigger(QUICContext::CallbackEvent::CONGESTION_STATE_CHANGED, QUICCongestionController::State::SLOW_START);
-    this->_congestion_window += acked_packet.sent_bytes;
-    QUICCCDebug("slow start window changed");
-  } else {
-    // Congestion avoidance.
-    this->_context.trigger(QUICContext::CallbackEvent::CONGESTION_STATE_CHANGED,
-                           QUICCongestionController::State::CONGESTION_AVOIDANCE);
-    this->_congestion_window += this->_k_max_datagram_size * acked_packet.sent_bytes / this->_congestion_window;
-    QUICCCDebug("Congestion avoidance window changed");
+      if (next_expected != it.second->packet_number) {
+        return false;
+      }
+
+      next_expected = it.second->packet_number + 1;
+    }
   }
+
+  return next_expected == UINT64_MAX ? false : true;
 }
 
-// additional code
-// the original one is:
-//   CongestionEvent(sent_time):
 void
 QUICNewRenoCongestionController::_congestion_event(ink_hrtime sent_time)
 {
@@ -122,54 +124,82 @@ QUICNewRenoCongestionController::_congestion_event(ink_hrtime sent_time)
     this->_context.trigger(QUICContext::CallbackEvent::CONGESTION_STATE_CHANGED, QUICCongestionController::State::RECOVERY);
     this->_context.trigger(QUICContext::CallbackEvent::METRICS_UPDATE, this->_congestion_window, this->_bytes_in_flight,
                            this->_ssthresh);
+    // A packet can be sent to speed up loss recovery.
+    this->_maybe_send_one_packet();
   }
 }
 
-// additional code
-// the original one is:
-//   ProcessECN(ack):
 void
-QUICNewRenoCongestionController::process_ecn(const QUICPacketInfo &acked_largest_packet,
-                                             const QUICAckFrame::EcnSection *ecn_section)
+QUICNewRenoCongestionController::process_ecn(const QUICAckFrame &ack_frame, QUICPacketNumberSpace pn_space,
+                                             ink_hrtime largest_acked_time_sent)
 {
   // If the ECN-CE counter reported by the peer has increased,
   // this could be a new congestion event.
-  if (ecn_section->ecn_ce_count() > this->_ecn_ce_counter) {
-    this->_ecn_ce_counter = ecn_section->ecn_ce_count();
+  if (ack_frame.ecn_section()->ecn_ce_count() > this->_ecn_ce_counters[static_cast<int>(pn_space)]) {
+    this->_ecn_ce_counters[static_cast<int>(pn_space)] = ack_frame.ecn_section()->ecn_ce_count();
     // Start a new congestion event if the last acknowledged
     // packet was sent after the start of the previous
     // recovery epoch.
-    this->_congestion_event(acked_largest_packet.time_sent);
+    this->_congestion_event(largest_acked_time_sent);
   }
 }
 
 bool
-QUICNewRenoCongestionController::_in_persistent_congestion(const std::map<QUICPacketNumber, QUICPacketInfo *> &lost_packets,
-                                                           QUICPacketInfo *largest_lost_packet)
+QUICNewRenoCongestionController::_in_persistent_congestion(const std::map<QUICPacketNumber, QUICSentPacketInfoUPtr> &lost_packets,
+                                                           const QUICSentPacketInfoUPtr &largest_lost_packet)
 {
-  ink_hrtime period = this->_context.rtt_provider()->congestion_period(this->_k_persistent_congestion_threshold);
-  // Determine if all packets in the window before the
-  // newest lost packet, including the edges, are marked
-  // lost
-  return this->_in_window_lost(lost_packets, largest_lost_packet, period);
+  ink_hrtime congestion_period = this->_context.rtt_provider()->congestion_period(this->_k_persistent_congestion_threshold);
+  // Determine if all packets in the time period before the
+  // largest newly lost packet, including the edges, are
+  // marked lost
+  return this->_are_all_packets_lost(lost_packets, largest_lost_packet, congestion_period);
+}
+
+void
+QUICNewRenoCongestionController::on_packets_acked(const std::vector<QUICSentPacketInfoUPtr> &packets)
+{
+  SCOPED_MUTEX_LOCK(lock, this->_cc_mutex, this_ethread());
+
+  for (auto &packet : packets) {
+    // Remove from bytes_in_flight.
+    this->_bytes_in_flight -= packet->sent_bytes;
+    if (this->_in_congestion_recovery(packet->time_sent)) {
+      // Do not increase congestion window in recovery period.
+      continue;
+    }
+    if (this->_is_app_or_flow_control_limited()) {
+      // Do not increase congestion_window if application
+      // limited or flow control limited.
+      continue;
+    }
+    if (this->_congestion_window < this->_ssthresh) {
+      // Slow start.
+      this->_context.trigger(QUICContext::CallbackEvent::CONGESTION_STATE_CHANGED, QUICCongestionController::State::SLOW_START);
+      this->_congestion_window += packet->sent_bytes;
+      QUICCCVDebug("slow start window changed");
+      continue;
+    }
+    // Congestion avoidance.
+    this->_context.trigger(QUICContext::CallbackEvent::CONGESTION_STATE_CHANGED,
+                           QUICCongestionController::State::CONGESTION_AVOIDANCE);
+    this->_congestion_window += this->_max_datagram_size * static_cast<double>(packet->sent_bytes) / this->_congestion_window;
+    QUICCCVDebug("Congestion avoidance window changed");
+  }
 }
 
 // additional code
 // the original one is:
 //   OnPacketsLost(lost_packets):
 void
-QUICNewRenoCongestionController::on_packets_lost(const std::map<QUICPacketNumber, QUICPacketInfo *> &lost_packets)
+QUICNewRenoCongestionController::on_packets_lost(const std::map<QUICPacketNumber, QUICSentPacketInfoUPtr> &lost_packets)
 {
-  if (lost_packets.empty()) {
-    return;
-  }
-
   SCOPED_MUTEX_LOCK(lock, this->_cc_mutex, this_ethread());
+
   // Remove lost packets from bytes_in_flight.
   for (auto &lost_packet : lost_packets) {
     this->_bytes_in_flight -= lost_packet.second->sent_bytes;
   }
-  QUICPacketInfo *largest_lost_packet = lost_packets.rbegin()->second;
+  const auto &largest_lost_packet = lost_packets.rbegin()->second;
   this->_congestion_event(largest_lost_packet->time_sent);
 
   // Collapse congestion window if persistent congestion
@@ -178,8 +208,14 @@ QUICNewRenoCongestionController::on_packets_lost(const std::map<QUICPacketNumber
   }
 }
 
+void
+QUICNewRenoCongestionController::on_packet_number_space_discarded(size_t bytes_in_flight)
+{
+  this->_bytes_in_flight -= bytes_in_flight;
+}
+
 bool
-QUICNewRenoCongestionController::check_credit() const
+QUICNewRenoCongestionController::_check_credit() const
 {
   if (this->_bytes_in_flight >= this->_congestion_window) {
     QUICCCDebug("Congestion control pending");
@@ -195,7 +231,7 @@ QUICNewRenoCongestionController::credit() const
     return UINT32_MAX;
   }
 
-  if (this->check_credit()) {
+  if (this->_check_credit()) {
     return this->_congestion_window - this->_bytes_in_flight;
   } else {
     return 0;
@@ -226,34 +262,13 @@ QUICNewRenoCongestionController::reset()
 {
   SCOPED_MUTEX_LOCK(lock, this->_cc_mutex, this_ethread());
 
-  this->_bytes_in_flight                = 0;
   this->_congestion_window              = this->_k_initial_window;
+  this->_bytes_in_flight                = 0;
   this->_congestion_recovery_start_time = 0;
   this->_ssthresh                       = UINT32_MAX;
-}
-
-bool
-QUICNewRenoCongestionController::_in_window_lost(const std::map<QUICPacketNumber, QUICPacketInfo *> &lost_packets,
-                                                 QUICPacketInfo *largest_lost_packet, ink_hrtime period) const
-{
-  // check whether packets are continuous. return true if all continuous packets are in period
-  QUICPacketNumber next_expected = UINT64_MAX;
-  for (auto &it : lost_packets) {
-    if (it.second->time_sent >= largest_lost_packet->time_sent - period) {
-      if (next_expected == UINT64_MAX) {
-        next_expected = it.second->packet_number + 1;
-        continue;
-      }
-
-      if (next_expected != it.second->packet_number) {
-        return false;
-      }
-
-      next_expected = it.second->packet_number + 1;
-    }
+  for (int i = 0; i < QUIC_N_PACKET_SPACES; ++i) {
+    this->_ecn_ce_counters[i] = 0;
   }
-
-  return next_expected == UINT64_MAX ? false : true;
 }
 
 void
diff --git a/iocore/net/quic/QUICNewRenoCongestionController.h b/iocore/net/quic/QUICNewRenoCongestionController.h
new file mode 100644
index 0000000..43a870c
--- /dev/null
+++ b/iocore/net/quic/QUICNewRenoCongestionController.h
@@ -0,0 +1,81 @@
+/** @file
+ *
+ *  A brief file description
+ *
+ *  @section license License
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#pragma once
+
+#include "QUICTypes.h"
+#include "QUICContext.h"
+#include "QUICCongestionController.h"
+
+class QUICNewRenoCongestionController : public QUICCongestionController
+{
+public:
+  QUICNewRenoCongestionController(QUICContext &context);
+  virtual ~QUICNewRenoCongestionController() {}
+
+  void on_packet_sent(size_t bytes_sent) override;
+  void on_packets_acked(const std::vector<QUICSentPacketInfoUPtr> &packets) override;
+  virtual void on_packets_lost(const std::map<QUICPacketNumber, QUICSentPacketInfoUPtr> &packets) override;
+  void on_packet_number_space_discarded(size_t bytes_in_flight) override;
+  void process_ecn(const QUICAckFrame &ack, QUICPacketNumberSpace pn_space, ink_hrtime largest_acked_packet_time_sent) override;
+  uint32_t credit() const override;
+  void reset() override;
+
+  // Debug
+  uint32_t bytes_in_flight() const override;
+  uint32_t congestion_window() const override;
+  uint32_t current_ssthresh() const override;
+
+  void add_extra_credit() override;
+
+private:
+  Ptr<ProxyMutex> _cc_mutex;
+  uint32_t _extra_packets_count = 0;
+  QUICContext &_context;
+  bool _check_credit() const;
+
+  // Appendix B.  Congestion Control Pseudocode
+  bool _in_congestion_recovery(ink_hrtime sent_time) const;
+  void _congestion_event(ink_hrtime sent_time);
+  bool _in_persistent_congestion(const std::map<QUICPacketNumber, QUICSentPacketInfoUPtr> &lost_packets,
+                                 const QUICSentPacketInfoUPtr &largest_lost_packet);
+  bool _is_app_or_flow_control_limited();
+  void _maybe_send_one_packet();
+  bool _are_all_packets_lost(const std::map<QUICPacketNumber, QUICSentPacketInfoUPtr> &lost_packets,
+                             const QUICSentPacketInfoUPtr &largest_lost_packet, ink_hrtime period) const;
+
+  // Recovery B.1. Constants of interest
+  // Values will be loaded from records.config via QUICConfig at constructor
+  uint32_t _k_initial_window                  = 0;
+  uint32_t _k_minimum_window                  = 0;
+  float _k_loss_reduction_factor              = 0.0;
+  uint32_t _k_persistent_congestion_threshold = 0;
+
+  // B.2. Variables of interest
+  uint32_t _max_datagram_size                     = 0;
+  uint32_t _ecn_ce_counters[QUIC_N_PACKET_SPACES] = {0};
+  uint32_t _bytes_in_flight                       = 0;
+  uint32_t _congestion_window                     = 0;
+  ink_hrtime _congestion_recovery_start_time      = 0;
+  uint32_t _ssthresh                              = UINT32_MAX;
+};
diff --git a/iocore/net/quic/QUICPacket.cc b/iocore/net/quic/QUICPacket.cc
index 0bcf624..5f5707d 100644
--- a/iocore/net/quic/QUICPacket.cc
+++ b/iocore/net/quic/QUICPacket.cc
@@ -883,9 +883,14 @@ QUICStatelessResetPacketR::destination_cid() const
 //
 // QUICVersionNegotiationPacket
 //
-QUICVersionNegotiationPacket::QUICVersionNegotiationPacket(QUICConnectionId dcid, QUICConnectionId scid,
-                                                           const QUICVersion versions[], int nversions)
-  : QUICLongHeaderPacket(0, dcid, scid, false, false, false), _versions(versions), _nversions(nversions)
+
+QUICVersionNegotiationPacket::QUICVersionNegotiationPacket(const QUICConnectionId &dcid, const QUICConnectionId &scid,
+                                                           const QUICVersion versions[], int nversions,
+                                                           QUICVersion version_in_initial)
+  : QUICLongHeaderPacket(0, dcid, scid, false, false, false),
+    _versions(versions),
+    _nversions(nversions),
+    _version_in_initial(version_in_initial)
 {
 }
 
@@ -961,7 +966,11 @@ QUICVersionNegotiationPacket::payload_block() const
   // [draft-18] 6.3. Using Reserved Versions
   // To help ensure this, a server SHOULD include a reserved version (see Section 15) while generating a
   // Version Negotiation packet.
-  QUICTypeUtil::write_QUICVersion(QUIC_EXERCISE_VERSION, buf + written_len, &n);
+  QUICVersion exersice_version = QUIC_EXERCISE_VERSION1;
+  if (this->_version_in_initial == QUIC_EXERCISE_VERSION1) {
+    exersice_version = QUIC_EXERCISE_VERSION2;
+  }
+  QUICTypeUtil::write_QUICVersion(exersice_version, buf + written_len, &n);
   written_len += n;
 
   block->fill(written_len);
@@ -1736,7 +1745,7 @@ QUICRetryPacket::payload_block() const
   block->fill(written_len);
 
   // Retry Integrity Tag
-  QUICRetryIntegrityTag::compute(buf + written_len, this->_token.original_dcid(), this->header_block(), block);
+  QUICRetryIntegrityTag::compute(buf + written_len, this->version(), this->_token.original_dcid(), this->header_block(), block);
   written_len += QUICRetryIntegrityTag::LEN;
   block->fill(QUICRetryIntegrityTag::LEN);
 
@@ -1838,7 +1847,7 @@ bool
 QUICRetryPacketR::has_valid_tag(const QUICConnectionId &odcid) const
 {
   uint8_t tag_computed[QUICRetryIntegrityTag::LEN];
-  QUICRetryIntegrityTag::compute(tag_computed, odcid, this->_header_block, this->_payload_block_without_tag);
+  QUICRetryIntegrityTag::compute(tag_computed, this->version(), odcid, this->_header_block, this->_payload_block_without_tag);
 
   return memcmp(this->_integrity_tag, tag_computed, QUICRetryIntegrityTag::LEN) == 0;
 }
diff --git a/iocore/net/quic/QUICPacket.h b/iocore/net/quic/QUICPacket.h
index a62daa4..e9d0212 100644
--- a/iocore/net/quic/QUICPacket.h
+++ b/iocore/net/quic/QUICPacket.h
@@ -319,7 +319,8 @@ public:
   /**
    * For sending packet
    */
-  QUICVersionNegotiationPacket(QUICConnectionId dcid, QUICConnectionId scid, const QUICVersion versions[], int nversions);
+  QUICVersionNegotiationPacket(const QUICConnectionId &dcid, const QUICConnectionId &scid, const QUICVersion versions[],
+                               int nversions, QUICVersion version_in_initial);
 
   QUICPacketType type() const override;
   QUICVersion version() const override;
@@ -335,6 +336,7 @@ public:
 private:
   const QUICVersion *_versions;
   int _nversions;
+  const QUICVersion _version_in_initial;
 };
 
 class QUICVersionNegotiationPacketR : public QUICLongHeaderPacketR
diff --git a/iocore/net/quic/QUICPacketFactory.cc b/iocore/net/quic/QUICPacketFactory.cc
index 436c683..2fcb1a2 100644
--- a/iocore/net/quic/QUICPacketFactory.cc
+++ b/iocore/net/quic/QUICPacketFactory.cc
@@ -197,10 +197,11 @@ QUICPacketFactory::create(uint8_t *packet_buf, UDPConnection *udp_con, IpEndpoin
 }
 
 QUICPacketUPtr
-QUICPacketFactory::create_version_negotiation_packet(QUICConnectionId dcid, QUICConnectionId scid)
+QUICPacketFactory::create_version_negotiation_packet(QUICConnectionId dcid, QUICConnectionId scid, QUICVersion version_in_initial)
 {
-  return QUICPacketUPtr(new QUICVersionNegotiationPacket(dcid, scid, QUIC_SUPPORTED_VERSIONS, countof(QUIC_SUPPORTED_VERSIONS)),
-                        &QUICPacketDeleter::delete_packet_new);
+  return QUICPacketUPtr(
+    new QUICVersionNegotiationPacket(dcid, scid, QUIC_SUPPORTED_VERSIONS, countof(QUIC_SUPPORTED_VERSIONS), version_in_initial),
+    &QUICPacketDeleter::delete_packet_new);
 }
 
 QUICPacketUPtr
@@ -228,10 +229,10 @@ QUICPacketFactory::create_initial_packet(uint8_t *packet_buf, QUICConnectionId d
 }
 
 QUICPacketUPtr
-QUICPacketFactory::create_retry_packet(QUICConnectionId destination_cid, QUICConnectionId source_cid, QUICRetryToken &token)
+QUICPacketFactory::create_retry_packet(QUICVersion version, QUICConnectionId destination_cid, QUICConnectionId source_cid,
+                                       QUICRetryToken &token)
 {
-  return QUICPacketUPtr(new QUICRetryPacket(QUIC_SUPPORTED_VERSIONS[0], destination_cid, source_cid, token),
-                        &QUICPacketDeleter::delete_packet_new);
+  return QUICPacketUPtr(new QUICRetryPacket(version, destination_cid, source_cid, token), &QUICPacketDeleter::delete_packet_new);
 }
 
 QUICPacketUPtr
@@ -329,7 +330,7 @@ QUICPacketFactory::is_ready_to_create_protected_packet()
 void
 QUICPacketFactory::reset()
 {
-  for (auto i = 0; i < kPacketNumberSpace; i++) {
+  for (auto i = 0; i < QUIC_N_PACKET_SPACES; i++) {
     this->_packet_number_generator[i].reset();
   }
 }
diff --git a/iocore/net/quic/QUICPacketFactory.h b/iocore/net/quic/QUICPacketFactory.h
index d5316ce..2d59de6 100644
--- a/iocore/net/quic/QUICPacketFactory.h
+++ b/iocore/net/quic/QUICPacketFactory.h
@@ -44,9 +44,11 @@ class QUICPacketFactory
 {
 public:
   static QUICPacketUPtr create_null_packet();
-  static QUICPacketUPtr create_version_negotiation_packet(QUICConnectionId dcid, QUICConnectionId scid);
+  static QUICPacketUPtr create_version_negotiation_packet(QUICConnectionId dcid, QUICConnectionId scid,
+                                                          QUICVersion version_in_version);
   static QUICPacketUPtr create_stateless_reset_packet(QUICStatelessResetToken stateless_reset_token, size_t maximum_size);
-  static QUICPacketUPtr create_retry_packet(QUICConnectionId destination_cid, QUICConnectionId source_cid, QUICRetryToken &token);
+  static QUICPacketUPtr create_retry_packet(QUICVersion version, QUICConnectionId destination_cid, QUICConnectionId source_cid,
+                                            QUICRetryToken &token);
 
   QUICPacketFactory(const QUICPacketProtectionKeyInfo &pp_key_info) : _pp_key_info(pp_key_info), _pp_protector(pp_key_info) {}
 
diff --git a/iocore/net/quic/QUICRetryIntegrityTag.cc b/iocore/net/quic/QUICRetryIntegrityTag.cc
index c528656..117dff6 100644
--- a/iocore/net/quic/QUICRetryIntegrityTag.cc
+++ b/iocore/net/quic/QUICRetryIntegrityTag.cc
@@ -24,7 +24,8 @@
 #include "QUICRetryIntegrityTag.h"
 
 bool
-QUICRetryIntegrityTag::compute(uint8_t *out, QUICConnectionId odcid, Ptr<IOBufferBlock> header, Ptr<IOBufferBlock> payload)
+QUICRetryIntegrityTag::compute(uint8_t *out, QUICVersion version, QUICConnectionId odcid, Ptr<IOBufferBlock> header,
+                               Ptr<IOBufferBlock> payload)
 {
   EVP_CIPHER_CTX *aead_ctx;
 
@@ -37,7 +38,23 @@ QUICRetryIntegrityTag::compute(uint8_t *out, QUICConnectionId odcid, Ptr<IOBuffe
   if (!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, nullptr)) {
     return false;
   }
-  if (!EVP_EncryptInit_ex(aead_ctx, nullptr, nullptr, KEY_FOR_RETRY_INTEGRITY_TAG, NONCE_FOR_RETRY_INTEGRITY_TAG)) {
+  const uint8_t *key;
+  const uint8_t *nonce;
+  switch (version) {
+  case 0xff00001d: // Draft-29
+    key   = KEY_FOR_RETRY_INTEGRITY_TAG;
+    nonce = NONCE_FOR_RETRY_INTEGRITY_TAG;
+    break;
+  case 0xff00001b: // Draft-27
+    key   = KEY_FOR_RETRY_INTEGRITY_TAG_D27;
+    nonce = NONCE_FOR_RETRY_INTEGRITY_TAG_D27;
+    break;
+  default:
+    key   = KEY_FOR_RETRY_INTEGRITY_TAG;
+    nonce = NONCE_FOR_RETRY_INTEGRITY_TAG;
+    break;
+  }
+  if (!EVP_EncryptInit_ex(aead_ctx, nullptr, nullptr, key, nonce)) {
     return false;
   }
 
diff --git a/iocore/net/quic/QUICRetryIntegrityTag.h b/iocore/net/quic/QUICRetryIntegrityTag.h
index 3ac9498..081bd59 100644
--- a/iocore/net/quic/QUICRetryIntegrityTag.h
+++ b/iocore/net/quic/QUICRetryIntegrityTag.h
@@ -29,11 +29,17 @@ class QUICRetryIntegrityTag
 {
 public:
   static constexpr int LEN = 16;
-  static bool compute(uint8_t *out, QUICConnectionId odcid, Ptr<IOBufferBlock> header, Ptr<IOBufferBlock> payload);
+  static bool compute(uint8_t *out, QUICVersion version, QUICConnectionId odcid, Ptr<IOBufferBlock> header,
+                      Ptr<IOBufferBlock> payload);
 
 private:
-  static constexpr uint8_t KEY_FOR_RETRY_INTEGRITY_TAG[]   = {0x4d, 0x32, 0xec, 0xdb, 0x2a, 0x21, 0x33, 0xc8,
-                                                            0x41, 0xe4, 0x04, 0x3d, 0xf2, 0x7d, 0x44, 0x30};
-  static constexpr uint8_t NONCE_FOR_RETRY_INTEGRITY_TAG[] = {0x4d, 0x16, 0x11, 0xd0, 0x55, 0x13,
-                                                              0xa5, 0x52, 0xc5, 0x87, 0xd5, 0x75};
+  static constexpr uint8_t KEY_FOR_RETRY_INTEGRITY_TAG[]   = {0xcc, 0xce, 0x18, 0x7e, 0xd0, 0x9a, 0x09, 0xd0,
+                                                            0x57, 0x28, 0x15, 0x5a, 0x6c, 0xb9, 0x6b, 0xe1};
+  static constexpr uint8_t NONCE_FOR_RETRY_INTEGRITY_TAG[] = {0xe5, 0x49, 0x30, 0xf9, 0x7f, 0x21,
+                                                              0x36, 0xf0, 0x53, 0x0a, 0x8c, 0x1c};
+  // For draft 27
+  static constexpr uint8_t KEY_FOR_RETRY_INTEGRITY_TAG_D27[]   = {0x4d, 0x32, 0xec, 0xdb, 0x2a, 0x21, 0x33, 0xc8,
+                                                                0x41, 0xe4, 0x04, 0x3d, 0xf2, 0x7d, 0x44, 0x30};
+  static constexpr uint8_t NONCE_FOR_RETRY_INTEGRITY_TAG_D27[] = {0x4d, 0x16, 0x11, 0xd0, 0x55, 0x13,
+                                                                  0xa5, 0x52, 0xc5, 0x87, 0xd5, 0x75};
 };
diff --git a/iocore/net/quic/QUICStream.h b/iocore/net/quic/QUICStream.h
index bff660a..afeb03c 100644
--- a/iocore/net/quic/QUICStream.h
+++ b/iocore/net/quic/QUICStream.h
@@ -130,5 +130,8 @@ protected:
 #define QUICStreamFCDebug(fmt, ...)                                                                         \
   Debug("quic_flow_ctrl", "[%s] [%" PRIu64 "] [%s] " fmt, this->_connection_info->cids().data(), this->_id, \
         QUICDebugNames::stream_state(this->_state.get()), ##__VA_ARGS__)
+#define QUICVStreamFCDebug(fmt, ...)                                                                          \
+  Debug("v_quic_flow_ctrl", "[%s] [%" PRIu64 "] [%s] " fmt, this->_connection_info->cids().data(), this->_id, \
+        QUICDebugNames::stream_state(this->_state.get()), ##__VA_ARGS__)
 
 extern const uint32_t MAX_STREAM_FRAME_OVERHEAD;
diff --git a/iocore/net/quic/QUICTLS.cc b/iocore/net/quic/QUICTLS.cc
index 4764ea1..a618e3a 100644
--- a/iocore/net/quic/QUICTLS.cc
+++ b/iocore/net/quic/QUICTLS.cc
@@ -165,7 +165,7 @@ QUICTLS::is_ready_to_derive() const
 }
 
 int
-QUICTLS::initialize_key_materials(QUICConnectionId cid)
+QUICTLS::initialize_key_materials(QUICConnectionId cid, QUICVersion version)
 {
   this->_pp_key_info.set_cipher_initial(EVP_aes_128_gcm());
   this->_pp_key_info.set_cipher_for_hp_initial(EVP_aes_128_ecb());
@@ -217,8 +217,8 @@ QUICTLS::initialize_key_materials(QUICConnectionId cid)
     server_iv_len         = this->_pp_key_info.decryption_iv_len(QUICKeyPhase::INITIAL);
   }
 
-  this->_keygen_for_client.generate(client_key_for_hp, client_key, client_iv, client_iv_len, cid);
-  this->_keygen_for_server.generate(server_key_for_hp, server_key, server_iv, server_iv_len, cid);
+  this->_keygen_for_client.generate(version, client_key_for_hp, client_key, client_iv, client_iv_len, cid);
+  this->_keygen_for_server.generate(version, server_key_for_hp, server_key, server_iv, server_iv_len, cid);
 
   this->_pp_key_info.set_decryption_key_available(QUICKeyPhase::INITIAL);
   this->_pp_key_info.set_encryption_key_available(QUICKeyPhase::INITIAL);
diff --git a/iocore/net/quic/QUICTLS.h b/iocore/net/quic/QUICTLS.h
index e60d920..938e1e2 100644
--- a/iocore/net/quic/QUICTLS.h
+++ b/iocore/net/quic/QUICTLS.h
@@ -71,7 +71,7 @@ public:
   void reset() override;
   bool is_handshake_finished() const override;
   bool is_ready_to_derive() const override;
-  int initialize_key_materials(QUICConnectionId cid) override;
+  int initialize_key_materials(QUICConnectionId cid, QUICVersion version) override;
   void update_negotiated_cipher();
   void update_key_materials_for_read(QUICEncryptionLevel level, const uint8_t *secret, size_t secret_len);
   void update_key_materials_for_write(QUICEncryptionLevel level, const uint8_t *secret, size_t secret_len);
diff --git a/iocore/net/quic/QUICTLS_boringssl.cc b/iocore/net/quic/QUICTLS_boringssl.cc
index 46f70f0..01aabfa 100644
--- a/iocore/net/quic/QUICTLS_boringssl.cc
+++ b/iocore/net/quic/QUICTLS_boringssl.cc
@@ -30,6 +30,7 @@
 #include <openssl/aead.h>
 
 #include "QUICGlobals.h"
+#include "QUICConnection.h"
 #include "QUICPacketProtectionKeyInfo.h"
 
 static constexpr char tag[] = "quic_tls";
@@ -80,10 +81,13 @@ set_write_secret(SSL *ssl, enum ssl_encryption_level_t level, const SSL_CIPHER *
     const uint8_t *tp_buf;
     size_t tp_buf_len;
     SSL_get_peer_quic_transport_params(ssl, &tp_buf, &tp_buf_len);
+    const QUICConnection *qc = static_cast<const QUICConnection *>(SSL_get_ex_data(ssl, QUIC::ssl_quic_qc_index));
+    QUICVersion version      = qc->negotiated_version();
     if (SSL_is_server(ssl)) {
-      qtls->set_remote_transport_parameters(std::make_shared<QUICTransportParametersInClientHello>(tp_buf, tp_buf_len));
+      qtls->set_remote_transport_parameters(std::make_shared<QUICTransportParametersInClientHello>(tp_buf, tp_buf_len, version));
     } else {
-      qtls->set_remote_transport_parameters(std::make_shared<QUICTransportParametersInEncryptedExtensions>(tp_buf, tp_buf_len));
+      qtls->set_remote_transport_parameters(
+        std::make_shared<QUICTransportParametersInEncryptedExtensions>(tp_buf, tp_buf_len, version));
     }
   }
 
@@ -111,10 +115,13 @@ set_encryption_secrets(SSL *ssl, enum ssl_encryption_level_t level, const uint8_
     const uint8_t *tp_buf;
     size_t tp_buf_len;
     SSL_get_peer_quic_transport_params(ssl, &tp_buf, &tp_buf_len);
+    const QUICConnection *qc = static_cast<const QUICConnection *>(SSL_get_ex_data(ssl, QUIC::ssl_quic_qc_index));
+    QUICVersion version      = qc->negotiated_version();
     if (SSL_is_server(ssl)) {
-      qtls->set_remote_transport_parameters(std::make_shared<QUICTransportParametersInClientHello>(tp_buf, tp_buf_len));
+      qtls->set_remote_transport_parameters(std::make_shared<QUICTransportParametersInClientHello>(tp_buf, tp_buf_len, version));
     } else {
-      qtls->set_remote_transport_parameters(std::make_shared<QUICTransportParametersInEncryptedExtensions>(tp_buf, tp_buf_len));
+      qtls->set_remote_transport_parameters(
+        std::make_shared<QUICTransportParametersInEncryptedExtensions>(tp_buf, tp_buf_len, version));
     }
   }
 
diff --git a/iocore/net/quic/QUICTLS_openssl.cc b/iocore/net/quic/QUICTLS_openssl.cc
index 28dfbf1..c34598d 100644
--- a/iocore/net/quic/QUICTLS_openssl.cc
+++ b/iocore/net/quic/QUICTLS_openssl.cc
@@ -29,6 +29,7 @@
 #include <openssl/evp.h>
 
 #include "QUICGlobals.h"
+#include "QUICConnection.h"
 #include "QUICPacketProtectionKeyInfo.h"
 
 static constexpr char tag[] = "quic_tls";
@@ -71,10 +72,13 @@ set_encryption_secrets(SSL *ssl, enum ssl_encryption_level_t level, const uint8_
     const uint8_t *tp_buf;
     size_t tp_buf_len;
     SSL_get_peer_quic_transport_params(ssl, &tp_buf, &tp_buf_len);
+    const QUICConnection *qc = static_cast<const QUICConnection *>(SSL_get_ex_data(ssl, QUIC::ssl_quic_qc_index));
+    QUICVersion version      = qc->negotiated_version();
     if (SSL_is_server(ssl)) {
-      qtls->set_remote_transport_parameters(std::make_shared<QUICTransportParametersInClientHello>(tp_buf, tp_buf_len));
+      qtls->set_remote_transport_parameters(std::make_shared<QUICTransportParametersInClientHello>(tp_buf, tp_buf_len, version));
     } else {
-      qtls->set_remote_transport_parameters(std::make_shared<QUICTransportParametersInEncryptedExtensions>(tp_buf, tp_buf_len));
+      qtls->set_remote_transport_parameters(
+        std::make_shared<QUICTransportParametersInEncryptedExtensions>(tp_buf, tp_buf_len, version));
     }
   }
 
diff --git a/iocore/net/quic/QUICTransportParameters.cc b/iocore/net/quic/QUICTransportParameters.cc
index cead970..c113458 100644
--- a/iocore/net/quic/QUICTransportParameters.cc
+++ b/iocore/net/quic/QUICTransportParameters.cc
@@ -35,9 +35,9 @@ static constexpr char tag[] = "quic_handshake";
 
 static constexpr int TRANSPORT_PARAMETERS_MAXIMUM_SIZE = 65535;
 
-static constexpr uint32_t TP_ERROR_LENGTH = 0x010000;
-static constexpr uint32_t TP_ERROR_VALUE  = 0x020000;
-// static constexpr uint32_t TP_ERROR_MUST_EXIST     = 0x030000;
+static constexpr uint32_t TP_ERROR_LENGTH         = 0x010000;
+static constexpr uint32_t TP_ERROR_VALUE          = 0x020000;
+static constexpr uint32_t TP_ERROR_MUST_EXIST     = 0x030000;
 static constexpr uint32_t TP_ERROR_MUST_NOT_EXIST = 0x040000;
 
 QUICTransportParameters::Value::Value(const uint8_t *data, uint16_t len) : _len(len)
@@ -70,6 +70,14 @@ QUICTransportParameters::Value::len() const
   return this->_len;
 }
 
+QUICTransportParameters::QUICTransportParameters(const uint8_t *buf, size_t len, QUICVersion version)
+{
+  this->_load(buf, len, version);
+  if (is_debug_tag_set(tag)) {
+    this->_print();
+  }
+}
+
 QUICTransportParameters::~QUICTransportParameters()
 {
   for (auto p : this->_parameters) {
@@ -78,7 +86,7 @@ QUICTransportParameters::~QUICTransportParameters()
 }
 
 void
-QUICTransportParameters::_load(const uint8_t *buf, size_t len)
+QUICTransportParameters::_load(const uint8_t *buf, size_t len, QUICVersion version)
 {
   bool has_error   = false;
   const uint8_t *p = buf;
@@ -130,7 +138,7 @@ QUICTransportParameters::_load(const uint8_t *buf, size_t len)
   }
 
   // Validate parameters
-  int res = this->_validate_parameters();
+  int res = this->_validate_parameters(version);
   if (res < 0) {
     Debug(tag, "Transport parameter is not valid (err=%d)", res);
     this->_valid = false;
@@ -140,7 +148,7 @@ QUICTransportParameters::_load(const uint8_t *buf, size_t len)
 }
 
 int
-QUICTransportParameters::_validate_parameters() const
+QUICTransportParameters::_validate_parameters(QUICVersion version) const
 {
   decltype(this->_parameters)::const_iterator ite;
 
@@ -159,9 +167,9 @@ QUICTransportParameters::_validate_parameters() const
   if ((ite = this->_parameters.find(QUICTransportParameterId::MAX_IDLE_TIMEOUT)) != this->_parameters.end()) {
   }
 
-  if ((ite = this->_parameters.find(QUICTransportParameterId::MAX_PACKET_SIZE)) != this->_parameters.end()) {
+  if ((ite = this->_parameters.find(QUICTransportParameterId::MAX_UDP_PAYLOAD_SIZE)) != this->_parameters.end()) {
     if (QUICIntUtil::read_nbytes_as_uint(ite->second->data(), ite->second->len()) < 1200) {
-      return -(TP_ERROR_VALUE | QUICTransportParameterId::MAX_PACKET_SIZE);
+      return -(TP_ERROR_VALUE | QUICTransportParameterId::MAX_UDP_PAYLOAD_SIZE);
     }
   }
 
@@ -262,6 +270,10 @@ QUICTransportParameters::store(uint8_t *buf, uint16_t *len) const
   }
 
   *len = (p - buf);
+
+  if (is_debug_tag_set(tag)) {
+    this->_print();
+  }
 }
 
 void
@@ -301,12 +313,9 @@ QUICTransportParameters::_print() const
 // QUICTransportParametersInClientHello
 //
 
-QUICTransportParametersInClientHello::QUICTransportParametersInClientHello(const uint8_t *buf, size_t len)
+QUICTransportParametersInClientHello::QUICTransportParametersInClientHello(const uint8_t *buf, size_t len, QUICVersion version)
+  : QUICTransportParameters(buf, len, version)
 {
-  this->_load(buf, len);
-  if (is_debug_tag_set(tag)) {
-    this->_print();
-  }
 }
 
 std::ptrdiff_t
@@ -316,9 +325,9 @@ QUICTransportParametersInClientHello::_parameters_offset(const uint8_t *) const
 }
 
 int
-QUICTransportParametersInClientHello::_validate_parameters() const
+QUICTransportParametersInClientHello::_validate_parameters(QUICVersion version) const
 {
-  int res = QUICTransportParameters::_validate_parameters();
+  int res = QUICTransportParameters::_validate_parameters(version);
   if (res < 0) {
     return res;
   }
@@ -326,14 +335,22 @@ QUICTransportParametersInClientHello::_validate_parameters() const
   decltype(this->_parameters)::const_iterator ite;
 
   // MUST NOTs
-  if ((ite = this->_parameters.find(QUICTransportParameterId::STATELESS_RESET_TOKEN)) != this->_parameters.end()) {
-    return -(TP_ERROR_MUST_NOT_EXIST | QUICTransportParameterId::STATELESS_RESET_TOKEN);
+  if ((ite = this->_parameters.find(QUICTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID)) != this->_parameters.end()) {
+    return -(TP_ERROR_MUST_NOT_EXIST | QUICTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID);
   }
 
   if ((ite = this->_parameters.find(QUICTransportParameterId::PREFERRED_ADDRESS)) != this->_parameters.end()) {
     return -(TP_ERROR_MUST_NOT_EXIST | QUICTransportParameterId::PREFERRED_ADDRESS);
   }
 
+  if ((ite = this->_parameters.find(QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID)) != this->_parameters.end()) {
+    return -(TP_ERROR_MUST_NOT_EXIST | QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID);
+  }
+
+  if ((ite = this->_parameters.find(QUICTransportParameterId::STATELESS_RESET_TOKEN)) != this->_parameters.end()) {
+    return -(TP_ERROR_MUST_NOT_EXIST | QUICTransportParameterId::STATELESS_RESET_TOKEN);
+  }
+
   return 0;
 }
 
@@ -341,18 +358,10 @@ QUICTransportParametersInClientHello::_validate_parameters() const
 // QUICTransportParametersInEncryptedExtensions
 //
 
-QUICTransportParametersInEncryptedExtensions::QUICTransportParametersInEncryptedExtensions(const uint8_t *buf, size_t len)
-{
-  this->_load(buf, len);
-  if (is_debug_tag_set(tag)) {
-    this->_print();
-  }
-}
-
-void
-QUICTransportParametersInEncryptedExtensions::add_version(QUICVersion version)
+QUICTransportParametersInEncryptedExtensions::QUICTransportParametersInEncryptedExtensions(const uint8_t *buf, size_t len,
+                                                                                           QUICVersion version)
+  : QUICTransportParameters(buf, len, version)
 {
-  this->_versions[this->_n_versions++] = version;
 }
 
 std::ptrdiff_t
@@ -362,21 +371,36 @@ QUICTransportParametersInEncryptedExtensions::_parameters_offset(const uint8_t *
 }
 
 int
-QUICTransportParametersInEncryptedExtensions::_validate_parameters() const
+QUICTransportParametersInEncryptedExtensions::_validate_parameters(QUICVersion version) const
 {
-  int res = QUICTransportParameters::_validate_parameters();
+  int res = QUICTransportParameters::_validate_parameters(version);
   if (res < 0) {
     return res;
   }
 
   decltype(this->_parameters)::const_iterator ite;
 
-  // MUSTs if the server sent a Retry packet
-  if ((ite = this->_parameters.find(QUICTransportParameterId::ORIGINAL_CONNECTION_ID)) != this->_parameters.end()) {
-    // We cannot check the length because it's not a fixed length.
-  } else {
-    // TODO Need a way that checks if we received a Retry from the server
-    // return -(TP_ERROR_MUST_EXIST | QUICTransportParameterId::ORIGINAL_CONNECTION_ID);
+  // MUSTs
+  if (version == QUIC_SUPPORTED_VERSIONS[0]) { // draft-28
+    if ((ite = this->_parameters.find(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID)) != this->_parameters.end()) {
+      // We cannot check the length because it's not a fixed length.
+    } else {
+      return -(TP_ERROR_MUST_EXIST | QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID);
+    }
+
+    if ((ite = this->_parameters.find(QUICTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID)) != this->_parameters.end()) {
+      // We cannot check the length because it's not a fixed length.
+    } else {
+      return -(TP_ERROR_MUST_EXIST | QUICTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID);
+    }
+
+    // MUSTs if the server sent a Retry packet, but MUST NOT if the server did not send a Retry packet
+    // TODO Check if the server sent Retry packet
+    if ((ite = this->_parameters.find(QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID)) != this->_parameters.end()) {
+      // return -(TP_ERROR_MUST_NOT_EXIST | QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID);
+    } else {
+      // return -(TP_ERROR_MUST_EXIST | QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID);
+    }
   }
 
   // MAYs
@@ -422,14 +446,15 @@ int
 QUICTransportParametersHandler::parse(SSL *s, unsigned int ext_type, unsigned int context, const unsigned char *in, size_t inlen,
                                       X509 *x, size_t chainidx, int *al, void *parse_arg)
 {
-  QUICTLS *qtls = static_cast<QUICTLS *>(SSL_get_ex_data(s, QUIC::ssl_quic_tls_index));
-
+  QUICTLS *qtls            = static_cast<QUICTLS *>(SSL_get_ex_data(s, QUIC::ssl_quic_tls_index));
+  const QUICConnection *qc = static_cast<const QUICConnection *>(SSL_get_ex_data(s, QUIC::ssl_quic_qc_index));
+  QUICVersion version      = qc->negotiated_version();
   switch (context) {
   case SSL_EXT_CLIENT_HELLO:
-    qtls->set_remote_transport_parameters(std::make_shared<QUICTransportParametersInClientHello>(in, inlen));
+    qtls->set_remote_transport_parameters(std::make_shared<QUICTransportParametersInClientHello>(in, inlen, version));
     break;
   case SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS:
-    qtls->set_remote_transport_parameters(std::make_shared<QUICTransportParametersInEncryptedExtensions>(in, inlen));
+    qtls->set_remote_transport_parameters(std::make_shared<QUICTransportParametersInEncryptedExtensions>(in, inlen, version));
     break;
   default:
     // Do nothing
diff --git a/iocore/net/quic/QUICTransportParameters.h b/iocore/net/quic/QUICTransportParameters.h
index d3c19fe..7253384 100644
--- a/iocore/net/quic/QUICTransportParameters.h
+++ b/iocore/net/quic/QUICTransportParameters.h
@@ -34,10 +34,10 @@ class QUICTransportParameterId
 {
 public:
   enum {
-    ORIGINAL_CONNECTION_ID,
+    ORIGINAL_DESTINATION_CONNECTION_ID,
     MAX_IDLE_TIMEOUT,
     STATELESS_RESET_TOKEN,
-    MAX_PACKET_SIZE,
+    MAX_UDP_PAYLOAD_SIZE,
     INITIAL_MAX_DATA,
     INITIAL_MAX_STREAM_DATA_BIDI_LOCAL,
     INITIAL_MAX_STREAM_DATA_BIDI_REMOTE,
@@ -49,6 +49,8 @@ public:
     DISABLE_ACTIVE_MIGRATION,
     PREFERRED_ADDRESS,
     ACTIVE_CONNECTION_ID_LIMIT,
+    INITIAL_SOURCE_CONNECTION_ID,
+    RETRY_SOURCE_CONNECTION_ID,
   };
 
   explicit operator bool() const { return true; }
@@ -75,7 +77,7 @@ private:
 class QUICTransportParameters
 {
 public:
-  QUICTransportParameters(const uint8_t *buf, size_t len);
+  QUICTransportParameters(const uint8_t *buf, size_t len, QUICVersion version);
   virtual ~QUICTransportParameters();
 
   bool is_valid() const;
@@ -104,11 +106,11 @@ protected:
   };
 
   QUICTransportParameters(){};
-  void _load(const uint8_t *buf, size_t len);
+  void _load(const uint8_t *buf, size_t len, QUICVersion version);
   bool _valid = false;
 
   virtual std::ptrdiff_t _parameters_offset(const uint8_t *buf) const = 0;
-  virtual int _validate_parameters() const;
+  virtual int _validate_parameters(QUICVersion version) const;
   void _print() const;
 
   std::map<QUICTransportParameterId, Value *> _parameters;
@@ -118,11 +120,11 @@ class QUICTransportParametersInClientHello : public QUICTransportParameters
 {
 public:
   QUICTransportParametersInClientHello() : QUICTransportParameters(){};
-  QUICTransportParametersInClientHello(const uint8_t *buf, size_t len);
+  QUICTransportParametersInClientHello(const uint8_t *buf, size_t len, QUICVersion version);
 
 protected:
   std::ptrdiff_t _parameters_offset(const uint8_t *buf) const override;
-  int _validate_parameters() const override;
+  int _validate_parameters(QUICVersion version) const override;
 
 private:
 };
@@ -131,15 +133,11 @@ class QUICTransportParametersInEncryptedExtensions : public QUICTransportParamet
 {
 public:
   QUICTransportParametersInEncryptedExtensions() : QUICTransportParameters(){};
-  QUICTransportParametersInEncryptedExtensions(const uint8_t *buf, size_t len);
-  void add_version(QUICVersion version);
+  QUICTransportParametersInEncryptedExtensions(const uint8_t *buf, size_t len, QUICVersion version);
 
 protected:
   std::ptrdiff_t _parameters_offset(const uint8_t *buf) const override;
-  int _validate_parameters() const override;
-
-  uint8_t _n_versions        = 0;
-  QUICVersion _versions[256] = {};
+  int _validate_parameters(QUICVersion version) const override;
 };
 
 class QUICTransportParametersHandler
diff --git a/iocore/net/quic/QUICTypes.cc b/iocore/net/quic/QUICTypes.cc
index 80a8379..305abc2 100644
--- a/iocore/net/quic/QUICTypes.cc
+++ b/iocore/net/quic/QUICTypes.cc
@@ -153,13 +153,14 @@ QUICTypeUtil::key_phase(QUICPacketType type)
 QUICPacketNumberSpace
 QUICTypeUtil::pn_space(QUICEncryptionLevel level)
 {
+  // TODO Optimize the case order
   switch (level) {
   case QUICEncryptionLevel::HANDSHAKE:
-    return QUICPacketNumberSpace::Handshake;
+    return QUICPacketNumberSpace::HANDSHAKE;
   case QUICEncryptionLevel::INITIAL:
-    return QUICPacketNumberSpace::Initial;
+    return QUICPacketNumberSpace::INITIAL;
   default:
-    return QUICPacketNumberSpace::ApplicationData;
+    return QUICPacketNumberSpace::APPLICATION_DATA;
   }
 }
 
@@ -360,7 +361,7 @@ QUICResumptionToken::expire_time() const
   return QUICIntUtil::read_nbytes_as_uint(this->_token + (1 + 20), 4);
 }
 
-QUICRetryToken::QUICRetryToken(const IpEndpoint &src, QUICConnectionId original_dcid)
+QUICRetryToken::QUICRetryToken(const IpEndpoint &src, QUICConnectionId original_dcid, QUICConnectionId scid)
 {
   // TODO: read cookie secret from file like SSLTicketKeyConfig
   static constexpr char stateless_retry_token_secret[] = "stateless_cookie_secret";
@@ -371,8 +372,14 @@ QUICRetryToken::QUICRetryToken(const IpEndpoint &src, QUICConnectionId original_
   data_len = strlen(reinterpret_cast<char *>(data));
 
   size_t cid_len;
+  *(data + data_len) = original_dcid.length();
+  data_len += 1;
   QUICTypeUtil::write_QUICConnectionId(original_dcid, data + data_len, &cid_len);
   data_len += cid_len;
+  *(data + data_len) = scid.length();
+  data_len += 1;
+  QUICTypeUtil::write_QUICConnectionId(scid, data + data_len, &cid_len);
+  data_len += cid_len;
 
   this->_token[0] = static_cast<uint8_t>(Type::RETRY);
   HMAC(EVP_sha1(), stateless_retry_token_secret, sizeof(stateless_retry_token_secret), data, data_len, this->_token + 1,
@@ -380,21 +387,38 @@ QUICRetryToken::QUICRetryToken(const IpEndpoint &src, QUICConnectionId original_
   ink_assert(this->_token_len == 20);
   this->_token_len += 1;
 
+  *(this->_token + this->_token_len) = original_dcid.length();
+  this->_token_len += 1;
   QUICTypeUtil::write_QUICConnectionId(original_dcid, this->_token + this->_token_len, &cid_len);
   this->_token_len += cid_len;
+  *(this->_token + this->_token_len) = scid.length();
+  this->_token_len += 1;
+  QUICTypeUtil::write_QUICConnectionId(scid, this->_token + this->_token_len, &cid_len);
+  this->_token_len += cid_len;
 }
 
 bool
 QUICRetryToken::is_valid(const IpEndpoint &src) const
 {
-  return *this == QUICRetryToken(src, this->original_dcid());
+  return *this == QUICRetryToken(src, this->original_dcid(), this->scid());
 }
 
 const QUICConnectionId
 QUICRetryToken::original_dcid() const
 {
   // Type uses 1 byte and output of EVP_sha1() should be 160 bits
-  return QUICTypeUtil::read_QUICConnectionId(this->_token + (1 + 20), this->_token_len - (1 + 20));
+  auto len   = *(this->_token + (1 + 20));
+  auto start = this->_token + (1 + 20 + 1);
+  return QUICTypeUtil::read_QUICConnectionId(start, len);
+}
+
+const QUICConnectionId
+QUICRetryToken::scid() const
+{
+  auto len   = *(this->_token + (1 + 20));
+  auto start = this->_token + (1 + 20 + 1 + len + 1);
+  len        = *(this->_token + (1 + 20 + 1 + len));
+  return QUICTypeUtil::read_QUICConnectionId(start, len);
 }
 
 QUICFrameType
@@ -674,6 +698,18 @@ QUICConnectionId::hex() const
   return stream.str();
 }
 
+QUICFrameId
+QUICSentPacketInfo::FrameInfo::id() const
+{
+  return this->_id;
+}
+
+QUICFrameGenerator *
+QUICSentPacketInfo::FrameInfo::generated_by() const
+{
+  return this->_generator;
+}
+
 //
 // QUICInvariants
 //
diff --git a/iocore/net/quic/QUICTypes.h b/iocore/net/quic/QUICTypes.h
index 6441ceb..47e830c 100644
--- a/iocore/net/quic/QUICTypes.h
+++ b/iocore/net/quic/QUICTypes.h
@@ -44,8 +44,7 @@ using QUICPacketNumber = uint64_t;
 using QUICVersion      = uint32_t;
 using QUICStreamId     = uint64_t;
 using QUICOffset       = uint64_t;
-
-static constexpr uint8_t kPacketNumberSpace = 3;
+using QUICFrameId      = uint64_t;
 
 // TODO: Update version number
 // Note: Prefix for drafts (0xff000000) + draft number
@@ -53,9 +52,11 @@ static constexpr uint8_t kPacketNumberSpace = 3;
 // Note: Fix QUIC_ALPN_PROTO_LIST in QUICConfig.cc
 // Note: Change ExtensionType (QUICTransportParametersHandler::TRANSPORT_PARAMETER_ID) if it's changed
 constexpr QUICVersion QUIC_SUPPORTED_VERSIONS[] = {
+  0xff00001d,
   0xff00001b,
 };
-constexpr QUICVersion QUIC_EXERCISE_VERSION = 0x1a2a3a4a;
+constexpr QUICVersion QUIC_EXERCISE_VERSION1 = 0x1a2a3a4a;
+constexpr QUICVersion QUIC_EXERCISE_VERSION2 = 0x5a6a7a8a;
 
 enum class QUICEncryptionLevel {
   NONE      = -1,
@@ -75,13 +76,10 @@ constexpr QUICEncryptionLevel QUIC_ENCRYPTION_LEVELS[] = {
   QUICEncryptionLevel::ONE_RTT,
 };
 
-// introduce by draft-19 kPacketNumberSpace
-enum class QUICPacketNumberSpace {
-  None            = -1,
-  Initial         = 0,
-  Handshake       = 1,
-  ApplicationData = 2,
-};
+// kPacketNumberSpace on Recovery A.2.Constants of Interest
+enum class QUICPacketNumberSpace : int { INITIAL, HANDSHAKE, APPLICATION_DATA, N_SPACES };
+// For conveniece (this removes neccesity of static_cast)
+constexpr int QUIC_N_PACKET_SPACES = static_cast<int>(QUICPacketNumberSpace::N_SPACES);
 
 // Divide to QUICPacketType and QUICPacketLongHeaderType ?
 enum class QUICPacketType : uint8_t {
@@ -154,7 +152,7 @@ enum class QUICErrorClass {
 enum class QUICTransErrorCode : uint64_t {
   NO_ERROR = 0x00,
   INTERNAL_ERROR,
-  SERVER_BUSY,
+  CONNECTION_REFUSED,
   FLOW_CONTROL_ERROR,
   STREAM_LIMIT_ERROR,
   STREAM_STATE_ERROR,
@@ -164,8 +162,9 @@ enum class QUICTransErrorCode : uint64_t {
   CONNECTION_ID_LIMIT_ERROR,
   PROTOCOL_VIOLATION,
   INVALID_TOKEN,
-  CRYPTO_BUFFER_EXCEEDED = 0x0D,
-  CRYPTO_ERROR           = 0x0100, // 0x100 - 0x1FF
+  APPLICATION_ERROR,
+  CRYPTO_BUFFER_EXCEEDED,
+  CRYPTO_ERROR = 0x0100, // 0x100 - 0x1FF
 };
 
 // Application Protocol Error Codes defined in application
@@ -381,7 +380,7 @@ class QUICRetryToken : public QUICAddressValidationToken
 {
 public:
   QUICRetryToken(const uint8_t *buf, size_t len) : QUICAddressValidationToken(buf, len) {}
-  QUICRetryToken(const IpEndpoint &src, QUICConnectionId original_dcid);
+  QUICRetryToken(const IpEndpoint &src, QUICConnectionId original_dcid, QUICConnectionId scid);
 
   bool
   operator==(const QUICRetryToken &x) const
@@ -395,9 +394,7 @@ public:
   bool is_valid(const IpEndpoint &src) const;
 
   const QUICConnectionId original_dcid() const;
-
-private:
-  QUICConnectionId _original_dcid;
+  const QUICConnectionId scid() const;
 };
 
 class QUICPreferredAddress
@@ -551,13 +548,54 @@ class QUICCCConfig
 {
 public:
   virtual ~QUICCCConfig() {}
-  virtual uint32_t max_datagram_size() const               = 0;
   virtual uint32_t initial_window() const                  = 0;
   virtual uint32_t minimum_window() const                  = 0;
   virtual float loss_reduction_factor() const              = 0;
   virtual uint32_t persistent_congestion_threshold() const = 0;
 };
 
+class QUICFrameGenerator;
+
+struct QUICSentPacketInfo {
+  class FrameInfo
+  {
+  public:
+    FrameInfo(QUICFrameId id, QUICFrameGenerator *generator) : _id(id), _generator(generator) {}
+
+    QUICFrameId id() const;
+    QUICFrameGenerator *generated_by() const;
+
+  private:
+    QUICFrameId _id = 0;
+    QUICFrameGenerator *_generator;
+  };
+
+  // Recovery A.1.1.  Sent Packet Fields
+  QUICPacketNumber packet_number;
+  bool ack_eliciting;
+  bool in_flight;
+  size_t sent_bytes;
+  ink_hrtime time_sent;
+
+  // Additional fields
+  QUICPacketType type;
+  std::vector<FrameInfo> frames;
+  QUICPacketNumberSpace pn_space;
+  // End of additional fields
+};
+
+using QUICSentPacketInfoUPtr = std::unique_ptr<QUICSentPacketInfo>;
+
+class QUICRTTProvider
+{
+public:
+  virtual ink_hrtime smoothed_rtt() const = 0;
+  virtual ink_hrtime rttvar() const       = 0;
+  virtual ink_hrtime latest_rtt() const   = 0;
+
+  virtual ink_hrtime congestion_period(uint32_t threshold) const = 0;
+};
+
 // TODO: move version independent functions to QUICInvariants
 class QUICTypeUtil
 {
diff --git a/iocore/net/quic/qlog/QLogListener.h b/iocore/net/quic/qlog/QLogListener.h
index 7b55af2c..9337b07 100644
--- a/iocore/net/quic/qlog/QLogListener.h
+++ b/iocore/net/quic/qlog/QLogListener.h
@@ -73,7 +73,7 @@ public:
   };
 
   void
-  packet_lost_callback(QUICCallbackContext &, const QUICPacketInfo &packet) override
+  packet_lost_callback(QUICCallbackContext &, const QUICSentPacketInfo &packet) override
   {
     auto qe = std::make_unique<Recovery::PacketLost>(PacketTypeToName(packet.type), packet.packet_number);
     this->_log.last_trace().push_event(std::move(qe));
diff --git a/iocore/net/quic/test/test_QUICAckFrameCreator.cc b/iocore/net/quic/test/test_QUICAckFrameCreator.cc
index 2b66f06..eb96869 100644
--- a/iocore/net/quic/test/test_QUICAckFrameCreator.cc
+++ b/iocore/net/quic/test/test_QUICAckFrameCreator.cc
@@ -283,7 +283,7 @@ TEST_CASE("QUICAckFrameManager_loss_recover", "[quic]")
 TEST_CASE("QUICAckFrameManager_QUICAckFrameCreator", "[quic]")
 {
   QUICAckFrameManager ack_manager;
-  QUICAckFrameManager::QUICAckFrameCreator packet_numbers(QUICPacketNumberSpace::Initial, &ack_manager);
+  QUICAckFrameManager::QUICAckFrameCreator packet_numbers(QUICPacketNumberSpace::INITIAL, &ack_manager);
 
   CHECK(packet_numbers.size() == 0);
   CHECK(packet_numbers.largest_ack_number() == 0);
diff --git a/iocore/net/quic/test/test_QUICHandshakeProtocol.cc b/iocore/net/quic/test/test_QUICHandshakeProtocol.cc
index 4a71a07..e697868 100644
--- a/iocore/net/quic/test/test_QUICHandshakeProtocol.cc
+++ b/iocore/net/quic/test/test_QUICHandshakeProtocol.cc
@@ -33,11 +33,12 @@
 
 #include <openssl/ssl.h>
 
-// #include "Mock.h"
 #include "QUICPacketHeaderProtector.h"
 #include "QUICPacketPayloadProtector.h"
 #include "QUICPacketProtectionKeyInfo.h"
 #include "QUICTLS.h"
+#include "QUICGlobals.h"
+#include "Mock.h"
 
 // XXX For NetVCOptions::reset
 struct PollCont;
@@ -107,8 +108,12 @@ TEST_CASE("QUICHandshakeProtocol")
     QUICPacketProtectionKeyInfo pp_key_info_client;
     QUICPacketProtectionKeyInfo pp_key_info_server;
     NetVCOptions netvc_options;
+    MockQUICConnection mock_client_connection;
+    MockQUICConnection mock_server_connection;
     QUICHandshakeProtocol *client = new QUICTLS(pp_key_info_client, client_ssl_ctx, NET_VCONNECTION_OUT, netvc_options);
     QUICHandshakeProtocol *server = new QUICTLS(pp_key_info_server, server_ssl_ctx, NET_VCONNECTION_IN, netvc_options);
+    SSL_set_ex_data(static_cast<QUICTLS *>(client)->ssl_handle(), QUIC::ssl_quic_qc_index, &mock_client_connection);
+    SSL_set_ex_data(static_cast<QUICTLS *>(server)->ssl_handle(), QUIC::ssl_quic_qc_index, &mock_server_connection);
     QUICPacketPayloadProtector ppp_client(pp_key_info_client);
     QUICPacketPayloadProtector ppp_server(pp_key_info_server);
 
@@ -119,8 +124,10 @@ TEST_CASE("QUICHandshakeProtocol")
     client->set_local_transport_parameters(client_tp);
     server->set_local_transport_parameters(server_tp);
 
-    CHECK(client->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8}));
-    CHECK(server->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8}));
+    CHECK(client->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8},
+                                           QUIC_SUPPORTED_VERSIONS[0]));
+    CHECK(server->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8},
+                                           QUIC_SUPPORTED_VERSIONS[0]));
 
     // CH
     QUICHandshakeMsgs msg0;
@@ -232,8 +239,12 @@ TEST_CASE("QUICHandshakeProtocol")
     QUICPacketProtectionKeyInfo pp_key_info_client;
     QUICPacketProtectionKeyInfo pp_key_info_server;
     NetVCOptions netvc_options;
+    MockQUICConnection mock_client_connection;
+    MockQUICConnection mock_server_connection;
     QUICHandshakeProtocol *client = new QUICTLS(pp_key_info_client, client_ssl_ctx, NET_VCONNECTION_OUT, netvc_options);
     QUICHandshakeProtocol *server = new QUICTLS(pp_key_info_server, server_ssl_ctx, NET_VCONNECTION_IN, netvc_options);
+    SSL_set_ex_data(static_cast<QUICTLS *>(client)->ssl_handle(), QUIC::ssl_quic_qc_index, &mock_client_connection);
+    SSL_set_ex_data(static_cast<QUICTLS *>(server)->ssl_handle(), QUIC::ssl_quic_qc_index, &mock_server_connection);
     QUICPacketPayloadProtector ppp_client(pp_key_info_client);
     QUICPacketPayloadProtector ppp_server(pp_key_info_server);
 
@@ -244,8 +255,10 @@ TEST_CASE("QUICHandshakeProtocol")
     client->set_local_transport_parameters(client_tp);
     server->set_local_transport_parameters(server_tp);
 
-    CHECK(client->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8}));
-    CHECK(server->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8}));
+    CHECK(client->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8},
+                                           QUIC_SUPPORTED_VERSIONS[0]));
+    CHECK(server->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8},
+                                           QUIC_SUPPORTED_VERSIONS[0]));
 
     // CH
     QUICHandshakeMsgs msg0;
@@ -369,7 +382,8 @@ TEST_CASE("QUICHandshakeProtocol")
     QUICPacketProtectionKeyInfo pp_key_info_server;
     NetVCOptions netvc_options;
     QUICHandshakeProtocol *server = new QUICTLS(pp_key_info_server, server_ssl_ctx, NET_VCONNECTION_IN, netvc_options);
-    CHECK(server->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8}));
+    CHECK(server->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8},
+                                           QUIC_SUPPORTED_VERSIONS[0]));
 
     // Malformed CH (finished)
     uint8_t msg1_buf[] = {0x14, 0x00, 0x00, 0x30, 0x35, 0xb9, 0x82, 0x9d, 0xb9, 0x14, 0x70, 0x03, 0x60,
@@ -401,8 +415,12 @@ TEST_CASE("QUICHandshakeProtocol")
     QUICPacketProtectionKeyInfo pp_key_info_client;
     QUICPacketProtectionKeyInfo pp_key_info_server;
     NetVCOptions netvc_options;
+    MockQUICConnection mock_client_connection;
+    MockQUICConnection mock_server_connection;
     QUICHandshakeProtocol *client = new QUICTLS(pp_key_info_client, client_ssl_ctx, NET_VCONNECTION_OUT, netvc_options);
     QUICHandshakeProtocol *server = new QUICTLS(pp_key_info_server, server_ssl_ctx, NET_VCONNECTION_IN, netvc_options);
+    SSL_set_ex_data(static_cast<QUICTLS *>(client)->ssl_handle(), QUIC::ssl_quic_qc_index, &mock_client_connection);
+    SSL_set_ex_data(static_cast<QUICTLS *>(server)->ssl_handle(), QUIC::ssl_quic_qc_index, &mock_server_connection);
 
     auto client_tp = std::make_shared<QUICTransportParametersInClientHello>();
     auto server_tp = std::make_shared<QUICTransportParametersInEncryptedExtensions>();
@@ -411,8 +429,10 @@ TEST_CASE("QUICHandshakeProtocol")
     client->set_local_transport_parameters(client_tp);
     server->set_local_transport_parameters(server_tp);
 
-    CHECK(client->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8}));
-    CHECK(server->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8}));
+    CHECK(client->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8},
+                                           QUIC_SUPPORTED_VERSIONS[0]));
+    CHECK(server->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8},
+                                           QUIC_SUPPORTED_VERSIONS[0]));
 
     // # Start Handshake
 
diff --git a/iocore/net/quic/test/test_QUICKeyGenerator.cc b/iocore/net/quic/test/test_QUICKeyGenerator.cc
index 4c1858a..cd9ff38 100644
--- a/iocore/net/quic/test/test_QUICKeyGenerator.cc
+++ b/iocore/net/quic/test/test_QUICKeyGenerator.cc
@@ -35,8 +35,8 @@
 #include "QUICKeyGenerator.h"
 #include "QUICPacketProtectionKeyInfo.h"
 
-// https://github.com/quicwg/base-drafts/wiki/Test-Vector-for-the-Clear-Text-AEAD-key-derivation
-TEST_CASE("draft-23 Test Vectors", "[quic]")
+// https://github.com/quicwg/base-drafts/wiki/Test-Vector-for-the-Initial-AEAD-key-derivation
+TEST_CASE("draft-23~27 Test Vectors", "[quic]")
 {
   SECTION("CLIENT Initial")
   {
@@ -51,8 +51,9 @@ TEST_CASE("draft-23 Test Vectors", "[quic]")
     QUICPacketProtectionKeyInfo pp_key_info;
     pp_key_info.set_cipher_initial(EVP_aes_128_gcm());
     pp_key_info.set_cipher_for_hp_initial(EVP_aes_128_ecb());
-    keygen.generate(pp_key_info.encryption_key_for_hp(QUICKeyPhase::INITIAL), pp_key_info.encryption_key(QUICKeyPhase::INITIAL),
-                    pp_key_info.encryption_iv(QUICKeyPhase::INITIAL), pp_key_info.encryption_iv_len(QUICKeyPhase::INITIAL), cid);
+    keygen.generate(0xff00001b, pp_key_info.encryption_key_for_hp(QUICKeyPhase::INITIAL),
+                    pp_key_info.encryption_key(QUICKeyPhase::INITIAL), pp_key_info.encryption_iv(QUICKeyPhase::INITIAL),
+                    pp_key_info.encryption_iv_len(QUICKeyPhase::INITIAL), cid);
 
     CHECK(pp_key_info.encryption_key_len(QUICKeyPhase::INITIAL) == sizeof(expected_client_key));
     CHECK(memcmp(pp_key_info.encryption_key(QUICKeyPhase::INITIAL), expected_client_key, sizeof(expected_client_key)) == 0);
@@ -76,8 +77,9 @@ TEST_CASE("draft-23 Test Vectors", "[quic]")
     QUICPacketProtectionKeyInfo pp_key_info;
     pp_key_info.set_cipher_initial(EVP_aes_128_gcm());
     pp_key_info.set_cipher_for_hp_initial(EVP_aes_128_ecb());
-    keygen.generate(pp_key_info.encryption_key_for_hp(QUICKeyPhase::INITIAL), pp_key_info.encryption_key(QUICKeyPhase::INITIAL),
-                    pp_key_info.encryption_iv(QUICKeyPhase::INITIAL), pp_key_info.encryption_iv_len(QUICKeyPhase::INITIAL), cid);
+    keygen.generate(0xff00001b, pp_key_info.encryption_key_for_hp(QUICKeyPhase::INITIAL),
+                    pp_key_info.encryption_key(QUICKeyPhase::INITIAL), pp_key_info.encryption_iv(QUICKeyPhase::INITIAL),
+                    pp_key_info.encryption_iv_len(QUICKeyPhase::INITIAL), cid);
 
     CHECK(pp_key_info.encryption_key_len(QUICKeyPhase::INITIAL) == sizeof(expected_server_key));
     CHECK(memcmp(pp_key_info.encryption_key(QUICKeyPhase::INITIAL), expected_server_key, sizeof(expected_server_key)) == 0);
diff --git a/iocore/net/quic/test/test_QUICLossDetector.cc b/iocore/net/quic/test/test_QUICLossDetector.cc
index acabd82..5dd8271 100644
--- a/iocore/net/quic/test/test_QUICLossDetector.cc
+++ b/iocore/net/quic/test/test_QUICLossDetector.cc
@@ -27,6 +27,8 @@
 #include "QUICPacketFactory.h"
 #include "QUICAckFrameCreator.h"
 #include "QUICEvents.h"
+#include "QUICPacketFactory.h"
+#include "QUICAckFrameCreator.h"
 #include "Mock.h"
 #include "tscore/ink_hrtime.h"
 
@@ -79,16 +81,15 @@ TEST_CASE("QUICLossDetector_Loss", "[quic]")
       {reinterpret_cast<const uint8_t *>("\x11\x12\x13\x14\x15\x16\x17\x18"), 8}, sizeof(raw), 0, true, true, false);
     handshake_packet->attach_payload(payload, true);
     QUICPacketUPtr packet = QUICPacketUPtr(handshake_packet, [](QUICPacket *p) { delete p; });
-    detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{
+    detector.on_packet_sent(QUICSentPacketInfoUPtr(new QUICSentPacketInfo{
       packet->packet_number(),
-      Thread::get_hrtime(),
       packet->is_ack_eliciting(),
-      static_cast<QUICLongHeaderPacket &>(*packet).is_crypto_packet(),
       true,
       packet->size(),
+      Thread::get_hrtime(),
       packet->type(),
       {},
-      QUICPacketNumberSpace::Handshake,
+      QUICPacketNumberSpace::HANDSHAKE,
     }));
     ink_hrtime_sleep(HRTIME_MSECONDS(1000));
     CHECK(g.lost_frame_count >= 0);
@@ -106,7 +107,7 @@ TEST_CASE("QUICLossDetector_Loss", "[quic]")
   SECTION("1-RTT")
   {
     // Send packet (1) to (7)
-    QUICPacketNumberSpace pn_space = QUICPacketNumberSpace::ApplicationData;
+    QUICPacketNumberSpace pn_space = QUICPacketNumberSpace::APPLICATION_DATA;
     QUICEncryptionLevel level      = QUICEncryptionLevel::ONE_RTT;
     payload                        = make_ptr<IOBufferBlock>(new_IOBufferBlock());
     payload->alloc(iobuffer_size_to_index(payload_len, BUFFER_SIZE_INDEX_32K));
@@ -181,97 +182,87 @@ TEST_CASE("QUICLossDetector_Loss", "[quic]")
     QUICPacketNumber pn9  = packet9->packet_number();
     QUICPacketNumber pn10 = packet10->packet_number();
 
-    QUICPacketInfoUPtr packet_info = nullptr;
-    detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{packet1->packet_number(),
-                                                                  Thread::get_hrtime(),
-                                                                  packet1->is_ack_eliciting(),
-                                                                  false,
-                                                                  true,
-                                                                  packet1->size(),
-                                                                  packet1->type(),
-                                                                  {},
-                                                                  pn_space}));
-    detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{packet2->packet_number(),
-                                                                  Thread::get_hrtime(),
-                                                                  packet2->is_ack_eliciting(),
-                                                                  false,
-                                                                  true,
-                                                                  packet2->size(),
-                                                                  packet2->type(),
-                                                                  {},
-                                                                  pn_space}));
-    detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{packet3->packet_number(),
-                                                                  Thread::get_hrtime(),
-                                                                  packet3->is_ack_eliciting(),
-                                                                  false,
-                                                                  true,
-                                                                  packet3->size(),
-                                                                  packet3->type(),
-                                                                  {},
-                                                                  pn_space}));
-    detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{packet4->packet_number(),
-                                                                  Thread::get_hrtime(),
-                                                                  packet4->is_ack_eliciting(),
-                                                                  false,
-                                                                  true,
-                                                                  packet4->size(),
-                                                                  packet4->type(),
-                                                                  {},
-                                                                  pn_space}));
-    detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{packet5->packet_number(),
-                                                                  Thread::get_hrtime(),
-                                                                  packet5->is_ack_eliciting(),
-                                                                  false,
-                                                                  true,
-                                                                  packet5->size(),
-                                                                  packet5->type(),
-                                                                  {},
-                                                                  pn_space}));
-    detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{packet6->packet_number(),
-                                                                  Thread::get_hrtime(),
-                                                                  packet6->is_ack_eliciting(),
-                                                                  false,
-                                                                  true,
-                                                                  packet6->size(),
-                                                                  packet6->type(),
-                                                                  {},
-                                                                  pn_space}));
-    detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{packet7->packet_number(),
-                                                                  Thread::get_hrtime(),
-                                                                  packet6->is_ack_eliciting(),
-                                                                  false,
-                                                                  true,
-                                                                  packet7->size(),
-                                                                  packet7->type(),
-                                                                  {},
-                                                                  pn_space}));
-    detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{packet8->packet_number(),
-                                                                  Thread::get_hrtime(),
-                                                                  packet6->is_ack_eliciting(),
-                                                                  false,
-                                                                  true,
-                                                                  packet8->size(),
-                                                                  packet8->type(),
-                                                                  {},
-                                                                  pn_space}));
-    detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{packet9->packet_number(),
-                                                                  Thread::get_hrtime(),
-                                                                  packet6->is_ack_eliciting(),
-                                                                  false,
-                                                                  true,
-                                                                  packet9->size(),
-                                                                  packet9->type(),
-                                                                  {},
-                                                                  pn_space}));
-    detector.on_packet_sent(QUICPacketInfoUPtr(new QUICPacketInfo{packet10->packet_number(),
-                                                                  Thread::get_hrtime(),
-                                                                  packet10->is_ack_eliciting(),
-                                                                  false,
-                                                                  true,
-                                                                  packet10->size(),
-                                                                  packet10->type(),
-                                                                  {},
-                                                                  pn_space}));
+    QUICSentPacketInfoUPtr packet_info = nullptr;
+    detector.on_packet_sent(QUICSentPacketInfoUPtr(new QUICSentPacketInfo{packet1->packet_number(),
+                                                                          packet1->is_ack_eliciting(),
+                                                                          true,
+                                                                          packet1->size(),
+                                                                          Thread::get_hrtime(),
+                                                                          packet1->type(),
+                                                                          {},
+                                                                          pn_space}));
+    detector.on_packet_sent(QUICSentPacketInfoUPtr(new QUICSentPacketInfo{packet2->packet_number(),
+                                                                          packet2->is_ack_eliciting(),
+                                                                          true,
+                                                                          packet2->size(),
+                                                                          Thread::get_hrtime(),
+                                                                          packet2->type(),
+                                                                          {},
+                                                                          pn_space}));
+    detector.on_packet_sent(QUICSentPacketInfoUPtr(new QUICSentPacketInfo{packet3->packet_number(),
+                                                                          packet3->is_ack_eliciting(),
+                                                                          true,
+                                                                          packet3->size(),
+                                                                          Thread::get_hrtime(),
+                                                                          packet3->type(),
+                                                                          {},
+                                                                          pn_space}));
+    detector.on_packet_sent(QUICSentPacketInfoUPtr(new QUICSentPacketInfo{packet4->packet_number(),
+                                                                          packet4->is_ack_eliciting(),
+                                                                          true,
+                                                                          packet4->size(),
+                                                                          Thread::get_hrtime(),
+                                                                          packet4->type(),
+                                                                          {},
+                                                                          pn_space}));
+    detector.on_packet_sent(QUICSentPacketInfoUPtr(new QUICSentPacketInfo{packet5->packet_number(),
+                                                                          packet5->is_ack_eliciting(),
+                                                                          true,
+                                                                          packet5->size(),
+                                                                          Thread::get_hrtime(),
+                                                                          packet5->type(),
+                                                                          {},
+                                                                          pn_space}));
+    detector.on_packet_sent(QUICSentPacketInfoUPtr(new QUICSentPacketInfo{packet6->packet_number(),
+                                                                          packet6->is_ack_eliciting(),
+                                                                          true,
+                                                                          packet6->size(),
+                                                                          Thread::get_hrtime(),
+                                                                          packet6->type(),
+                                                                          {},
+                                                                          pn_space}));
+    detector.on_packet_sent(QUICSentPacketInfoUPtr(new QUICSentPacketInfo{packet7->packet_number(),
+                                                                          packet6->is_ack_eliciting(),
+                                                                          true,
+                                                                          packet7->size(),
+                                                                          Thread::get_hrtime(),
+                                                                          packet7->type(),
+                                                                          {},
+                                                                          pn_space}));
+    detector.on_packet_sent(QUICSentPacketInfoUPtr(new QUICSentPacketInfo{packet8->packet_number(),
+                                                                          packet6->is_ack_eliciting(),
+                                                                          true,
+                                                                          packet8->size(),
+                                                                          Thread::get_hrtime(),
+                                                                          packet8->type(),
+                                                                          {},
+                                                                          pn_space}));
+    detector.on_packet_sent(QUICSentPacketInfoUPtr(new QUICSentPacketInfo{packet9->packet_number(),
+                                                                          packet6->is_ack_eliciting(),
+                                                                          true,
+                                                                          packet9->size(),
+                                                                          Thread::get_hrtime(),
+                                                                          packet9->type(),
+                                                                          {},
+                                                                          pn_space}));
+    detector.on_packet_sent(QUICSentPacketInfoUPtr(new QUICSentPacketInfo{packet10->packet_number(),
+                                                                          packet10->is_ack_eliciting(),
+                                                                          true,
+                                                                          packet10->size(),
+                                                                          Thread::get_hrtime(),
+                                                                          packet10->type(),
+                                                                          {},
+                                                                          pn_space}));
 
     ink_hrtime_sleep(HRTIME_MSECONDS(2000));
     // Receive an ACK for (1) (4) (5) (7) (8) (9)
diff --git a/iocore/net/quic/test/test_QUICPacket.cc b/iocore/net/quic/test/test_QUICPacket.cc
index 5dda30a..35ffd5e 100644
--- a/iocore/net/quic/test/test_QUICPacket.cc
+++ b/iocore/net/quic/test/test_QUICPacket.cc
@@ -44,7 +44,7 @@ TEST_CASE("Receiving Packet", "[quic]")
       0x08,                                           // SCID Len
       0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Source Connection ID
       0x00, 0x00, 0x00, 0x08,                         // Supported Version 1
-      0x00, 0x00, 0x00, 0x09,                         // Supported Version 1
+      0x00, 0x00, 0x00, 0x09,                         // Supported Version 2
     };
     Ptr<IOBufferBlock> input_ibb = make_ptr<IOBufferBlock>(new_IOBufferBlock());
     input_ibb->set_internal(static_cast<void *>(input), sizeof(input), BUFFER_SIZE_NOT_ALLOCATED);
@@ -255,8 +255,8 @@ TEST_CASE("Sending Packet", "[quic]")
 
   SECTION("RETRY Packet (store)")
   {
-    uint8_t buf[64] = {0};
-    size_t len      = 0;
+    uint8_t buf[128] = {0};
+    size_t len       = 0;
 
     const uint8_t expected[] = {
       0xf0,                                           // Long header, Type: RETRY
@@ -267,11 +267,13 @@ TEST_CASE("Sending Packet", "[quic]")
       0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Source Connection ID
       0x01, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // Retry Token
       0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, //
-      0x10, 0x11, 0x12, 0x13, 0x14, 0xf0, 0xf1, 0xf2, //
+      0x10, 0x11, 0x12, 0x13, 0x14, 0x08, 0x01, 0x02, //
+      0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x08, 0x11, //
+      0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,       //
       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Retry Integrity Tag
       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     };
-    QUICRetryToken token(expected + 23, 24);
+    QUICRetryToken token(expected + 23, 39);
 
     QUICRetryPacket packet(0x11223344, {reinterpret_cast<const uint8_t *>("\x01\x02\x03\x04\x05\x06\x07\x08"), 8},
                            {reinterpret_cast<const uint8_t *>("\x11\x12\x13\x14\x15\x16\x17\x18"), 8}, token);
@@ -285,6 +287,21 @@ TEST_CASE("Sending Packet", "[quic]")
     CHECK(len == packet.size());
     CHECK(memcmp(buf, expected, sizeof(expected) - 16) == 0);
   }
+
+  SECTION("VersionNegotiation Packet")
+  {
+    QUICConnectionId dummy;
+    QUICVersionNegotiationPacket vn1(dummy, dummy, QUIC_SUPPORTED_VERSIONS, countof(QUIC_SUPPORTED_VERSIONS),
+                                     QUIC_EXERCISE_VERSION1);
+    for (auto i = 0; i < vn1.nversions(); ++i) {
+      REQUIRE(vn1.versions()[i] != QUIC_EXERCISE_VERSION1);
+    }
+    QUICVersionNegotiationPacket vn2(dummy, dummy, QUIC_SUPPORTED_VERSIONS, countof(QUIC_SUPPORTED_VERSIONS),
+                                     QUIC_EXERCISE_VERSION2);
+    for (auto i = 0; i < vn2.nversions(); ++i) {
+      REQUIRE(vn2.versions()[i] != QUIC_EXERCISE_VERSION2);
+    }
+  }
 }
 
 TEST_CASE("Encoded Packet Number Length", "[quic]")
diff --git a/iocore/net/quic/test/test_QUICPacketFactory.cc b/iocore/net/quic/test/test_QUICPacketFactory.cc
index 0df3436..57bf6b3 100644
--- a/iocore/net/quic/test/test_QUICPacketFactory.cc
+++ b/iocore/net/quic/test/test_QUICPacketFactory.cc
@@ -37,7 +37,7 @@ TEST_CASE("QUICPacketFactory_Create_VersionNegotiationPacket", "[quic]")
   QUICConnectionId dcid(raw_dcid, 8);
   QUICConnectionId scid(raw_scid, 8);
 
-  QUICPacketUPtr packet = factory.create_version_negotiation_packet(scid, dcid);
+  QUICPacketUPtr packet = factory.create_version_negotiation_packet(scid, dcid, QUIC_EXERCISE_VERSION1);
   REQUIRE(packet != nullptr);
 
   QUICVersionNegotiationPacket &vn_packet = static_cast<QUICVersionNegotiationPacket &>(*packet);
@@ -56,8 +56,9 @@ TEST_CASE("QUICPacketFactory_Create_VersionNegotiationPacket", "[quic]")
     0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Destination Connection ID
     0x08,                                           // SCID Len
     0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Source Connection ID
+    0xff, 0x00, 0x00, 0x1d,                         // Supported Version
     0xff, 0x00, 0x00, 0x1b,                         // Supported Version
-    0x1a, 0x2a, 0x3a, 0x4a,                         // Exercise Version
+    0x5a, 0x6a, 0x7a, 0x8a,                         // Exercise Version
   };
   uint8_t buf[1024] = {0};
   size_t buf_len;
@@ -75,9 +76,9 @@ TEST_CASE("QUICPacketFactory_Create_Retry", "[quic]")
   uint8_t raw[] = {0xaa, 0xbb, 0xcc, 0xdd};
   QUICRetryToken token(raw, 4);
 
-  QUICPacketUPtr packet =
-    factory.create_retry_packet(QUICConnectionId(reinterpret_cast<const uint8_t *>("\x01\x02\x03\x04"), 4),
-                                QUICConnectionId(reinterpret_cast<const uint8_t *>("\x11\x12\x13\x14"), 4), token);
+  QUICPacketUPtr packet = factory.create_retry_packet(
+    QUIC_SUPPORTED_VERSIONS[0], QUICConnectionId(reinterpret_cast<const uint8_t *>("\x01\x02\x03\x04"), 4),
+    QUICConnectionId(reinterpret_cast<const uint8_t *>("\x11\x12\x13\x14"), 4), token);
 
   REQUIRE(packet != nullptr);
 
diff --git a/iocore/net/quic/test/test_QUICPacketHeaderProtector.cc b/iocore/net/quic/test/test_QUICPacketHeaderProtector.cc
index 031aa51..0625595 100644
--- a/iocore/net/quic/test/test_QUICPacketHeaderProtector.cc
+++ b/iocore/net/quic/test/test_QUICPacketHeaderProtector.cc
@@ -26,6 +26,8 @@
 #include "QUICPacketProtectionKeyInfo.h"
 #include "QUICPacketHeaderProtector.h"
 #include "QUICTLS.h"
+#include "QUICGlobals.h"
+#include "Mock.h"
 
 struct PollCont;
 #include "P_UDPConnection.h"
@@ -87,8 +89,10 @@ TEST_CASE("QUICPacketHeaderProtector")
     QUICHandshakeProtocol *client = new QUICTLS(pp_key_info_client, client_ssl_ctx, NET_VCONNECTION_OUT, netvc_options);
     QUICHandshakeProtocol *server = new QUICTLS(pp_key_info_server, server_ssl_ctx, NET_VCONNECTION_IN, netvc_options);
 
-    CHECK(client->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8}));
-    CHECK(server->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8}));
+    CHECK(client->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8},
+                                           QUIC_SUPPORTED_VERSIONS[0]));
+    CHECK(server->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8},
+                                           QUIC_SUPPORTED_VERSIONS[0]));
 
     QUICPacketHeaderProtector client_ph_protector(pp_key_info_client);
     QUICPacketHeaderProtector server_ph_protector(pp_key_info_server);
@@ -121,8 +125,12 @@ TEST_CASE("QUICPacketHeaderProtector")
     QUICPacketProtectionKeyInfo pp_key_info_client;
     QUICPacketProtectionKeyInfo pp_key_info_server;
     NetVCOptions netvc_options;
+    MockQUICConnection mock_client_connection;
+    MockQUICConnection mock_server_connection;
     QUICHandshakeProtocol *client = new QUICTLS(pp_key_info_client, client_ssl_ctx, NET_VCONNECTION_OUT, netvc_options);
     QUICHandshakeProtocol *server = new QUICTLS(pp_key_info_server, server_ssl_ctx, NET_VCONNECTION_IN, netvc_options);
+    SSL_set_ex_data(static_cast<QUICTLS *>(client)->ssl_handle(), QUIC::ssl_quic_qc_index, &mock_client_connection);
+    SSL_set_ex_data(static_cast<QUICTLS *>(server)->ssl_handle(), QUIC::ssl_quic_qc_index, &mock_server_connection);
 
     auto client_tp = std::make_shared<QUICTransportParametersInClientHello>();
     auto server_tp = std::make_shared<QUICTransportParametersInEncryptedExtensions>();
@@ -131,8 +139,10 @@ TEST_CASE("QUICPacketHeaderProtector")
     client->set_local_transport_parameters(client_tp);
     server->set_local_transport_parameters(server_tp);
 
-    CHECK(client->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8}));
-    CHECK(server->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8}));
+    CHECK(client->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8},
+                                           QUIC_SUPPORTED_VERSIONS[0]));
+    CHECK(server->initialize_key_materials({reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x00"), 8},
+                                           QUIC_SUPPORTED_VERSIONS[0]));
 
     QUICPacketHeaderProtector client_ph_protector(pp_key_info_client);
     QUICPacketHeaderProtector server_ph_protector(pp_key_info_server);
diff --git a/iocore/net/quic/test/test_QUICStreamManager.cc b/iocore/net/quic/test/test_QUICStreamManager.cc
index 8d7ddf2..ad9443d 100644
--- a/iocore/net/quic/test/test_QUICStreamManager.cc
+++ b/iocore/net/quic/test/test_QUICStreamManager.cc
@@ -47,7 +47,7 @@ TEST_CASE("QUICStreamManager_NewStream", "[quic]")
     0x40, 0x10 // value
   };
   std::shared_ptr<QUICTransportParameters> local_tp =
-    std::make_shared<QUICTransportParametersInEncryptedExtensions>(local_tp_buf, sizeof(local_tp_buf));
+    std::make_shared<QUICTransportParametersInEncryptedExtensions>(local_tp_buf, sizeof(local_tp_buf), QUIC_SUPPORTED_VERSIONS[0]);
 
   uint8_t remote_tp_buf[] = {
     0x08,      // parameter id - initial_max_streams_bidi
@@ -55,7 +55,7 @@ TEST_CASE("QUICStreamManager_NewStream", "[quic]")
     0x40, 0x10 // value
   };
   std::shared_ptr<QUICTransportParameters> remote_tp =
-    std::make_shared<QUICTransportParametersInClientHello>(remote_tp_buf, sizeof(remote_tp_buf));
+    std::make_shared<QUICTransportParametersInClientHello>(remote_tp_buf, sizeof(remote_tp_buf), QUIC_SUPPORTED_VERSIONS[0]);
 
   sm.init_flow_control_params(local_tp, remote_tp);
 
@@ -139,7 +139,7 @@ TEST_CASE("QUICStreamManager_total_offset_received", "[quic]")
     0xbf, 0xff, 0xff, 0xff // value
   };
   std::shared_ptr<QUICTransportParameters> local_tp =
-    std::make_shared<QUICTransportParametersInEncryptedExtensions>(local_tp_buf, sizeof(local_tp_buf));
+    std::make_shared<QUICTransportParametersInEncryptedExtensions>(local_tp_buf, sizeof(local_tp_buf), QUIC_SUPPORTED_VERSIONS[0]);
 
   uint8_t remote_tp_buf[] = {
     0x08,                  // parameter id - initial_max_streams_bidi
@@ -150,7 +150,7 @@ TEST_CASE("QUICStreamManager_total_offset_received", "[quic]")
     0xbf, 0xff, 0xff, 0xff // value
   };
   std::shared_ptr<QUICTransportParameters> remote_tp =
-    std::make_shared<QUICTransportParametersInClientHello>(remote_tp_buf, sizeof(remote_tp_buf));
+    std::make_shared<QUICTransportParametersInClientHello>(remote_tp_buf, sizeof(remote_tp_buf), QUIC_SUPPORTED_VERSIONS[0]);
 
   sm.init_flow_control_params(local_tp, remote_tp);
 
@@ -194,7 +194,7 @@ TEST_CASE("QUICStreamManager_total_offset_sent", "[quic]")
     0xbf, 0xff, 0xff, 0xff // value
   };
   std::shared_ptr<QUICTransportParameters> local_tp =
-    std::make_shared<QUICTransportParametersInEncryptedExtensions>(local_tp_buf, sizeof(local_tp_buf));
+    std::make_shared<QUICTransportParametersInEncryptedExtensions>(local_tp_buf, sizeof(local_tp_buf), QUIC_SUPPORTED_VERSIONS[0]);
 
   uint8_t remote_tp_buf[] = {
     0x08,                  // parameter id - initial_max_streams_bidi
@@ -205,7 +205,7 @@ TEST_CASE("QUICStreamManager_total_offset_sent", "[quic]")
     0xbf, 0xff, 0xff, 0xff // value
   };
   std::shared_ptr<QUICTransportParameters> remote_tp =
-    std::make_shared<QUICTransportParametersInClientHello>(remote_tp_buf, sizeof(remote_tp_buf));
+    std::make_shared<QUICTransportParametersInClientHello>(remote_tp_buf, sizeof(remote_tp_buf), QUIC_SUPPORTED_VERSIONS[0]);
 
   sm.init_flow_control_params(local_tp, remote_tp);
 
@@ -262,7 +262,7 @@ TEST_CASE("QUICStreamManager_max_streams", "[quic]")
     0x03, // value
   };
   std::shared_ptr<QUICTransportParameters> local_tp =
-    std::make_shared<QUICTransportParametersInEncryptedExtensions>(local_tp_buf, sizeof(local_tp_buf));
+    std::make_shared<QUICTransportParametersInEncryptedExtensions>(local_tp_buf, sizeof(local_tp_buf), QUIC_SUPPORTED_VERSIONS[0]);
 
   uint8_t remote_tp_buf[] = {
     0x08, // parameter id - initial_max_streams_bidi
@@ -273,7 +273,7 @@ TEST_CASE("QUICStreamManager_max_streams", "[quic]")
     0x03, // value
   };
   std::shared_ptr<QUICTransportParameters> remote_tp =
-    std::make_shared<QUICTransportParametersInClientHello>(remote_tp_buf, sizeof(remote_tp_buf));
+    std::make_shared<QUICTransportParametersInClientHello>(remote_tp_buf, sizeof(remote_tp_buf), QUIC_SUPPORTED_VERSIONS[0]);
 
   sm.init_flow_control_params(local_tp, remote_tp);
 
diff --git a/iocore/net/quic/test/test_QUICTransportParameters.cc b/iocore/net/quic/test/test_QUICTransportParameters.cc
index a225b86..cddad46 100644
--- a/iocore/net/quic/test/test_QUICTransportParameters.cc
+++ b/iocore/net/quic/test/test_QUICTransportParameters.cc
@@ -44,13 +44,13 @@ TEST_CASE("QUICTransportParametersInClientHello_read", "[quic]")
       0x05, 0x67,             // value
     };
 
-    QUICTransportParametersInClientHello params_in_ch(buf, sizeof(buf));
+    QUICTransportParametersInClientHello params_in_ch(buf, sizeof(buf), QUIC_SUPPORTED_VERSIONS[0]);
     CHECK(params_in_ch.is_valid());
 
     uint16_t len        = 0;
     const uint8_t *data = nullptr;
 
-    data = params_in_ch.getAsBytes(QUICTransportParameterId::ORIGINAL_CONNECTION_ID, len);
+    data = params_in_ch.getAsBytes(QUICTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID, len);
     CHECK(len == 4);
     CHECK(memcmp(data, "\x11\x22\x33\x44", 4) == 0);
 
@@ -62,7 +62,7 @@ TEST_CASE("QUICTransportParametersInClientHello_read", "[quic]")
     CHECK(len == 2);
     CHECK(memcmp(data, "\x0a\x0b", 2) == 0);
 
-    data = params_in_ch.getAsBytes(QUICTransportParameterId::MAX_PACKET_SIZE, len);
+    data = params_in_ch.getAsBytes(QUICTransportParameterId::MAX_UDP_PAYLOAD_SIZE, len);
     CHECK(len == 2);
     CHECK(memcmp(data, "\x05\x67", 2) == 0);
 
@@ -82,7 +82,7 @@ TEST_CASE("QUICTransportParametersInClientHello_read", "[quic]")
       0x12, 0x34, 0x56, 0x78, // value
     };
 
-    QUICTransportParametersInClientHello params_in_ch(buf, sizeof(buf));
+    QUICTransportParametersInClientHello params_in_ch(buf, sizeof(buf), QUIC_SUPPORTED_VERSIONS[0]);
     CHECK(!params_in_ch.is_valid());
   }
 }
@@ -111,7 +111,7 @@ TEST_CASE("QUICTransportParametersInClientHello_write", "[quic]")
   params_in_ch.set(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, max_stream_data);
 
   uint16_t max_packet_size = 0x1bcd;
-  params_in_ch.set(QUICTransportParameterId::MAX_PACKET_SIZE, max_packet_size);
+  params_in_ch.set(QUICTransportParameterId::MAX_UDP_PAYLOAD_SIZE, max_packet_size);
 
   uint8_t stateless_reset_token[16] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
                                        0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77};
@@ -142,7 +142,7 @@ TEST_CASE("QUICTransportParametersInEncryptedExtensions_read", "[quic]")
       0x91, 0x22, 0x33, 0x44, // value
     };
 
-    QUICTransportParametersInEncryptedExtensions params_in_ee(buf, sizeof(buf));
+    QUICTransportParametersInEncryptedExtensions params_in_ee(buf, sizeof(buf), QUIC_SUPPORTED_VERSIONS[0]);
     CHECK(params_in_ee.is_valid());
 
     uint16_t len        = 0;
@@ -183,7 +183,7 @@ TEST_CASE("QUICTransportParametersInEncryptedExtensions_read", "[quic]")
       0x00,                   // length of value
     };
 
-    QUICTransportParametersInEncryptedExtensions params_in_ee(buf, sizeof(buf));
+    QUICTransportParametersInEncryptedExtensions params_in_ee(buf, sizeof(buf), QUIC_SUPPORTED_VERSIONS[0]);
     CHECK(params_in_ee.is_valid());
 
     uint16_t len        = 0;
@@ -215,7 +215,7 @@ TEST_CASE("QUICTransportParametersInEncryptedExtensions_read", "[quic]")
       0x12, 0x34, 0x56, 0x78, // value
     };
 
-    QUICTransportParametersInEncryptedExtensions params_in_ee(buf, sizeof(buf));
+    QUICTransportParametersInEncryptedExtensions params_in_ee(buf, sizeof(buf), QUIC_SUPPORTED_VERSIONS[0]);
     CHECK(!params_in_ee.is_valid());
   }
 }
@@ -242,10 +242,8 @@ TEST_CASE("QUICTransportParametersEncryptedExtensions_write", "[quic]")
     params_in_ee.set(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, max_stream_data);
 
     uint16_t max_packet_size = 0x1bcd;
-    params_in_ee.set(QUICTransportParameterId::MAX_PACKET_SIZE, max_packet_size);
+    params_in_ee.set(QUICTransportParameterId::MAX_UDP_PAYLOAD_SIZE, max_packet_size);
 
-    params_in_ee.add_version(0x01020304);
-    params_in_ee.add_version(0x05060708);
     params_in_ee.store(buf, &len);
     CHECK(len == 10);
     CHECK(memcmp(buf, expected, len) == 0);
@@ -273,11 +271,9 @@ TEST_CASE("QUICTransportParametersEncryptedExtensions_write", "[quic]")
     params_in_ee.set(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, max_stream_data);
 
     uint16_t max_packet_size = 0x1bcd;
-    params_in_ee.set(QUICTransportParameterId::MAX_PACKET_SIZE, max_packet_size);
+    params_in_ee.set(QUICTransportParameterId::MAX_UDP_PAYLOAD_SIZE, max_packet_size);
     params_in_ee.set(QUICTransportParameterId::DISABLE_ACTIVE_MIGRATION, nullptr, 0);
 
-    params_in_ee.add_version(0x01020304);
-    params_in_ee.add_version(0x05060708);
     params_in_ee.store(buf, &len);
     CHECK(len == 12);
     CHECK(memcmp(buf, expected, len) == 0);
diff --git a/iocore/net/quic/test/test_QUICType.cc b/iocore/net/quic/test/test_QUICType.cc
index fb45c44..b08085c 100644
--- a/iocore/net/quic/test/test_QUICType.cc
+++ b/iocore/net/quic/test/test_QUICType.cc
@@ -103,11 +103,14 @@ TEST_CASE("QUICType", "[quic]")
     IpEndpoint ep;
     ats_ip4_set(&ep, 0x04030201, 0x2211);
 
-    uint8_t cid_buf[] = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
-                         0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27};
-    QUICConnectionId cid(cid_buf, sizeof(cid_buf));
-
-    QUICRetryToken token1(ep, cid);
+    uint8_t cid1_buf[] = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+                          0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27};
+    QUICConnectionId cid1(cid1_buf, sizeof(cid1_buf));
+    uint8_t cid2_buf[] = {0xA0, 0xA1, 0x12, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8,
+                          0xA9, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7};
+    QUICConnectionId cid2(cid2_buf, sizeof(cid2_buf));
+
+    QUICRetryToken token1(ep, cid1, cid2);
     QUICRetryToken token2(token1.buf(), token1.length());
 
     CHECK(token1.is_valid(ep));
@@ -118,6 +121,7 @@ TEST_CASE("QUICType", "[quic]")
     CHECK(token1.length() == token2.length());
     CHECK(memcmp(token1.buf(), token2.buf(), token1.length()) == 0);
     CHECK(token1.original_dcid() == token2.original_dcid());
+    CHECK(token1.scid() == token2.scid());
   }
 
   SECTION("QUICResumptionToken")
diff --git a/iocore/net/quic/test/test_QUICVersionNegotiator.cc b/iocore/net/quic/test/test_QUICVersionNegotiator.cc
index a1bcab3..3539745 100644
--- a/iocore/net/quic/test/test_QUICVersionNegotiator.cc
+++ b/iocore/net/quic/test/test_QUICVersionNegotiator.cc
@@ -24,6 +24,7 @@
 #include "catch.hpp"
 
 #include "quic/QUICVersionNegotiator.h"
+#include "quic/QUICPacketFactory.h"
 #include "quic/QUICPacketProtectionKeyInfo.h"
 #include "quic/QUICPacketFactory.h"
 #include "quic/Mock.h"
@@ -41,7 +42,7 @@ TEST_CASE("QUICVersionNegotiator - Server Side", "[quic]")
   dummy_payload->alloc(iobuffer_size_to_index(dummy_payload_len, BUFFER_SIZE_INDEX_32K));
   dummy_payload->fill(dummy_payload_len);
 
-  SECTION("Normal case")
+  SECTION("Match")
   {
     // Check initial state
     CHECK(vn.status() == QUICVersionNegotiationStatus::NOT_NEGOTIATED);
@@ -60,13 +61,13 @@ TEST_CASE("QUICVersionNegotiator - Server Side", "[quic]")
     CHECK(vn.status() == QUICVersionNegotiationStatus::NEGOTIATED);
   }
 
-  SECTION("Negotiation case")
+  SECTION("Unmatch")
   {
     // Check initial state
     CHECK(vn.status() == QUICVersionNegotiationStatus::NOT_NEGOTIATED);
 
     // Negotiate version
-    packet_factory.set_version(QUIC_SUPPORTED_VERSIONS[0]);
+    packet_factory.set_version(0xff000001);
     uint8_t packet_buf[QUICPacket::MAX_INSTANCE_SIZE];
     QUICPacketUPtr initial_packet =
       packet_factory.create_initial_packet(packet_buf, {}, {}, 0, std::move(dummy_payload), dummy_payload_len, true, false, true);
@@ -76,16 +77,16 @@ TEST_CASE("QUICVersionNegotiator - Server Side", "[quic]")
     blocks->next = initial_packet->payload_block();
     QUICInitialPacketR received_initial_packet(nullptr, {}, {}, blocks, 0);
     vn.negotiate(received_initial_packet);
-    CHECK(vn.status() == QUICVersionNegotiationStatus::NEGOTIATED);
+    CHECK(vn.status() == QUICVersionNegotiationStatus::NOT_NEGOTIATED);
   }
 
-  SECTION("Downgrade case")
+  SECTION("Exercise")
   {
     // Check initial state
     CHECK(vn.status() == QUICVersionNegotiationStatus::NOT_NEGOTIATED);
 
     // Negotiate version
-    packet_factory.set_version(QUIC_EXERCISE_VERSION);
+    packet_factory.set_version(QUIC_EXERCISE_VERSION1);
     uint8_t packet_buf[QUICPacket::MAX_INSTANCE_SIZE];
     QUICPacketUPtr initial_packet =
       packet_factory.create_initial_packet(packet_buf, {}, {}, 0, std::move(dummy_payload), dummy_payload_len, true, false, true);
@@ -125,7 +126,7 @@ TEST_CASE("QUICVersionNegotiator - Client Side", "[quic]")
     CHECK(vn.status() == QUICVersionNegotiationStatus::NOT_NEGOTIATED);
 
     // Negotiate version
-    packet_factory.set_version(QUIC_EXERCISE_VERSION);
+    packet_factory.set_version(QUIC_EXERCISE_VERSION1);
     uint8_t initial_packet_buf[QUICPacket::MAX_INSTANCE_SIZE];
     QUICPacketUPtr packet = packet_factory.create_initial_packet(initial_packet_buf, {}, {}, 0, std::move(dummy_payload),
                                                                  dummy_payload_len, true, false, true);
@@ -133,8 +134,8 @@ TEST_CASE("QUICVersionNegotiator - Client Side", "[quic]")
     QUICInitialPacket &initial_packet = static_cast<QUICInitialPacket &>(*packet);
 
     // Server send VN packet based on Initial packet
-    QUICPacketUPtr vn_packet =
-      packet_factory.create_version_negotiation_packet(initial_packet.source_cid(), initial_packet.destination_cid());
+    QUICPacketUPtr vn_packet = packet_factory.create_version_negotiation_packet(
+      initial_packet.source_cid(), initial_packet.destination_cid(), QUIC_EXERCISE_VERSION1);
     REQUIRE(vn_packet != nullptr);
 
     auto blocks  = vn_packet->header_block();
diff --git a/lib/records/RecHttp.cc b/lib/records/RecHttp.cc
index cadb0d9..4a35d29 100644
--- a/lib/records/RecHttp.cc
+++ b/lib/records/RecHttp.cc
@@ -38,39 +38,45 @@ SessionProtocolNameRegistry globalSessionProtocolNameRegistry;
    These are also used for NPN setup.
 */
 
-const char *const TS_ALPN_PROTOCOL_HTTP_0_9  = IP_PROTO_TAG_HTTP_0_9.data();
-const char *const TS_ALPN_PROTOCOL_HTTP_1_0  = IP_PROTO_TAG_HTTP_1_0.data();
-const char *const TS_ALPN_PROTOCOL_HTTP_1_1  = IP_PROTO_TAG_HTTP_1_1.data();
-const char *const TS_ALPN_PROTOCOL_HTTP_2_0  = IP_PROTO_TAG_HTTP_2_0.data();
-const char *const TS_ALPN_PROTOCOL_HTTP_3    = IP_PROTO_TAG_HTTP_3.data();
-const char *const TS_ALPN_PROTOCOL_HTTP_QUIC = IP_PROTO_TAG_HTTP_QUIC.data();
+const char *const TS_ALPN_PROTOCOL_HTTP_0_9      = IP_PROTO_TAG_HTTP_0_9.data();
+const char *const TS_ALPN_PROTOCOL_HTTP_1_0      = IP_PROTO_TAG_HTTP_1_0.data();
+const char *const TS_ALPN_PROTOCOL_HTTP_1_1      = IP_PROTO_TAG_HTTP_1_1.data();
+const char *const TS_ALPN_PROTOCOL_HTTP_2_0      = IP_PROTO_TAG_HTTP_2_0.data();
+const char *const TS_ALPN_PROTOCOL_HTTP_3        = IP_PROTO_TAG_HTTP_3.data();
+const char *const TS_ALPN_PROTOCOL_HTTP_QUIC     = IP_PROTO_TAG_HTTP_QUIC.data();
+const char *const TS_ALPN_PROTOCOL_HTTP_3_D27    = IP_PROTO_TAG_HTTP_3_D27.data();
+const char *const TS_ALPN_PROTOCOL_HTTP_QUIC_D27 = IP_PROTO_TAG_HTTP_QUIC_D27.data();
 
 const char *const TS_ALPN_PROTOCOL_GROUP_HTTP  = "http";
 const char *const TS_ALPN_PROTOCOL_GROUP_HTTP2 = "http2";
 
-const char *const TS_PROTO_TAG_HTTP_1_0  = TS_ALPN_PROTOCOL_HTTP_1_0;
-const char *const TS_PROTO_TAG_HTTP_1_1  = TS_ALPN_PROTOCOL_HTTP_1_1;
-const char *const TS_PROTO_TAG_HTTP_2_0  = TS_ALPN_PROTOCOL_HTTP_2_0;
-const char *const TS_PROTO_TAG_HTTP_3    = TS_ALPN_PROTOCOL_HTTP_3;
-const char *const TS_PROTO_TAG_HTTP_QUIC = TS_ALPN_PROTOCOL_HTTP_QUIC;
-const char *const TS_PROTO_TAG_TLS_1_3   = IP_PROTO_TAG_TLS_1_3.data();
-const char *const TS_PROTO_TAG_TLS_1_2   = IP_PROTO_TAG_TLS_1_2.data();
-const char *const TS_PROTO_TAG_TLS_1_1   = IP_PROTO_TAG_TLS_1_1.data();
-const char *const TS_PROTO_TAG_TLS_1_0   = IP_PROTO_TAG_TLS_1_0.data();
-const char *const TS_PROTO_TAG_TCP       = IP_PROTO_TAG_TCP.data();
-const char *const TS_PROTO_TAG_UDP       = IP_PROTO_TAG_UDP.data();
-const char *const TS_PROTO_TAG_IPV4      = IP_PROTO_TAG_IPV4.data();
-const char *const TS_PROTO_TAG_IPV6      = IP_PROTO_TAG_IPV6.data();
+const char *const TS_PROTO_TAG_HTTP_1_0      = TS_ALPN_PROTOCOL_HTTP_1_0;
+const char *const TS_PROTO_TAG_HTTP_1_1      = TS_ALPN_PROTOCOL_HTTP_1_1;
+const char *const TS_PROTO_TAG_HTTP_2_0      = TS_ALPN_PROTOCOL_HTTP_2_0;
+const char *const TS_PROTO_TAG_HTTP_3        = TS_ALPN_PROTOCOL_HTTP_3;
+const char *const TS_PROTO_TAG_HTTP_QUIC     = TS_ALPN_PROTOCOL_HTTP_QUIC;
+const char *const TS_PROTO_TAG_HTTP_3_D27    = TS_ALPN_PROTOCOL_HTTP_3_D27;
+const char *const TS_PROTO_TAG_HTTP_QUIC_D27 = TS_ALPN_PROTOCOL_HTTP_QUIC_D27;
+const char *const TS_PROTO_TAG_TLS_1_3       = IP_PROTO_TAG_TLS_1_3.data();
+const char *const TS_PROTO_TAG_TLS_1_2       = IP_PROTO_TAG_TLS_1_2.data();
+const char *const TS_PROTO_TAG_TLS_1_1       = IP_PROTO_TAG_TLS_1_1.data();
+const char *const TS_PROTO_TAG_TLS_1_0       = IP_PROTO_TAG_TLS_1_0.data();
+const char *const TS_PROTO_TAG_TCP           = IP_PROTO_TAG_TCP.data();
+const char *const TS_PROTO_TAG_UDP           = IP_PROTO_TAG_UDP.data();
+const char *const TS_PROTO_TAG_IPV4          = IP_PROTO_TAG_IPV4.data();
+const char *const TS_PROTO_TAG_IPV6          = IP_PROTO_TAG_IPV6.data();
 
 std::unordered_set<std::string_view> TSProtoTags;
 
 // Precomputed indices for ease of use.
-int TS_ALPN_PROTOCOL_INDEX_HTTP_0_9  = SessionProtocolNameRegistry::INVALID;
-int TS_ALPN_PROTOCOL_INDEX_HTTP_1_0  = SessionProtocolNameRegistry::INVALID;
-int TS_ALPN_PROTOCOL_INDEX_HTTP_1_1  = SessionProtocolNameRegistry::INVALID;
-int TS_ALPN_PROTOCOL_INDEX_HTTP_2_0  = SessionProtocolNameRegistry::INVALID;
-int TS_ALPN_PROTOCOL_INDEX_HTTP_3    = SessionProtocolNameRegistry::INVALID;
-int TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC = SessionProtocolNameRegistry::INVALID;
+int TS_ALPN_PROTOCOL_INDEX_HTTP_0_9      = SessionProtocolNameRegistry::INVALID;
+int TS_ALPN_PROTOCOL_INDEX_HTTP_1_0      = SessionProtocolNameRegistry::INVALID;
+int TS_ALPN_PROTOCOL_INDEX_HTTP_1_1      = SessionProtocolNameRegistry::INVALID;
+int TS_ALPN_PROTOCOL_INDEX_HTTP_2_0      = SessionProtocolNameRegistry::INVALID;
+int TS_ALPN_PROTOCOL_INDEX_HTTP_3        = SessionProtocolNameRegistry::INVALID;
+int TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC     = SessionProtocolNameRegistry::INVALID;
+int TS_ALPN_PROTOCOL_INDEX_HTTP_3_D27    = SessionProtocolNameRegistry::INVALID;
+int TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC_D27 = SessionProtocolNameRegistry::INVALID;
 
 // Predefined protocol sets for ease of use.
 SessionProtocolSet HTTP_PROTOCOL_SET;
@@ -705,12 +711,15 @@ void
 ts_session_protocol_well_known_name_indices_init()
 {
   // register all the well known protocols and get the indices set.
-  TS_ALPN_PROTOCOL_INDEX_HTTP_0_9  = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_0_9});
-  TS_ALPN_PROTOCOL_INDEX_HTTP_1_0  = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_1_0});
-  TS_ALPN_PROTOCOL_INDEX_HTTP_1_1  = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_1_1});
-  TS_ALPN_PROTOCOL_INDEX_HTTP_2_0  = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_2_0});
-  TS_ALPN_PROTOCOL_INDEX_HTTP_3    = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_3});
-  TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_QUIC});
+  TS_ALPN_PROTOCOL_INDEX_HTTP_0_9   = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_0_9});
+  TS_ALPN_PROTOCOL_INDEX_HTTP_1_0   = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_1_0});
+  TS_ALPN_PROTOCOL_INDEX_HTTP_1_1   = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_1_1});
+  TS_ALPN_PROTOCOL_INDEX_HTTP_2_0   = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_2_0});
+  TS_ALPN_PROTOCOL_INDEX_HTTP_3     = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_3});
+  TS_ALPN_PROTOCOL_INDEX_HTTP_3_D27 = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_3_D27});
+  TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC  = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_QUIC});
+  TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC_D27 =
+    globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_QUIC_D27});
 
   // Now do the predefined protocol sets.
   HTTP_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_0_9);
@@ -724,6 +733,8 @@ ts_session_protocol_well_known_name_indices_init()
 
   DEFAULT_QUIC_SESSION_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_3);
   DEFAULT_QUIC_SESSION_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC);
+  DEFAULT_QUIC_SESSION_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_3_D27);
+  DEFAULT_QUIC_SESSION_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC_D27);
 
   DEFAULT_NON_TLS_SESSION_PROTOCOL_SET = HTTP_PROTOCOL_SET;
 
@@ -732,6 +743,8 @@ ts_session_protocol_well_known_name_indices_init()
   TSProtoTags.insert(TS_PROTO_TAG_HTTP_2_0);
   TSProtoTags.insert(TS_PROTO_TAG_HTTP_3);
   TSProtoTags.insert(TS_PROTO_TAG_HTTP_QUIC);
+  TSProtoTags.insert(TS_PROTO_TAG_HTTP_3_D27);
+  TSProtoTags.insert(TS_PROTO_TAG_HTTP_QUIC_D27);
   TSProtoTags.insert(TS_PROTO_TAG_TLS_1_3);
   TSProtoTags.insert(TS_PROTO_TAG_TLS_1_2);
   TSProtoTags.insert(TS_PROTO_TAG_TLS_1_1);
diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc
index cbca682..3ac18d2 100644
--- a/mgmt/RecordsConfig.cc
+++ b/mgmt/RecordsConfig.cc
@@ -1437,9 +1437,9 @@ static const RecordElement RecordsConfig[] =
   // Constatns of Congestion Control
   {RECT_CONFIG, "proxy.config.quic.congestion_control.max_datagram_size", RECD_INT, "1200", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.quic.congestion_control.initial_window_scale", RECD_INT, "10", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.quic.congestion_control.initial_window", RECD_INT, "12000", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.quic.congestion_control.minimum_window_scale", RECD_INT, "2", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.quic.congestion_control.minimum_window", RECD_INT, "2400", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
   ,
   {RECT_CONFIG, "proxy.config.quic.congestion_control.loss_reduction_factor", RECD_FLOAT, "0.5", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[\\.0-9]+$", RECA_NULL}
   ,
diff --git a/proxy/http/HttpProxyServerMain.cc b/proxy/http/HttpProxyServerMain.cc
index 98941b3..2dbac63 100644
--- a/proxy/http/HttpProxyServerMain.cc
+++ b/proxy/http/HttpProxyServerMain.cc
@@ -238,6 +238,12 @@ MakeHttpProxyAcceptor(HttpProxyAcceptor &acceptor, HttpProxyPort &port, unsigned
 
     quic->enableProtocols(port.m_session_protocol_preference);
 
+    // HTTP/0.9 over QUIC draft-27 (for interop only, will be removed)
+    quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_QUIC_D27, new Http3SessionAccept(accept_opt));
+
+    // HTTP/3 draft-27
+    quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_3_D27, new Http3SessionAccept(accept_opt));
+
     // HTTP/0.9 over QUIC (for interop only, will be removed)
     quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_QUIC, new Http3SessionAccept(accept_opt));
 
diff --git a/proxy/http3/Http3Session.cc b/proxy/http3/Http3Session.cc
index 4d9ceaf..aaea1dfd 100644
--- a/proxy/http3/Http3Session.cc
+++ b/proxy/http3/Http3Session.cc
@@ -29,7 +29,14 @@
 //
 // HQSession
 //
-HQSession ::~HQSession()
+HQSession::HQSession(NetVConnection *vc) : ProxySession(vc)
+{
+  auto app_name = static_cast<QUICNetVConnection *>(vc)->negotiated_application_name();
+  memcpy(this->_protocol_string, app_name.data(), std::min(app_name.length(), sizeof(this->_protocol_string)));
+  this->_protocol_string[app_name.length()] = '\0';
+}
+
+HQSession::~HQSession()
 {
   for (HQTransaction *t = this->_transaction_list.head; t; t = static_cast<HQTransaction *>(t->link.next)) {
     delete t;
@@ -44,6 +51,25 @@ HQSession::add_transaction(HQTransaction *trans)
   return;
 }
 
+const char *
+HQSession::get_protocol_string() const
+{
+  return this->_protocol_string;
+}
+
+int
+HQSession::populate_protocol(std::string_view *result, int size) const
+{
+  int retval = 0;
+  if (size > retval) {
+    result[retval++] = this->get_protocol_string();
+    if (size > retval) {
+      retval += super::populate_protocol(result + retval, size - retval);
+    }
+  }
+  return retval;
+}
+
 HQTransaction *
 HQSession::get_transaction(QUICStreamId id)
 {
@@ -143,25 +169,6 @@ Http3Session::~Http3Session()
   delete this->_remote_qpack;
 }
 
-const char *
-Http3Session::get_protocol_string() const
-{
-  return IP_PROTO_TAG_HTTP_3.data();
-}
-
-int
-Http3Session::populate_protocol(std::string_view *result, int size) const
-{
-  int retval = 0;
-  if (size > retval) {
-    result[retval++] = IP_PROTO_TAG_HTTP_3;
-    if (size > retval) {
-      retval += super::populate_protocol(result + retval, size - retval);
-    }
-  }
-  return retval;
-}
-
 void
 Http3Session::increment_current_active_client_connections_stat()
 {
@@ -194,25 +201,6 @@ Http09Session::~Http09Session()
   this->_vc = nullptr;
 }
 
-const char *
-Http09Session::get_protocol_string() const
-{
-  return IP_PROTO_TAG_HTTP_QUIC.data();
-}
-
-int
-Http09Session::populate_protocol(std::string_view *result, int size) const
-{
-  int retval = 0;
-  if (size > retval) {
-    result[retval++] = IP_PROTO_TAG_HTTP_QUIC;
-    if (size > retval) {
-      retval += super::populate_protocol(result + retval, size - retval);
-    }
-  }
-  return retval;
-}
-
 void
 Http09Session::increment_current_active_client_connections_stat()
 {
diff --git a/proxy/http3/Http3Session.h b/proxy/http3/Http3Session.h
index 6bf5fd2..a6aec41 100644
--- a/proxy/http3/Http3Session.h
+++ b/proxy/http3/Http3Session.h
@@ -32,7 +32,7 @@ class HQSession : public ProxySession
 public:
   using super = ProxySession; ///< Parent type
 
-  HQSession(NetVConnection *vc) : ProxySession(vc){};
+  HQSession(NetVConnection *vc);
   virtual ~HQSession();
 
   // Implement VConnection interface
@@ -43,6 +43,8 @@ public:
   void reenable(VIO *vio) override;
 
   // Implement ProxySession interface
+  const char *get_protocol_string() const override;
+  int populate_protocol(std::string_view *result, int size) const override;
   void new_connection(NetVConnection *new_vc, MIOBuffer *iobuf, IOBufferReader *reader) override;
   void start() override;
   void destroy() override;
@@ -56,6 +58,8 @@ public:
 private:
   // this should be unordered map?
   Queue<HQTransaction> _transaction_list;
+
+  char _protocol_string[16];
 };
 
 class Http3Session : public HQSession
@@ -67,8 +71,6 @@ public:
   ~Http3Session();
 
   // ProxySession interface
-  const char *get_protocol_string() const override;
-  int populate_protocol(std::string_view *result, int size) const override;
   void increment_current_active_client_connections_stat() override;
   void decrement_current_active_client_connections_stat() override;
 
@@ -92,8 +94,6 @@ public:
   ~Http09Session();
 
   // ProxySession interface
-  const char *get_protocol_string() const override;
-  int populate_protocol(std::string_view *result, int size) const override;
   void increment_current_active_client_connections_stat() override;
   void decrement_current_active_client_connections_stat() override;
 
diff --git a/proxy/http3/Http3SessionAccept.cc b/proxy/http3/Http3SessionAccept.cc
index f5d1ff6..93dfbd8 100644
--- a/proxy/http3/Http3SessionAccept.cc
+++ b/proxy/http3/Http3SessionAccept.cc
@@ -62,16 +62,16 @@ Http3SessionAccept::accept(NetVConnection *netvc, MIOBuffer *iobuf, IOBufferRead
 
   std::string_view alpn = qvc->negotiated_application_name();
 
-  if (alpn.empty() || IP_PROTO_TAG_HTTP_QUIC.compare(alpn) == 0) {
+  if (alpn.empty() || IP_PROTO_TAG_HTTP_QUIC.compare(alpn) == 0 || IP_PROTO_TAG_HTTP_QUIC_D27.compare(alpn) == 0) {
     if (alpn.empty()) {
       Debug("http3", "[%s] start HTTP/0.9 app (ALPN=null)", qvc->cids().data());
     } else {
-      Debug("http3", "[%s] start HTTP/0.9 app (ALPN=%s)", qvc->cids().data(), IP_PROTO_TAG_HTTP_QUIC.data());
+      Debug("http3", "[%s] start HTTP/0.9 app (ALPN=%.*s)", qvc->cids().data(), static_cast<int>(alpn.length()), alpn.data());
     }
 
     new Http09App(qvc, std::move(session_acl), this->options);
-  } else if (IP_PROTO_TAG_HTTP_3.compare(alpn) == 0) {
-    Debug("http3", "[%s] start HTTP/3 app (ALPN=%s)", qvc->cids().data(), IP_PROTO_TAG_HTTP_3.data());
+  } else if (IP_PROTO_TAG_HTTP_3.compare(alpn) == 0 || IP_PROTO_TAG_HTTP_3_D27.compare(alpn) == 0) {
+    Debug("http3", "[%s] start HTTP/3 app (ALPN=%.*s)", qvc->cids().data(), static_cast<int>(alpn.length()), alpn.data());
 
     Http3App *app = new Http3App(qvc, std::move(session_acl), this->options);
     app->start();
diff --git a/src/traffic_quic/quic_client.cc b/src/traffic_quic/quic_client.cc
index 8a65c28..04fb44d 100644
--- a/src/traffic_quic/quic_client.cc
+++ b/src/traffic_quic/quic_client.cc
@@ -34,8 +34,8 @@
 // https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_alpn_protos.html
 // Should be integrate with IP_PROTO_TAG_HTTP_QUIC in ts/ink_inet.h ?
 using namespace std::literals;
-static constexpr std::string_view HQ_ALPN_PROTO_LIST("\5hq-27"sv);
-static constexpr std::string_view H3_ALPN_PROTO_LIST("\5h3-27"sv);
+static constexpr std::string_view HQ_ALPN_PROTO_LIST("\5hq-29\5hq-27"sv);
+static constexpr std::string_view H3_ALPN_PROTO_LIST("\5h3-29\5h3-27"sv);
 
 QUICClient::QUICClient(const QUICClientConfig *config) : Continuation(new_ProxyMutex()), _config(config)
 {
diff --git a/src/tscore/ink_inet.cc b/src/tscore/ink_inet.cc
index 9d1dff7..6f781cc 100644
--- a/src/tscore/ink_inet.cc
+++ b/src/tscore/ink_inet.cc
@@ -49,9 +49,11 @@ const std::string_view IP_PROTO_TAG_TLS_1_3("tls/1.3"sv);
 const std::string_view IP_PROTO_TAG_HTTP_0_9("http/0.9"sv);
 const std::string_view IP_PROTO_TAG_HTTP_1_0("http/1.0"sv);
 const std::string_view IP_PROTO_TAG_HTTP_1_1("http/1.1"sv);
-const std::string_view IP_PROTO_TAG_HTTP_2_0("h2"sv);     // HTTP/2 over TLS
-const std::string_view IP_PROTO_TAG_HTTP_QUIC("hq-27"sv); // HTTP/0.9 over QUIC
-const std::string_view IP_PROTO_TAG_HTTP_3("h3-27"sv);    // HTTP/3 over QUIC
+const std::string_view IP_PROTO_TAG_HTTP_2_0("h2"sv);         // HTTP/2 over TLS
+const std::string_view IP_PROTO_TAG_HTTP_QUIC("hq-29"sv);     // HTTP/0.9 over QUIC
+const std::string_view IP_PROTO_TAG_HTTP_3("h3-29"sv);        // HTTP/3 over QUIC
+const std::string_view IP_PROTO_TAG_HTTP_QUIC_D27("hq-27"sv); // HTTP/0.9 over QUIC (draft-27)
+const std::string_view IP_PROTO_TAG_HTTP_3_D27("h3-27"sv);    // HTTP/3 over QUIC (draft-27)
 
 const std::string_view UNIX_PROTO_TAG{"unix"sv};