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/07/23 21:24:11 UTC

[trafficserver] 02/03: Squashed commit of the following: (#7000)

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

commit ce9ffb97a228da85482940538f47b60754212653
Author: Masakazu Kitajo <ma...@apache.org>
AuthorDate: Fri Jul 24 04:38:33 2020 +0900

    Squashed commit of the following: (#7000)
    
    This merges the QUIC branch up until, and including draft v27.
    
    (cherry picked from commit 18e7cad3fe246f7b5f676a5b7b061d887feb04e4)
---
 configure.ac                                       |   21 +-
 doc/admin-guide/files/records.config.en.rst        |    6 +
 iocore/net/P_Net.h                                 |    7 -
 iocore/net/P_QUICNet.h                             |    3 +
 iocore/net/P_QUICNetProcessor.h                    |    2 +
 iocore/net/P_QUICNetVConnection.h                  |   57 +-
 iocore/net/P_QUICPacketHandler.h                   |   14 +-
 iocore/net/QUICNet.cc                              |    3 +-
 iocore/net/QUICNetProcessor.cc                     |   12 +-
 iocore/net/QUICNetVConnection.cc                   |  407 ++--
 iocore/net/QUICPacketHandler.cc                    |  159 +-
 iocore/net/quic/Makefile.am                        |   16 +-
 iocore/net/quic/Mock.h                             |  110 +-
 iocore/net/quic/QUICAckFrameCreator.cc             |    9 +-
 iocore/net/quic/QUICAckFrameCreator.h              |    2 +-
 iocore/net/quic/QUICAltConnectionManager.cc        |   46 +-
 iocore/net/quic/QUICAltConnectionManager.h         |    7 +-
 iocore/net/quic/QUICConfig.cc                      |   39 +-
 iocore/net/quic/QUICConfig.h                       |   16 +-
 iocore/net/quic/QUICCongestionController.h         |   13 +
 iocore/net/quic/QUICConnection.h                   |    9 +-
 iocore/net/quic/QUICContext.cc                     |   23 +-
 iocore/net/quic/QUICContext.h                      |  170 +-
 iocore/net/quic/QUICDebugNames.cc                  |   10 +-
 iocore/net/quic/QUICEvents.h                       |    1 +
 iocore/net/quic/QUICFlowController.cc              |   46 +-
 iocore/net/quic/QUICFrame.cc                       |  180 +-
 iocore/net/quic/QUICFrame.h                        |  115 +-
 iocore/net/quic/QUICFrameDispatcher.cc             |    7 +-
 iocore/net/quic/QUICFrameDispatcher.h              |    5 +-
 iocore/net/quic/QUICHandshake.cc                   |  111 +-
 iocore/net/quic/QUICHandshake.h                    |   14 +-
 iocore/net/quic/QUICHandshakeProtocol.h            |    4 +-
 iocore/net/quic/QUICIntUtil.cc                     |    4 +-
 iocore/net/quic/QUICIntUtil.h                      |    2 +-
 iocore/net/quic/QUICKeyGenerator.cc                |    8 +-
 iocore/net/quic/QUICKeyGenerator.h                 |   12 +-
 iocore/net/quic/QUICKeyGenerator_boringssl.cc      |   21 +-
 ...rator_openssl.cc => QUICKeyGenerator_legacy.cc} |    8 +-
 iocore/net/quic/QUICKeyGenerator_openssl.cc        |   27 +-
 iocore/net/quic/QUICLossDetector.cc                |   15 +-
 iocore/net/quic/QUICLossDetector.h                 |   14 +-
 iocore/net/quic/QUICNewRenoCongestionController.cc |    8 +-
 iocore/net/quic/QUICPacket.cc                      | 2113 ++++++++++++++------
 iocore/net/quic/QUICPacket.h                       |  698 ++++---
 iocore/net/quic/QUICPacketFactory.cc               |  397 ++--
 iocore/net/quic/QUICPacketFactory.h                |   34 +-
 iocore/net/quic/QUICPacketHeaderProtector.cc       |   43 +-
 .../quic/QUICPacketHeaderProtector_boringssl.cc    |   31 +-
 ...nssl.cc => QUICPacketHeaderProtector_legacy.cc} |    0
 .../net/quic/QUICPacketHeaderProtector_openssl.cc  |    9 +-
 iocore/net/quic/QUICPacketPayloadProtector.cc      |  124 +-
 .../quic/QUICPacketPayloadProtector_boringssl.cc   |  131 +-
 ...ssl.cc => QUICPacketPayloadProtector_legacy.cc} |    8 +-
 .../net/quic/QUICPacketPayloadProtector_openssl.cc |   15 +
 iocore/net/quic/QUICPacketReceiveQueue.cc          |   20 +-
 iocore/net/quic/QUICPacketReceiveQueue.h           |    3 +-
 iocore/net/quic/QUICPathManager.cc                 |   10 +-
 iocore/net/quic/QUICPathManager.h                  |   20 +-
 iocore/net/quic/QUICPinger.cc                      |   36 +-
 iocore/net/quic/QUICPinger.h                       |    8 +-
 iocore/net/quic/QUICResetTokenTable.cc             |   53 +
 .../net/quic/QUICResetTokenTable.h                 |   27 +-
 iocore/net/quic/QUICRetryIntegrityTag.cc           |   79 +
 .../{QUICIntUtil.h => QUICRetryIntegrityTag.h}     |   24 +-
 iocore/net/quic/QUICStream.cc                      |    2 +-
 iocore/net/quic/QUICStreamManager.cc               |   42 +-
 iocore/net/quic/QUICStreamManager.h                |    8 +-
 iocore/net/quic/QUICTLS.cc                         |  323 ++-
 iocore/net/quic/QUICTLS.h                          |   32 +-
 iocore/net/quic/QUICTLS_boringssl.cc               |  388 +++-
 iocore/net/quic/QUICTLS_legacy.cc                  |  445 +++++
 iocore/net/quic/QUICTLS_openssl.cc                 |  587 +-----
 iocore/net/quic/QUICTransportParameters.cc         |   85 +-
 iocore/net/quic/QUICTransportParameters.h          |    5 +-
 iocore/net/quic/QUICTypes.cc                       |   80 +-
 iocore/net/quic/QUICTypes.h                        |  129 +-
 iocore/net/quic/QUICVersionNegotiator.cc           |   15 +-
 iocore/net/quic/qlog/QLog.cc                       |  103 +
 iocore/net/quic/qlog/QLog.h                        |  145 ++
 iocore/net/quic/qlog/QLogEvent.cc                  |  317 +++
 iocore/net/quic/qlog/QLogEvent.h                   | 1013 ++++++++++
 iocore/net/quic/qlog/QLogFrame.cc                  |  282 +++
 iocore/net/quic/qlog/QLogFrame.h                   |  309 +++
 iocore/net/quic/qlog/QLogListener.h                |  119 ++
 iocore/net/quic/qlog/QLogUtils.h                   |   80 +
 iocore/net/quic/test/main.cc                       |    2 +
 iocore/net/quic/test/test_QUICAckFrameCreator.cc   |   23 +
 iocore/net/quic/test/test_QUICFrame.cc             |   39 +-
 iocore/net/quic/test/test_QUICFrameDispatcher.cc   |    8 +-
 .../net/quic/test/test_QUICFrameRetransmitter.cc   |    4 +-
 iocore/net/quic/test/test_QUICHandshakeProtocol.cc |  266 ++-
 iocore/net/quic/test/test_QUICLossDetector.cc      |  141 +-
 iocore/net/quic/test/test_QUICPacket.cc            |  751 +++++--
 iocore/net/quic/test/test_QUICPacketFactory.cc     |   69 +-
 .../quic/test/test_QUICPacketHeaderProtector.cc    |   77 +-
 iocore/net/quic/test/test_QUICPathValidator.cc     |    2 +-
 iocore/net/quic/test/test_QUICPinger.cc            |   58 +-
 iocore/net/quic/test/test_QUICStream.cc            |   13 +-
 iocore/net/quic/test/test_QUICStreamManager.cc     |  192 +-
 iocore/net/quic/test/test_QUICStreamState.cc       |    6 +-
 .../net/quic/test/test_QUICTransportParameters.cc  |  127 +-
 iocore/net/quic/test/test_QUICVersionNegotiator.cc |   59 +-
 lib/records/RecHttp.cc                             |    2 +
 mgmt/RecordsConfig.cc                              |    8 +
 proxy/http/HttpProxyServerMain.cc                  |    7 +-
 proxy/http/Makefile.am                             |    3 +-
 proxy/http3/Http09App.cc                           |    1 +
 proxy/http3/Http3App.cc                            |    1 +
 proxy/http3/Http3Frame.cc                          |    4 +-
 proxy/http3/Http3HeaderFramer.cc                   |    2 +-
 proxy/http3/Http3Session.cc                        |    1 +
 proxy/http3/Http3SessionAccept.cc                  |    1 +
 proxy/http3/Http3StreamDataVIOAdaptor.cc           |    7 +
 proxy/http3/Http3StreamDataVIOAdaptor.h            |    6 +-
 proxy/http3/Http3Transaction.cc                    |   23 +-
 proxy/http3/Http3Transaction.h                     |    9 +-
 proxy/http3/Makefile.am                            |    3 +-
 proxy/http3/test/test_QPACK.cc                     |    2 +-
 src/traffic_quic/Makefile.inc                      |    2 +-
 src/traffic_quic/quic_client.cc                    |   41 +-
 src/traffic_quic/quic_client.h                     |    5 +-
 src/traffic_quic/traffic_quic.cc                   |    2 +
 src/traffic_server/traffic_server.cc               |    1 +
 src/tscore/ink_inet.cc                             |    4 +-
 125 files changed, 8856 insertions(+), 3271 deletions(-)

diff --git a/configure.ac b/configure.ac
index 92c155a..b07f9a7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1261,18 +1261,31 @@ enable_quic=no
 AC_MSG_CHECKING([whether APIs for QUIC are available])
 AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <openssl/ssl.h>]],
                                    [[
-                                     #ifdef OPENSSL_IS_BORINGSSL
                                      SSL_QUIC_METHOD var;
+                                   ]])
+                  ],
+                  [
+                    AC_MSG_RESULT([yes])
+                    enable_quic=yes
+                    _quic_saved_LIBS=$LIBS
+                    TS_ADDTO(LIBS, [$OPENSSL_LIBS])
+                    AC_CHECK_FUNCS(SSL_set_quic_early_data_enabled)
+                    LIBS=$_quic_saved_LIBS
+                  ],
+                  [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <openssl/ssl.h>]],
+                                   [[
+                                     #ifdef SSL_MODE_QUIC_HACK
                                      #else
-                                     #ifndef SSL_MODE_QUIC_HACK
                                      # error no hack for quic
                                      #endif
-                                     #endif
                                    ]])
                   ],
-                  [AC_MSG_RESULT([yes]); enable_quic=yes],
+                  [AC_MSG_RESULT([yes]); enable_quic=yes; enable_quic_old_api=yes],
                   [AC_MSG_RESULT([no])])
+                  ])
+
 AM_CONDITIONAL([ENABLE_QUIC], [test "x$enable_quic" = "xyes"])
+AM_CONDITIONAL([ENABLE_QUIC_OLD_API], [test "x$enable_quic_old_api" = "xyes"])
 TS_ARG_ENABLE_VAR([use], [quic])
 AC_SUBST(use_quic)
 
diff --git a/doc/admin-guide/files/records.config.en.rst b/doc/admin-guide/files/records.config.en.rst
index 6e3d9fb..516778f 100644
--- a/doc/admin-guide/files/records.config.en.rst
+++ b/doc/admin-guide/files/records.config.en.rst
@@ -3869,6 +3869,12 @@ QUIC Configuration
 All configurations for QUIC are still experimental and may be changed or
 removed in the future without prior notice.
 
+.. ts:cv:: CONFIG proxy.config.quic.qlog_dir STRING NULL
+   :reloadable:
+
+    The qlog is enabled when this configuration is not NULL. And will dump
+    the qlog to this dir.
+
 .. ts:cv:: CONFIG proxy.config.quic.instance_id INT 0
    :reloadable:
 
diff --git a/iocore/net/P_Net.h b/iocore/net/P_Net.h
index 46a05c3..b3027ce 100644
--- a/iocore/net/P_Net.h
+++ b/iocore/net/P_Net.h
@@ -111,13 +111,6 @@ extern RecRawStatBlock *net_rsb;
 #include "P_SSLNetAccept.h"
 #include "P_SSLCertLookup.h"
 
-#if TS_USE_QUIC == 1
-#include "P_QUICNetVConnection.h"
-#include "P_QUICNetProcessor.h"
-#include "P_QUICPacketHandler.h"
-#include "P_QUICNet.h"
-#endif
-
 static constexpr ts::ModuleVersion NET_SYSTEM_MODULE_INTERNAL_VERSION(NET_SYSTEM_MODULE_PUBLIC_VERSION, ts::ModuleVersion::PRIVATE);
 
 // For very verbose iocore debugging.
diff --git a/iocore/net/P_QUICNet.h b/iocore/net/P_QUICNet.h
index 802bf6e..a8a59a2 100644
--- a/iocore/net/P_QUICNet.h
+++ b/iocore/net/P_QUICNet.h
@@ -29,6 +29,9 @@
 #include "tscore/ink_platform.h"
 
 #include "P_Net.h"
+#include "quic/QUICTypes.h"
+#include "P_QUICNetProcessor.h"
+#include "P_QUICNetVConnection.h"
 
 class NetHandler;
 typedef int (NetHandler::*NetContHandler)(int, void *);
diff --git a/iocore/net/P_QUICNetProcessor.h b/iocore/net/P_QUICNetProcessor.h
index bb3e857..68e7228 100644
--- a/iocore/net/P_QUICNetProcessor.h
+++ b/iocore/net/P_QUICNetProcessor.h
@@ -42,6 +42,7 @@
 #include "quic/QUICConnectionTable.h"
 
 class UnixNetVConnection;
+class QUICResetTokenTable;
 struct NetAccept;
 
 //////////////////////////////////////////////////////////////////
@@ -73,6 +74,7 @@ private:
   QUICNetProcessor &operator=(const QUICNetProcessor &);
 
   QUICConnectionTable *_ctable = nullptr;
+  QUICResetTokenTable *_rtable = nullptr;
 };
 
 extern QUICNetProcessor quic_NetProcessor;
diff --git a/iocore/net/P_QUICNetVConnection.h b/iocore/net/P_QUICNetVConnection.h
index d39824e..a7aee81 100644
--- a/iocore/net/P_QUICNetVConnection.h
+++ b/iocore/net/P_QUICNetVConnection.h
@@ -37,18 +37,20 @@
 #include "P_UnixNetVConnection.h"
 #include "P_UnixNet.h"
 #include "P_UDPNet.h"
+#include "P_ALPNSupport.h"
+#include "TLSSessionResumptionSupport.h"
 #include "tscore/ink_apidefs.h"
 #include "tscore/List.h"
 
 #include "quic/QUICConfig.h"
 #include "quic/QUICConnection.h"
 #include "quic/QUICConnectionTable.h"
+#include "quic/QUICResetTokenTable.h"
 #include "quic/QUICVersionNegotiator.h"
 #include "quic/QUICPacket.h"
 #include "quic/QUICPacketFactory.h"
 #include "quic/QUICFrame.h"
 #include "quic/QUICFrameDispatcher.h"
-#include "quic/QUICHandshake.h"
 #include "quic/QUICApplication.h"
 #include "quic/QUICStream.h"
 #include "quic/QUICHandshakeProtocol.h"
@@ -62,10 +64,12 @@
 #include "quic/QUICPathManager.h"
 #include "quic/QUICApplicationMap.h"
 #include "quic/QUICPacketReceiveQueue.h"
+#include "quic/QUICPacketHeaderProtector.h"
 #include "quic/QUICAddrVerifyState.h"
 #include "quic/QUICPacketProtectionKeyInfo.h"
 #include "quic/QUICContext.h"
 #include "quic/QUICTokenCreator.h"
+#include "quic/qlog/QLogListener.h"
 
 // Size of connection ids for debug log : e.g. aaaaaaaa-bbbbbbbb\0
 static constexpr size_t MAX_CIDS_SIZE = 8 + 1 + 8 + 1;
@@ -80,6 +84,7 @@ static constexpr size_t MAX_CIDS_SIZE = 8 + 1 + 8 + 1;
 
 class QUICPacketHandler;
 class QUICLossDetector;
+class QUICHandshake;
 
 class SSLNextProtocolSet;
 
@@ -131,16 +136,21 @@ class SSLNextProtocolSet;
  *    WRITE:
  *      Do nothing
  **/
-class QUICNetVConnection : public UnixNetVConnection, public QUICConnection, public RefCountObj, public ALPNSupport
+class QUICNetVConnection : public UnixNetVConnection,
+                           public QUICConnection,
+                           public RefCountObj,
+                           public ALPNSupport,
+                           public TLSSessionResumptionSupport
 {
   using super = UnixNetVConnection; ///< Parent type.
 
 public:
   QUICNetVConnection();
   ~QUICNetVConnection();
-  void init(QUICConnectionId peer_cid, QUICConnectionId original_cid, UDPConnection *, QUICPacketHandler *);
+  void init(QUICConnectionId peer_cid, QUICConnectionId original_cid, UDPConnection *, QUICPacketHandler *,
+            QUICResetTokenTable *rtable);
   void init(QUICConnectionId peer_cid, QUICConnectionId original_cid, QUICConnectionId first_cid, UDPConnection *,
-            QUICPacketHandler *, QUICConnectionTable *ctable);
+            QUICPacketHandler *, QUICResetTokenTable *rtable, QUICConnectionTable *ctable);
 
   // accept new conn_id
   int acceptEvent(int event, Event *e);
@@ -180,7 +190,8 @@ public:
 
   // QUICConnection
   QUICStreamManager *stream_manager() override;
-  void close(QUICConnectionErrorUPtr error) override;
+  void close_quic_connection(QUICConnectionErrorUPtr error) override;
+  void reset_quic_connection() override;
   void handle_received_packet(UDPPacket *packet) override;
   void ping() override;
 
@@ -207,6 +218,9 @@ public:
   LINK(QUICNetVConnection, closed_link);
   SLINK(QUICNetVConnection, closed_alink);
 
+protected:
+  const IpEndpoint &_getLocalEndpoint() override;
+
 private:
   std::random_device _rnd;
 
@@ -247,6 +261,7 @@ private:
   QUICCongestionController *_congestion_controller  = nullptr;
   QUICRemoteFlowController *_remote_flow_controller = nullptr;
   QUICLocalFlowController *_local_flow_controller   = nullptr;
+  QUICResetTokenTable *_rtable                      = nullptr;
   QUICConnectionTable *_ctable                      = nullptr;
   QUICAltConnectionManager *_alt_con_manager        = nullptr;
   QUICPathValidator *_path_validator                = nullptr;
@@ -290,22 +305,23 @@ private:
 
   Ptr<IOBufferBlock> _store_frame(Ptr<IOBufferBlock> parent_block, size_t &size_added, uint64_t &max_frame_size, QUICFrame &frame,
                                   std::vector<QUICFrameInfo> &frames);
-  QUICPacketUPtr _packetize_frames(QUICEncryptionLevel level, uint64_t max_packet_size, std::vector<QUICFrameInfo> &frames);
+  QUICPacketUPtr _packetize_frames(uint8_t *packet_buf, QUICEncryptionLevel level, uint64_t max_packet_size,
+                                   std::vector<QUICFrameInfo> &frames);
   void _packetize_closing_frame();
-  QUICPacketUPtr _build_packet(QUICEncryptionLevel level, Ptr<IOBufferBlock> parent_block, bool retransmittable, bool probing,
-                               bool crypto);
+  QUICPacketUPtr _build_packet(uint8_t *packet_buf, QUICEncryptionLevel level, Ptr<IOBufferBlock> parent_block,
+                               bool retransmittable, bool probing, bool crypto);
 
-  QUICConnectionErrorUPtr _recv_and_ack(const QUICPacket &packet, bool *has_non_probing_frame = nullptr);
+  QUICConnectionErrorUPtr _recv_and_ack(const QUICPacketR &packet, bool *has_non_probing_frame = nullptr);
 
   QUICConnectionErrorUPtr _state_handshake_process_packet(const QUICPacket &packet);
-  QUICConnectionErrorUPtr _state_handshake_process_version_negotiation_packet(const QUICPacket &packet);
-  QUICConnectionErrorUPtr _state_handshake_process_initial_packet(const QUICPacket &packet);
-  QUICConnectionErrorUPtr _state_handshake_process_retry_packet(const QUICPacket &packet);
-  QUICConnectionErrorUPtr _state_handshake_process_handshake_packet(const QUICPacket &packet);
-  QUICConnectionErrorUPtr _state_handshake_process_zero_rtt_protected_packet(const QUICPacket &packet);
+  QUICConnectionErrorUPtr _state_handshake_process_version_negotiation_packet(const QUICVersionNegotiationPacketR &packet);
+  QUICConnectionErrorUPtr _state_handshake_process_initial_packet(const QUICInitialPacketR &packet);
+  QUICConnectionErrorUPtr _state_handshake_process_retry_packet(const QUICRetryPacketR &packet);
+  QUICConnectionErrorUPtr _state_handshake_process_handshake_packet(const QUICHandshakePacketR &packet);
+  QUICConnectionErrorUPtr _state_handshake_process_zero_rtt_protected_packet(const QUICZeroRttPacketR &packet);
   QUICConnectionErrorUPtr _state_connection_established_receive_packet();
-  QUICConnectionErrorUPtr _state_connection_established_process_protected_packet(const QUICPacket &packet);
-  QUICConnectionErrorUPtr _state_connection_established_migrate_connection(const QUICPacket &packet);
+  QUICConnectionErrorUPtr _state_connection_established_process_protected_packet(const QUICShortHeaderPacketR &packet);
+  QUICConnectionErrorUPtr _state_connection_established_migrate_connection(const QUICPacketR &packet);
   QUICConnectionErrorUPtr _state_connection_established_initiate_connection_migration();
   QUICConnectionErrorUPtr _state_closing_receive_packet();
   QUICConnectionErrorUPtr _state_draining_receive_packet();
@@ -318,7 +334,7 @@ private:
   void _init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
                                  const std::shared_ptr<const QUICTransportParameters> &remote_tp);
   void _handle_error(QUICConnectionErrorUPtr error);
-  QUICPacketUPtr _dequeue_recv_packet(QUICPacketCreationResult &result);
+  QUICPacketUPtr _dequeue_recv_packet(uint8_t *packet_buf, QUICPacketCreationResult &result);
   void _validate_new_path(const QUICPath &path);
 
   int _complete_handshake_if_possible();
@@ -344,6 +360,7 @@ private:
   QUICHandshakeProtocol *_setup_handshake_protocol(shared_SSL_CTX ctx);
 
   QUICPacketUPtr _the_final_packet = QUICPacketFactory::create_null_packet();
+  uint8_t _final_packet_buf[QUICPacket::MAX_INSTANCE_SIZE];
   QUICStatelessResetToken _reset_token;
 
   ats_unique_buf _av_token = {nullptr};
@@ -353,9 +370,11 @@ private:
   uint32_t _seq_num            = 0;
 
   // TODO: Source addresses verification through an address validation token
-  QUICAddrVerifyState _verfied_state;
+  QUICAddrVerifyState _verified_state;
+
+  std::unique_ptr<QUICContext> _context;
 
-  std::unique_ptr<QUICContextImpl> _context;
+  std::shared_ptr<QLog::QLogListener> _qlog;
 };
 
 typedef int (QUICNetVConnection::*QUICNetVConnHandler)(int, void *);
diff --git a/iocore/net/P_QUICPacketHandler.h b/iocore/net/P_QUICPacketHandler.h
index 27a5235..4df92ec 100644
--- a/iocore/net/P_QUICPacketHandler.h
+++ b/iocore/net/P_QUICPacketHandler.h
@@ -28,6 +28,7 @@
 #include "P_NetAccept.h"
 #include "quic/QUICTypes.h"
 #include "quic/QUICConnectionTable.h"
+#include "quic/QUICResetTokenTable.h"
 
 class QUICClosedConCollector;
 class QUICNetVConnection;
@@ -37,7 +38,7 @@ class QUICPacketHeaderProtector;
 class QUICPacketHandler
 {
 public:
-  QUICPacketHandler();
+  QUICPacketHandler(QUICResetTokenTable &rtable);
   ~QUICPacketHandler();
 
   void send_packet(const QUICPacket &packet, QUICNetVConnection *vc, const QUICPacketHeaderProtector &pn_protector);
@@ -49,6 +50,7 @@ protected:
   void _send_packet(const QUICPacket &packet, UDPConnection *udp_con, IpEndpoint &addr, uint32_t pmtu,
                     const QUICPacketHeaderProtector *ph_protector, int dcil);
   void _send_packet(UDPConnection *udp_con, IpEndpoint &addr, Ptr<IOBufferBlock> udp_payload);
+  QUICConnection *_check_stateless_reset(const uint8_t *buf, size_t buf_len);
 
   // FIXME Remove this
   // QUICPacketHandler could be a continuation, but NetAccept is a contination too.
@@ -58,6 +60,8 @@ protected:
   QUICClosedConCollector *_closed_con_collector = nullptr;
 
   virtual void _recv_packet(int event, UDPPacket *udpPacket) = 0;
+
+  QUICResetTokenTable &_rtable;
 };
 
 /*
@@ -67,7 +71,7 @@ protected:
 class QUICPacketHandlerIn : public NetAccept, public QUICPacketHandler
 {
 public:
-  QUICPacketHandlerIn(const NetProcessor::AcceptOptions &opt, QUICConnectionTable &ctable);
+  QUICPacketHandlerIn(const NetProcessor::AcceptOptions &opt, QUICConnectionTable &ctable, QUICResetTokenTable &rtable);
   ~QUICPacketHandlerIn();
 
   // NetAccept
@@ -84,6 +88,10 @@ 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);
+  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,
+                                 IpEndpoint from);
 
   QUICConnectionTable &_ctable;
 };
@@ -95,7 +103,7 @@ private:
 class QUICPacketHandlerOut : public Continuation, public QUICPacketHandler
 {
 public:
-  QUICPacketHandlerOut();
+  QUICPacketHandlerOut(QUICResetTokenTable &rtable);
   ~QUICPacketHandlerOut(){};
 
   void init(QUICNetVConnection *vc);
diff --git a/iocore/net/QUICNet.cc b/iocore/net/QUICNet.cc
index d3df166..34e21ae 100644
--- a/iocore/net/QUICNet.cc
+++ b/iocore/net/QUICNet.cc
@@ -22,6 +22,7 @@
  */
 
 #include "P_Net.h"
+#include "P_QUICNet.h"
 #include "quic/QUICEvents.h"
 
 ClassAllocator<QUICPollEvent> quicPollEventAllocator("quicPollEvent");
@@ -68,7 +69,7 @@ QUICPollCont::_process_long_header_packet(QUICPollEvent *e, NetHandler *nh)
   uint8_t *buf           = (uint8_t *)p->getIOBlockChain()->buf();
 
   QUICPacketType ptype;
-  QUICPacketLongHeader::type(ptype, buf, 1);
+  QUICLongHeaderPacketR::type(ptype, buf, 1);
   if (ptype == QUICPacketType::INITIAL && !vc->read.triggered) {
     SCOPED_MUTEX_LOCK(lock, vc->mutex, this_ethread());
     vc->read.triggered = 1;
diff --git a/iocore/net/QUICNetProcessor.cc b/iocore/net/QUICNetProcessor.cc
index a0c706a..4dfebae 100644
--- a/iocore/net/QUICNetProcessor.cc
+++ b/iocore/net/QUICNetProcessor.cc
@@ -25,9 +25,13 @@
 #include "P_Net.h"
 #include "records/I_RecHttp.h"
 
+#include "P_QUICNetProcessor.h"
+#include "P_QUICNet.h"
+#include "P_QUICPacketHandler.h"
 #include "QUICGlobals.h"
 #include "QUICConfig.h"
 #include "QUICMultiCertConfigLoader.h"
+#include "QUICResetTokenTable.h"
 
 //
 // Global Data
@@ -76,8 +80,9 @@ QUICNetProcessor::createNetAccept(const NetProcessor::AcceptOptions &opt)
   if (this->_ctable == nullptr) {
     QUICConfig::scoped_config params;
     this->_ctable = new QUICConnectionTable(params->connection_table_size());
+    this->_rtable = new QUICResetTokenTable();
   }
-  return (NetAccept *)new QUICPacketHandlerIn(opt, *this->_ctable);
+  return (NetAccept *)new QUICPacketHandlerIn(opt, *this->_ctable, *this->_rtable);
 }
 
 NetVConnection *
@@ -125,7 +130,8 @@ QUICNetProcessor::connect_re(Continuation *cont, sockaddr const *remote_addr, Ne
   UnixUDPConnection *con = new UnixUDPConnection(fd);
   Debug("quic_ps", "con=%p fd=%d", con, fd);
 
-  QUICPacketHandlerOut *packet_handler = new QUICPacketHandlerOut();
+  this->_rtable                        = new QUICResetTokenTable();
+  QUICPacketHandlerOut *packet_handler = new QUICPacketHandlerOut(*this->_rtable);
   if (opt->local_ip.isValid()) {
     con->setBinding(opt->local_ip, opt->local_port);
   }
@@ -144,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);
+  vc->init(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 004867d..8338753 100644
--- a/iocore/net/QUICNetVConnection.cc
+++ b/iocore/net/QUICNetVConnection.cc
@@ -27,6 +27,8 @@
 #include "records/I_RecHttp.h"
 #include "tscore/Diags.h"
 
+#include "P_QUICNetVConnection.h"
+#include "P_QUICPacketHandler.h"
 #include "P_Net.h"
 #include "InkAPIInternal.h" // Added to include the quic_hook definitions
 #include "Log.h"
@@ -39,12 +41,16 @@
 #include "QUICGlobals.h"
 #include "QUICDebugNames.h"
 #include "QUICEvents.h"
+#include "QUICHandshake.h"
 #include "QUICConfig.h"
 #include "QUICIntUtil.h"
 
 using namespace std::literals;
 static constexpr std::string_view QUIC_DEBUG_TAG = "quic_net"sv;
 
+static constexpr uint16_t QUANTUM_TEST_ID         = 3127;
+static constexpr uint8_t QUANTUM_TEST_VALUE[1200] = {'Q'};
+
 #define QUICConDebug(fmt, ...) Debug(QUIC_DEBUG_TAG.data(), "[%s] " fmt, this->cids().data(), ##__VA_ARGS__)
 
 #define QUICConVDebug(fmt, ...) Debug("v_quic_net", "[%s] " fmt, this->cids().data(), ##__VA_ARGS__)
@@ -188,9 +194,33 @@ public:
     }
   }
 
+  bool
+  disable_active_migration() const override
+  {
+    if (this->_ctx == NET_VCONNECTION_IN) {
+      return this->_params->disable_active_migration();
+    } else {
+      return false;
+    }
+  }
+
+  std::unordered_map<uint16_t, std::pair<const uint8_t *, uint16_t>>
+  additional_tp() const override
+  {
+    return this->_additional_tp;
+  }
+
+  void
+  add_tp(uint16_t id, const uint8_t *value, uint16_t length)
+  {
+    this->_additional_tp.emplace(std::piecewise_construct, std::forward_as_tuple(id), std::forward_as_tuple(value, length));
+  }
+
 private:
   const QUICConfigParams *_params;
   NetVConnectionContext_t _ctx;
+
+  std::unordered_map<uint16_t, std::pair<const uint8_t *, uint16_t>> _additional_tp;
 };
 
 QUICNetVConnection::QUICNetVConnection() : _packet_factory(this->_pp_key_info), _ph_protector(this->_pp_key_info) {}
@@ -207,31 +237,28 @@ QUICNetVConnection::~QUICNetVConnection()
 // Initialize QUICNetVC for out going connection (NET_VCONNECTION_OUT)
 void
 QUICNetVConnection::init(QUICConnectionId peer_cid, QUICConnectionId original_cid, UDPConnection *udp_con,
-                         QUICPacketHandler *packet_handler)
+                         QUICPacketHandler *packet_handler, QUICResetTokenTable *rtable)
 {
   SET_HANDLER((NetVConnHandler)&QUICNetVConnection::startEvent);
   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->_update_cids();
 
   if (is_debug_tag_set(QUIC_DEBUG_TAG.data())) {
-    char dcid_hex_str[QUICConnectionId::MAX_HEX_STR_LENGTH];
-    char scid_hex_str[QUICConnectionId::MAX_HEX_STR_LENGTH];
-    this->_peer_quic_connection_id.hex(dcid_hex_str, QUICConnectionId::MAX_HEX_STR_LENGTH);
-    this->_quic_connection_id.hex(scid_hex_str, QUICConnectionId::MAX_HEX_STR_LENGTH);
-
-    QUICConDebug("dcid=%s scid=%s", dcid_hex_str, scid_hex_str);
+    QUICConDebug("dcid=%s scid=%s", this->_peer_quic_connection_id.hex().c_str(), this->_quic_connection_id.hex().c_str());
   }
 }
 
 // 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, QUICConnectionTable *ctable)
+                         UDPConnection *udp_con, QUICPacketHandler *packet_handler, QUICResetTokenTable *rtable,
+                         QUICConnectionTable *ctable)
 {
   SET_HANDLER((NetVConnHandler)&QUICNetVConnection::acceptEvent);
   this->_udp_con                     = udp_con;
@@ -246,16 +273,12 @@ QUICNetVConnection::init(QUICConnectionId peer_cid, QUICConnectionId original_ci
     this->_ctable->insert(this->_quic_connection_id, this);
     this->_ctable->insert(this->_original_quic_connection_id, this);
   }
+  this->_rtable = rtable;
 
   this->_update_cids();
 
   if (is_debug_tag_set(QUIC_DEBUG_TAG.data())) {
-    char dcid_hex_str[QUICConnectionId::MAX_HEX_STR_LENGTH];
-    char scid_hex_str[QUICConnectionId::MAX_HEX_STR_LENGTH];
-    this->_peer_quic_connection_id.hex(dcid_hex_str, QUICConnectionId::MAX_HEX_STR_LENGTH);
-    this->_quic_connection_id.hex(scid_hex_str, QUICConnectionId::MAX_HEX_STR_LENGTH);
-
-    QUICConDebug("dcid=%s scid=%s", dcid_hex_str, scid_hex_str);
+    QUICConDebug("dcid=%s scid=%s", this->_peer_quic_connection_id.hex().c_str(), this->_quic_connection_id.hex().c_str());
   }
 }
 
@@ -356,7 +379,17 @@ void
 QUICNetVConnection::start()
 {
   ink_release_assert(this->thread != nullptr);
-  this->_context = std::make_unique<QUICContextImpl>(&this->_rtt_measure, this, &this->_pp_key_info);
+  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
@@ -374,6 +407,9 @@ QUICNetVConnection::start()
   } else {
     trusted_path = {this->local_addr, this->remote_addr};
     QUICTPConfigQCP tp_config(this->_quic_config, NET_VCONNECTION_OUT);
+    if (this->_quic_config->quantum_readiness_test_enabled_out()) {
+      tp_config.add_tp(QUANTUM_TEST_ID, QUANTUM_TEST_VALUE, sizeof(QUANTUM_TEST_VALUE));
+    }
     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());
@@ -383,6 +419,7 @@ QUICNetVConnection::start()
     this->_ack_frame_manager.set_max_ack_delay(this->_quic_config->max_ack_delay_out());
     this->_schedule_ack_manager_periodic(this->_quic_config->max_ack_delay_out());
   }
+  this->_path_manager->set_trusted_path(trusted_path);
 
   this->_application_map = new QUICApplicationMap();
 
@@ -399,19 +436,8 @@ QUICNetVConnection::start()
 
   this->_remote_flow_controller = new QUICRemoteConnectionFlowController(UINT64_MAX);
   this->_local_flow_controller  = new QUICLocalConnectionFlowController(&this->_rtt_measure, UINT64_MAX);
-  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->_stream_manager         = new QUICStreamManager(this, &this->_rtt_measure, this->_application_map);
-  this->_path_manager           = new QUICPathManager(*this, *this->_path_validator);
-  this->_path_manager->set_trusted_path(trusted_path);
-  this->_token_creator = new QUICTokenCreator(this->_context.get());
+  this->_stream_manager         = new QUICStreamManager(this->_context.get(), this->_application_map);
+  this->_token_creator          = new QUICTokenCreator(this->_context.get());
 
   static constexpr int QUIC_STREAM_MANAGER_WEIGHT = QUICFrameGeneratorWeight::AFTER_DATA - 1;
   static constexpr int QUIC_PINGER_WEIGHT         = QUICFrameGeneratorWeight::LATE + 1;
@@ -436,6 +462,14 @@ QUICNetVConnection::start()
   this->_frame_dispatcher->add_handler(this->_stream_manager);
   this->_frame_dispatcher->add_handler(this->_path_validator);
   this->_frame_dispatcher->add_handler(this->_handshake_handler);
+
+  // regist qlog
+  if (this->_context->config()->qlog_dir() != nullptr) {
+    this->_qlog = std::make_unique<QLog::QLogListener>(*this->_context, this->_original_quic_connection_id.hex());
+    this->_qlog->last_trace().set_vantage_point(
+      {"ats", QLog::Trace::VantagePointType::server, QLog::Trace::VantagePointType::server});
+    this->_context->regist_callback(this->_qlog);
+  }
 }
 
 void
@@ -462,6 +496,7 @@ QUICNetVConnection::free(EThread *t)
 
     super::clear();
   */
+  this->_context->trigger(QUICContext::CallbackEvent::CONNECTION_CLOSE);
   ALPNSupport::clear();
   this->_packet_handler->close_connection(this);
 }
@@ -641,11 +676,11 @@ QUICNetVConnection::handle_received_packet(UDPPacket *packet)
 void
 QUICNetVConnection::ping()
 {
-  this->_pinger->request();
+  this->_pinger->request(QUICEncryptionLevel::ONE_RTT);
 }
 
 void
-QUICNetVConnection::close(QUICConnectionErrorUPtr error)
+QUICNetVConnection::close_quic_connection(QUICConnectionErrorUPtr error)
 {
   if (this->handler == reinterpret_cast<ContinuationHandler>(&QUICNetVConnection::state_connection_closed) ||
       this->handler == reinterpret_cast<ContinuationHandler>(&QUICNetVConnection::state_connection_closing)) {
@@ -655,6 +690,24 @@ QUICNetVConnection::close(QUICConnectionErrorUPtr error)
   }
 }
 
+void
+QUICNetVConnection::reset_quic_connection()
+{
+  this->_switch_to_close_state();
+
+  QUICStatelessResetToken token(this->connection_id(), this->_quic_config->instance_id());
+  auto packet = QUICPacketFactory::create_stateless_reset_packet(token, 128);
+  if (packet) {
+    Ptr<IOBufferBlock> udp_payload(new_IOBufferBlock());
+    udp_payload->alloc(iobuffer_size_to_index(128, BUFFER_SIZE_INDEX_32K));
+    uint8_t *buf = reinterpret_cast<uint8_t *>(udp_payload->end());
+    size_t len   = 0;
+    packet->store(buf, &len);
+    udp_payload->fill(len);
+    this->_packet_handler->send_packet(this, udp_payload);
+  }
+}
+
 std::vector<QUICFrameType>
 QUICNetVConnection::interests()
 {
@@ -745,7 +798,8 @@ QUICNetVConnection::state_handshake(int event, Event *data)
     QUICPacketCreationResult result;
     net_activity(this, this_ethread());
     do {
-      QUICPacketUPtr packet = this->_dequeue_recv_packet(result);
+      uint8_t packet_buf[QUICPacket::MAX_INSTANCE_SIZE];
+      QUICPacketUPtr packet = this->_dequeue_recv_packet(packet_buf, result);
       if (result == QUICPacketCreationResult::NOT_READY) {
         error = nullptr;
       } else if (result == QUICPacketCreationResult::FAILED) {
@@ -767,6 +821,9 @@ QUICNetVConnection::state_handshake(int event, Event *data)
     } while (error == nullptr && (result == QUICPacketCreationResult::SUCCESS || result == QUICPacketCreationResult::IGNORED));
     break;
   }
+  case QUIC_EVENT_STATELESS_RESET:
+    this->_switch_to_draining_state(std::make_unique<QUICConnectionError>(QUICTransErrorCode::NO_ERROR, "Stateless Reset"));
+    break;
   case QUIC_EVENT_ACK_PERIODIC:
     this->_handle_periodic_ack_event();
     break;
@@ -810,6 +867,9 @@ QUICNetVConnection::state_connection_established(int event, Event *data)
     // Reschedule WRITE_READY
     this->_schedule_packet_write_ready(true);
     break;
+  case QUIC_EVENT_STATELESS_RESET:
+    this->_switch_to_draining_state(std::make_unique<QUICConnectionError>(QUICTransErrorCode::NO_ERROR, "Stateless Reset"));
+    break;
   case VC_EVENT_INACTIVITY_TIMEOUT:
     // Start Immediate Close because of Idle Timeout
     this->_handle_idle_timeout();
@@ -844,6 +904,8 @@ QUICNetVConnection::state_connection_closing(int event, Event *data)
     this->_close_closing_timeout(data);
     this->_switch_to_close_state();
     break;
+  case QUIC_EVENT_STATELESS_RESET:
+    break;
   case QUIC_EVENT_ACK_PERIODIC:
   default:
     QUICConDebug("Unexpected event: %s (%d)", QUICDebugNames::quic_event(event), event);
@@ -872,6 +934,8 @@ QUICNetVConnection::state_connection_draining(int event, Event *data)
     this->_close_closing_timeout(data);
     this->_switch_to_close_state();
     break;
+  case QUIC_EVENT_STATELESS_RESET:
+    break;
   case QUIC_EVENT_ACK_PERIODIC:
   default:
     QUICConDebug("Unexpected event: %s (%d)", QUICDebugNames::quic_event(event), event);
@@ -1024,28 +1088,28 @@ QUICNetVConnection::_state_handshake_process_packet(const QUICPacket &packet)
   QUICConnectionErrorUPtr error = nullptr;
   switch (packet.type()) {
   case QUICPacketType::VERSION_NEGOTIATION:
-    error = this->_state_handshake_process_version_negotiation_packet(packet);
+    error = this->_state_handshake_process_version_negotiation_packet(static_cast<const QUICVersionNegotiationPacketR &>(packet));
     break;
   case QUICPacketType::INITIAL:
-    error = this->_state_handshake_process_initial_packet(packet);
+    error = this->_state_handshake_process_initial_packet(static_cast<const QUICInitialPacketR &>(packet));
     break;
   case QUICPacketType::RETRY:
-    error = this->_state_handshake_process_retry_packet(packet);
+    error = this->_state_handshake_process_retry_packet(static_cast<const QUICRetryPacketR &>(packet));
     break;
   case QUICPacketType::HANDSHAKE:
-    error = this->_state_handshake_process_handshake_packet(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->_minimum_encryption_level = QUICEncryptionLevel::HANDSHAKE;
     }
     break;
   case QUICPacketType::ZERO_RTT_PROTECTED:
-    error = this->_state_handshake_process_zero_rtt_protected_packet(packet);
+    error = this->_state_handshake_process_zero_rtt_protected_packet(static_cast<const QUICZeroRttPacketR &>(packet));
     break;
   case QUICPacketType::PROTECTED:
-  default:
     QUICConDebug("Ignore %s(%" PRIu8 ") packet", QUICDebugNames::packet_type(packet.type()), static_cast<uint8_t>(packet.type()));
-
+    break;
+  default:
     error = std::make_unique<QUICConnectionError>(QUICTransErrorCode::INTERNAL_ERROR);
     break;
   }
@@ -1053,7 +1117,7 @@ QUICNetVConnection::_state_handshake_process_packet(const QUICPacket &packet)
 }
 
 QUICConnectionErrorUPtr
-QUICNetVConnection::_state_handshake_process_version_negotiation_packet(const QUICPacket &packet)
+QUICNetVConnection::_state_handshake_process_version_negotiation_packet(const QUICVersionNegotiationPacketR &packet)
 {
   QUICConnectionErrorUPtr error = nullptr;
 
@@ -1082,7 +1146,7 @@ QUICNetVConnection::_state_handshake_process_version_negotiation_packet(const QU
 }
 
 QUICConnectionErrorUPtr
-QUICNetVConnection::_state_handshake_process_initial_packet(const QUICPacket &packet)
+QUICNetVConnection::_state_handshake_process_initial_packet(const QUICInitialPacketR &packet)
 {
   // QUIC packet could be smaller than MINIMUM_INITIAL_PACKET_SIZE when coalescing packets
   // if (packet->size() < MINIMUM_INITIAL_PACKET_SIZE) {
@@ -1101,28 +1165,31 @@ QUICNetVConnection::_state_handshake_process_initial_packet(const QUICPacket &pa
 
     if (!this->_alt_con_manager) {
       this->_alt_con_manager =
-        new QUICAltConnectionManager(this, *this->_ctable, this->_peer_quic_connection_id, this->_quic_config->instance_id(),
-                                     this->_quic_config->active_cid_limit_in(), this->_quic_config->preferred_address_ipv4(),
-                                     this->_quic_config->preferred_address_ipv6());
+        new QUICAltConnectionManager(this, *this->_ctable, *this->_rtable, this->_peer_quic_connection_id,
+                                     this->_quic_config->instance_id(), this->_quic_config->active_cid_limit_in(),
+                                     this->_quic_config->preferred_address_ipv4(), this->_quic_config->preferred_address_ipv6());
       this->_frame_generators.add_generator(*this->_alt_con_manager, QUICFrameGeneratorWeight::EARLY);
       this->_frame_dispatcher->add_handler(this->_alt_con_manager);
     }
     QUICTPConfigQCP tp_config(this->_quic_config, NET_VCONNECTION_IN);
+    if (this->_quic_config->quantum_readiness_test_enabled_in()) {
+      tp_config.add_tp(QUANTUM_TEST_ID, QUANTUM_TEST_VALUE, sizeof(QUANTUM_TEST_VALUE));
+    }
     error = this->_handshake_handler->start(tp_config, packet, &this->_packet_factory, this->_alt_con_manager->preferred_address());
 
     // If version negotiation was failed and VERSION NEGOTIATION packet was sent, nothing to do.
     if (this->_handshake_handler->is_version_negotiated()) {
       error = this->_recv_and_ack(packet);
 
-      if (error == nullptr && !this->_handshake_handler->has_remote_tp()) {
+      if (error == nullptr && this->_handshake_handler->is_completed() && !this->_handshake_handler->has_remote_tp()) {
         error = std::make_unique<QUICConnectionError>(QUICTransErrorCode::TRANSPORT_PARAMETER_ERROR);
       }
     }
   } else {
     if (!this->_alt_con_manager) {
       this->_alt_con_manager =
-        new QUICAltConnectionManager(this, *this->_ctable, this->_peer_quic_connection_id, this->_quic_config->instance_id(),
-                                     this->_quic_config->active_cid_limit_out());
+        new QUICAltConnectionManager(this, *this->_ctable, *this->_rtable, this->_peer_quic_connection_id,
+                                     this->_quic_config->instance_id(), this->_quic_config->active_cid_limit_out());
       this->_frame_generators.add_generator(*this->_alt_con_manager, QUICFrameGeneratorWeight::BEFORE_DATA);
       this->_frame_dispatcher->add_handler(this->_alt_con_manager);
     }
@@ -1138,7 +1205,7 @@ QUICNetVConnection::_state_handshake_process_initial_packet(const QUICPacket &pa
    This doesn't call this->_recv_and_ack(), because RETRY packet doesn't have any frames.
  */
 QUICConnectionErrorUPtr
-QUICNetVConnection::_state_handshake_process_retry_packet(const QUICPacket &packet)
+QUICNetVConnection::_state_handshake_process_retry_packet(const QUICRetryPacketR &packet)
 {
   ink_assert(this->netvc_context == NET_VCONNECTION_OUT);
 
@@ -1147,10 +1214,19 @@ QUICNetVConnection::_state_handshake_process_retry_packet(const QUICPacket &pack
     return nullptr;
   }
 
+  // Check Integrity Tag
+  if (!packet.has_valid_tag(this->_original_quic_connection_id)) {
+    // Discard the packet
+    QUICConDebug("Ignore RETRY packet - integrity tag is not valid");
+    return nullptr;
+  } else {
+    QUICConDebug("Integrity tag is valid");
+  }
+
   // TODO: move packet->payload to _av_token
-  this->_av_token_len = packet.payload_length();
+  this->_av_token_len = packet.token().length();
   this->_av_token     = ats_unique_malloc(this->_av_token_len);
-  memcpy(this->_av_token.get(), packet.payload(), this->_av_token_len);
+  memcpy(this->_av_token.get(), packet.token().buf(), this->_av_token_len);
 
   this->_padder->set_av_token_len(this->_av_token_len);
 
@@ -1174,18 +1250,18 @@ QUICNetVConnection::_state_handshake_process_retry_packet(const QUICPacket &pack
 }
 
 QUICConnectionErrorUPtr
-QUICNetVConnection::_state_handshake_process_handshake_packet(const QUICPacket &packet)
+QUICNetVConnection::_state_handshake_process_handshake_packet(const QUICHandshakePacketR &packet)
 {
   // Source address is verified by receiving any message from the client encrypted using the
   // Handshake keys.
-  if (this->netvc_context == NET_VCONNECTION_IN && !this->_verfied_state.is_verified()) {
-    this->_verfied_state.set_addr_verifed();
+  if (this->netvc_context == NET_VCONNECTION_IN && !this->_verified_state.is_verified()) {
+    this->_verified_state.set_addr_verifed();
   }
   return this->_recv_and_ack(packet);
 }
 
 QUICConnectionErrorUPtr
-QUICNetVConnection::_state_handshake_process_zero_rtt_protected_packet(const QUICPacket &packet)
+QUICNetVConnection::_state_handshake_process_zero_rtt_protected_packet(const QUICZeroRttPacketR &packet)
 {
   this->_stream_manager->init_flow_control_params(this->_handshake_handler->local_transport_parameters(),
                                                   this->_handshake_handler->remote_transport_parameters());
@@ -1194,7 +1270,7 @@ QUICNetVConnection::_state_handshake_process_zero_rtt_protected_packet(const QUI
 }
 
 QUICConnectionErrorUPtr
-QUICNetVConnection::_state_connection_established_process_protected_packet(const QUICPacket &packet)
+QUICNetVConnection::_state_connection_established_process_protected_packet(const QUICShortHeaderPacketR &packet)
 {
   QUICConnectionErrorUPtr error = nullptr;
   bool has_non_probing_frame    = false;
@@ -1204,16 +1280,10 @@ QUICNetVConnection::_state_connection_established_process_protected_packet(const
     return error;
   }
 
-  // Migrate connection if required
-  // FIXME Connection migration will be initiated when a peer sent non-probing frames.
-  // We need to two or more paths because we need to respond to probing packets on a new path and also need to send other frames
-  // on the old path until they initiate migration.
-  // if (packet.destination_cid() == this->_quic_connection_id && has_non_probing_frame) {
+  // Migrate connection if needed
   if (this->_alt_con_manager != nullptr) {
-    if (packet.destination_cid() != this->_quic_connection_id || !ats_ip_addr_port_eq(packet.from(), this->remote_addr)) {
-      if (!has_non_probing_frame) {
-        QUICConDebug("FIXME: Connection migration has been initiated without non-probing frames");
-      }
+    if (has_non_probing_frame &&
+        (packet.destination_cid() != this->_quic_connection_id || !ats_ip_addr_port_eq(packet.from(), this->remote_addr))) {
       error = this->_state_connection_established_migrate_connection(packet);
       if (error != nullptr) {
         return error;
@@ -1238,7 +1308,8 @@ QUICNetVConnection::_state_connection_established_receive_packet()
   // Receive a QUIC packet
   net_activity(this, this_ethread());
   do {
-    QUICPacketUPtr packet = this->_dequeue_recv_packet(result);
+    uint8_t packet_buf[QUICPacket::MAX_INSTANCE_SIZE];
+    QUICPacketUPtr packet = this->_dequeue_recv_packet(packet_buf, result);
     if (result == QUICPacketCreationResult::FAILED) {
       // Don't make this error, and discard the packet.
       // Because:
@@ -1256,13 +1327,13 @@ QUICNetVConnection::_state_connection_established_receive_packet()
     // Process the packet
     switch (packet->type()) {
     case QUICPacketType::PROTECTED:
-      error = this->_state_connection_established_process_protected_packet(*packet);
+      error = this->_state_connection_established_process_protected_packet(static_cast<QUICShortHeaderPacketR &>(*packet));
       break;
     case QUICPacketType::INITIAL:
     case QUICPacketType::HANDSHAKE:
     case QUICPacketType::ZERO_RTT_PROTECTED:
       // Pass packet to _recv_and_ack to send ack to the packet. Stream data will be discarded by offset mismatch.
-      error = this->_recv_and_ack(*packet);
+      error = this->_recv_and_ack(static_cast<QUICPacketR &>(*packet));
       break;
     default:
       QUICConDebug("Unknown packet type: %s(%" PRIu8 ")", QUICDebugNames::packet_type(packet->type()),
@@ -1281,14 +1352,15 @@ QUICNetVConnection::_state_closing_receive_packet()
 {
   while (this->_packet_recv_queue.size() > 0) {
     QUICPacketCreationResult result;
-    QUICPacketUPtr packet = this->_dequeue_recv_packet(result);
+    uint8_t packet_buf[QUICPacket::MAX_INSTANCE_SIZE];
+    QUICPacketUPtr packet = this->_dequeue_recv_packet(packet_buf, result);
     if (result == QUICPacketCreationResult::SUCCESS) {
       switch (packet->type()) {
       case QUICPacketType::VERSION_NEGOTIATION:
         // Ignore VN packets on closing state
         break;
       default:
-        this->_recv_and_ack(*packet);
+        this->_recv_and_ack(static_cast<QUICPacketR &>(*packet));
         break;
       }
     }
@@ -1312,9 +1384,10 @@ QUICNetVConnection::_state_draining_receive_packet()
 {
   while (this->_packet_recv_queue.size() > 0) {
     QUICPacketCreationResult result;
-    QUICPacketUPtr packet = this->_dequeue_recv_packet(result);
+    uint8_t packet_buf[QUICPacket::MAX_INSTANCE_SIZE];
+    QUICPacketUPtr packet = this->_dequeue_recv_packet(packet_buf, result);
     if (result == QUICPacketCreationResult::SUCCESS) {
-      this->_recv_and_ack(*packet);
+      this->_recv_and_ack(static_cast<QUICPacketR &>(*packet));
       // Do NOT schedule WRITE_READY event from this point.
       // An endpoint in the draining state MUST NOT send any packets.
     }
@@ -1348,25 +1421,34 @@ QUICNetVConnection::_state_common_send_packet()
 
     uint32_t written = 0;
     for (int i = static_cast<int>(this->_minimum_encryption_level); i <= static_cast<int>(QUICEncryptionLevel::ONE_RTT); ++i) {
+      uint8_t packet_buf[QUICPacket::MAX_INSTANCE_SIZE];
+
       auto level = QUIC_ENCRYPTION_LEVELS[i];
       if (level == QUICEncryptionLevel::ONE_RTT && !this->_handshake_handler->is_completed()) {
         continue;
       }
 
       uint32_t max_packet_size = udp_payload_len - written;
-      if (this->netvc_context == NET_VCONNECTION_IN && !this->_verfied_state.is_verified()) {
-        max_packet_size = std::min(max_packet_size, this->_verfied_state.windows());
+      if (this->netvc_context == NET_VCONNECTION_IN && !this->_verified_state.is_verified()) {
+        max_packet_size = std::min(max_packet_size, this->_verified_state.windows());
       }
 
       QUICPacketInfoUPtr packet_info = std::make_unique<QUICPacketInfo>();
-      QUICPacketUPtr packet          = this->_packetize_frames(level, max_packet_size, packet_info->frames);
+      QUICPacketUPtr packet          = this->_packetize_frames(packet_buf, level, max_packet_size, packet_info->frames);
 
       if (packet) {
-        packet_info->packet_number    = packet->packet_number();
-        packet_info->time_sent        = Thread::get_hrtime();
-        packet_info->ack_eliciting    = packet->is_ack_eliciting();
-        packet_info->is_crypto_packet = packet->is_crypto_packet();
-        packet_info->in_flight        = true;
+        // trigger callback
+        this->_context->trigger(QUICContext::CallbackEvent::PACKET_SEND, packet.get());
+
+        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;
         if (packet_info->ack_eliciting) {
           packet_info->sent_bytes = packet->size();
         } else {
@@ -1375,9 +1457,9 @@ QUICNetVConnection::_state_common_send_packet()
         packet_info->type     = packet->type();
         packet_info->pn_space = QUICTypeUtil::pn_space(level);
 
-        if (this->netvc_context == NET_VCONNECTION_IN && !this->_verfied_state.is_verified()) {
-          QUICConDebug("send to unverified window: %u", this->_verfied_state.windows());
-          this->_verfied_state.consume(packet->size());
+        if (this->netvc_context == NET_VCONNECTION_IN && !this->_verified_state.is_verified()) {
+          QUICConDebug("send to unverified window: %u", this->_verified_state.windows());
+          this->_verified_state.consume(packet->size());
         }
 
         // TODO: do not write two QUIC Short Header Packets
@@ -1415,6 +1497,9 @@ QUICNetVConnection::_state_common_send_packet()
   }
 
   if (packet_count) {
+    this->_context->trigger(QUICContext::CallbackEvent::METRICS_UPDATE, this->_congestion_controller->congestion_window(),
+                            this->_congestion_controller->bytes_in_flight(), this->_congestion_controller->current_ssthresh());
+
     QUIC_INCREMENT_DYN_STAT_EX(QUICStats::total_packets_sent_stat, packet_count);
     net_activity(this, this_ethread());
   }
@@ -1447,11 +1532,9 @@ QUICNetVConnection::_store_frame(Ptr<IOBufferBlock> parent_block, size_t &size_a
 {
   Ptr<IOBufferBlock> new_block = frame.to_io_buffer_block(max_frame_size);
 
-  size_added             = 0;
-  Ptr<IOBufferBlock> tmp = new_block;
-  while (tmp) {
+  size_added = 0;
+  for (Ptr<IOBufferBlock> tmp = new_block; tmp; tmp = tmp->next) {
     size_added += tmp->size();
-    tmp = tmp->next;
   }
 
   if (parent_block == nullptr) {
@@ -1480,7 +1563,8 @@ QUICNetVConnection::_store_frame(Ptr<IOBufferBlock> parent_block, size_t &size_a
 }
 
 QUICPacketUPtr
-QUICNetVConnection::_packetize_frames(QUICEncryptionLevel level, uint64_t max_packet_size, std::vector<QUICFrameInfo> &frames)
+QUICNetVConnection::_packetize_frames(uint8_t *packet_buf, QUICEncryptionLevel level, uint64_t max_packet_size,
+                                      std::vector<QUICFrameInfo> &frames)
 {
   QUICPacketUPtr packet = QUICPacketFactory::create_null_packet();
   if (max_packet_size <= MAX_PACKET_OVERHEAD) {
@@ -1516,24 +1600,17 @@ QUICNetVConnection::_packetize_frames(QUICEncryptionLevel level, uint64_t max_pa
         break;
       }
 
-      if (g == this->_stream_manager) {
-        // Don't send DATA frames if current path is not validated
-        // FIXME will_generate_frame should receive more parameters so we don't need extra checks
-        if (auto path = this->_path_manager->get_verified_path(); !path.remote_ep().isValid()) {
-          break;
-        }
-      }
-
       // Common block
       frame =
         g->generate_frame(frame_instance_buffer, level, this->_remote_flow_controller->credit(), max_frame_size, len, seq_num);
       if (frame) {
+        this->_context->trigger(QUICContext::CallbackEvent::FRAME_PACKETIZE, *frame);
         // Some frame types must not be sent on Initial and Handshake packets
         switch (auto t = frame->type(); level) {
         case QUICEncryptionLevel::INITIAL:
         case QUICEncryptionLevel::HANDSHAKE:
           ink_assert(t == QUICFrameType::CRYPTO || t == QUICFrameType::ACK || t == QUICFrameType::PADDING ||
-                     t == QUICFrameType::CONNECTION_CLOSE);
+                     t == QUICFrameType::CONNECTION_CLOSE || t == QUICFrameType::PING);
           break;
         default:
           break;
@@ -1576,7 +1653,7 @@ QUICNetVConnection::_packetize_frames(QUICEncryptionLevel level, uint64_t max_pa
   // Schedule a packet
   if (len != 0) {
     // Packet is retransmittable if it's not ack only packet
-    packet = this->_build_packet(level, first_block, ack_eliciting, probing, crypto);
+    packet = this->_build_packet(packet_buf, level, first_block, ack_eliciting, probing, crypto);
   }
 
   return packet;
@@ -1600,22 +1677,30 @@ QUICNetVConnection::_packetize_closing_frame()
   size_t size_added       = 0;
   uint64_t max_frame_size = static_cast<uint64_t>(max_size);
   std::vector<QUICFrameInfo> frames;
-  Ptr<IOBufferBlock> parent_block;
-  parent_block = nullptr;
-  parent_block = this->_store_frame(parent_block, size_added, max_frame_size, *frame, 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));
+  first_block->fill(0);
+  last_block = this->_store_frame(last_block, size_added, max_frame_size, *frame, frames);
 
   QUICEncryptionLevel level = this->_hs_protocol->current_encryption_level();
   ink_assert(level != QUICEncryptionLevel::ZERO_RTT);
-  this->_the_final_packet = this->_build_packet(level, parent_block, true, false, false);
+  this->_the_final_packet = this->_build_packet(this->_final_packet_buf, level, first_block, true, false, false);
 }
 
 QUICConnectionErrorUPtr
-QUICNetVConnection::_recv_and_ack(const QUICPacket &packet, bool *has_non_probing_frame)
+QUICNetVConnection::_recv_and_ack(const QUICPacketR &packet, bool *has_non_probing_frame)
 {
   ink_assert(packet.type() != QUICPacketType::RETRY);
 
-  const uint8_t *payload      = packet.payload();
   uint16_t size               = packet.payload_length();
+  ats_unique_buf payload_ubuf = ats_unique_malloc(size);
+  uint8_t *payload            = payload_ubuf.get();
+  size_t copied_len           = 0;
+  for (auto b = packet.payload_block(); b; b = b->next) {
+    memcpy(payload + copied_len, b->start(), b->size());
+    copied_len += b->size();
+  }
   QUICPacketNumber packet_num = packet.packet_number();
   QUICEncryptionLevel level   = QUICTypeUtil::encryption_level(packet.type());
 
@@ -1627,8 +1712,10 @@ QUICNetVConnection::_recv_and_ack(const QUICPacket &packet, bool *has_non_probin
     *has_non_probing_frame = false;
   }
 
-  error =
-    this->_frame_dispatcher->receive_frames(level, payload, size, ack_only, is_flow_controlled, has_non_probing_frame, &packet);
+  error = this->_frame_dispatcher->receive_frames(*this->_context, level, payload, size, ack_only, is_flow_controlled,
+                                                  has_non_probing_frame, static_cast<const QUICPacketR *>(&packet));
+  this->_context->trigger(QUICContext::CallbackEvent::PACKET_RECV, &packet);
+
   if (error != nullptr) {
     return error;
   }
@@ -1653,21 +1740,15 @@ QUICNetVConnection::_recv_and_ack(const QUICPacket &packet, bool *has_non_probin
 }
 
 QUICPacketUPtr
-QUICNetVConnection::_build_packet(QUICEncryptionLevel level, Ptr<IOBufferBlock> parent_block, bool ack_eliciting, bool probing,
-                                  bool crypto)
+QUICNetVConnection::_build_packet(uint8_t *packet_buf, QUICEncryptionLevel level, Ptr<IOBufferBlock> parent_block,
+                                  bool ack_eliciting, bool probing, bool crypto)
 {
   QUICPacketType type   = QUICTypeUtil::packet_type(level);
   QUICPacketUPtr packet = QUICPacketFactory::create_null_packet();
 
-  // FIXME Pass parent_block to create_x_packet
-  // No need to make a unique buf here
-  ats_unique_buf buf = ats_unique_malloc(2048);
-  uint8_t *raw_buf   = buf.get();
-  size_t len         = 0;
-  while (parent_block) {
-    memcpy(raw_buf + len, parent_block->start(), parent_block->size());
-    len += parent_block->size();
-    parent_block = parent_block->next;
+  size_t len = 0;
+  for (Ptr<IOBufferBlock> tmp = parent_block; tmp; tmp = tmp->next) {
+    len += tmp->size();
   }
 
   switch (type) {
@@ -1688,26 +1769,26 @@ QUICNetVConnection::_build_packet(QUICEncryptionLevel level, Ptr<IOBufferBlock>
     }
 
     packet = this->_packet_factory.create_initial_packet(
-      dcid, this->_quic_connection_id, this->_largest_acked_packet_number(QUICEncryptionLevel::INITIAL), std::move(buf), len,
-      ack_eliciting, probing, crypto, std::move(token), token_len);
+      packet_buf, dcid, this->_quic_connection_id, this->_largest_acked_packet_number(QUICEncryptionLevel::INITIAL), parent_block,
+      len, ack_eliciting, probing, crypto, std::move(token), token_len);
     break;
   }
   case QUICPacketType::HANDSHAKE: {
-    packet = this->_packet_factory.create_handshake_packet(this->_peer_quic_connection_id, this->_quic_connection_id,
+    packet = this->_packet_factory.create_handshake_packet(packet_buf, this->_peer_quic_connection_id, this->_quic_connection_id,
                                                            this->_largest_acked_packet_number(QUICEncryptionLevel::HANDSHAKE),
-                                                           std::move(buf), len, ack_eliciting, probing, crypto);
+                                                           parent_block, len, ack_eliciting, probing, crypto);
     break;
   }
   case QUICPacketType::ZERO_RTT_PROTECTED: {
-    packet = this->_packet_factory.create_zero_rtt_packet(this->_original_quic_connection_id, this->_quic_connection_id,
+    packet = this->_packet_factory.create_zero_rtt_packet(packet_buf, this->_original_quic_connection_id, this->_quic_connection_id,
                                                           this->_largest_acked_packet_number(QUICEncryptionLevel::ZERO_RTT),
-                                                          std::move(buf), len, ack_eliciting, probing);
+                                                          parent_block, len, ack_eliciting, probing);
     break;
   }
   case QUICPacketType::PROTECTED: {
-    packet = this->_packet_factory.create_protected_packet(this->_peer_quic_connection_id,
-                                                           this->_largest_acked_packet_number(QUICEncryptionLevel::ONE_RTT),
-                                                           std::move(buf), len, ack_eliciting, probing);
+    packet = this->_packet_factory.create_short_header_packet(packet_buf, this->_peer_quic_connection_id,
+                                                              this->_largest_acked_packet_number(QUICEncryptionLevel::ONE_RTT),
+                                                              parent_block, len, ack_eliciting, probing);
     break;
   }
   default:
@@ -1750,29 +1831,31 @@ QUICNetVConnection::_handle_error(QUICConnectionErrorUPtr error)
             static_cast<unsigned int>(error->cls), QUICDebugNames::error_code(error->code), error->code);
 
   // Connection Error
-  this->close(std::move(error));
+  this->close_quic_connection(std::move(error));
 }
 
 QUICPacketUPtr
-QUICNetVConnection::_dequeue_recv_packet(QUICPacketCreationResult &result)
+QUICNetVConnection::_dequeue_recv_packet(uint8_t *packet_buf, QUICPacketCreationResult &result)
 {
-  QUICPacketUPtr packet = this->_packet_recv_queue.dequeue(result);
+  QUICPacketUPtr packet = this->_packet_recv_queue.dequeue(packet_buf, result);
 
   if (result == QUICPacketCreationResult::SUCCESS) {
     if (this->direction() == NET_VCONNECTION_OUT) {
       // Reset CID if a server sent back a new CID
-      // FIXME This should happen only once
-      QUICConnectionId src_cid = packet->source_cid();
-      // FIXME src connection id could be zero ? if so, check packet header type.
-      if (src_cid != QUICConnectionId::ZERO()) {
-        if (this->_peer_quic_connection_id != src_cid) {
-          this->_update_peer_cid(src_cid);
+      // FIXME This should happen only once - it should probably be controlled by PathManager
+      if (packet->type() != QUICPacketType::PROTECTED && (packet->type() != QUICPacketType::RETRY || !this->_av_token)) {
+        QUICConnectionId src_cid = static_cast<QUICLongHeaderPacketR &>(*packet).source_cid();
+        // FIXME src connection id could be zero ? if so, check packet header type.
+        if (src_cid != QUICConnectionId::ZERO()) {
+          if (this->_peer_quic_connection_id != src_cid) {
+            this->_update_peer_cid(src_cid);
+          }
         }
       }
     }
 
-    if (!this->_verfied_state.is_verified()) {
-      this->_verfied_state.fill(packet->size());
+    if (!this->_verified_state.is_verified()) {
+      this->_verified_state.fill(packet->size());
     }
   }
 
@@ -1790,11 +1873,15 @@ QUICNetVConnection::_dequeue_recv_packet(QUICPacketCreationResult &result)
     QUICConDebug("Unsupported version");
     break;
   case QUICPacketCreationResult::SUCCESS:
-    if (packet->type() == QUICPacketType::VERSION_NEGOTIATION) {
+    switch (packet->type()) {
+    case QUICPacketType::VERSION_NEGOTIATION:
+    case QUICPacketType::RETRY:
       QUICConDebug("[RX] %s packet size=%u", QUICDebugNames::packet_type(packet->type()), packet->size());
-    } else {
+      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());
+      break;
     }
     break;
   default:
@@ -1926,6 +2013,14 @@ QUICNetVConnection::_complete_handshake_if_possible()
     this->_handshake_handler->remote_transport_parameters()->getAsUInt(QUICTransportParameterId::ACK_DELAY_EXPONENT);
   this->_loss_detector->update_ack_delay_exponent(ack_delay_exponent);
 
+  const uint8_t *reset_token;
+  uint16_t reset_token_len;
+  reset_token = this->_handshake_handler->remote_transport_parameters()->getAsBytes(QUICTransportParameterId::STATELESS_RESET_TOKEN,
+                                                                                    reset_token_len);
+  if (reset_token) {
+    this->_rtable->insert({reset_token}, this);
+  }
+
   this->_start_application();
 
   return 0;
@@ -1974,12 +2069,16 @@ QUICNetVConnection::_switch_to_established_state()
     SET_HANDLER((NetVConnHandler)&QUICNetVConnection::state_connection_established);
 
     std::shared_ptr<const QUICTransportParameters> remote_tp = this->_handshake_handler->remote_transport_parameters();
+    std::shared_ptr<const QUICTransportParameters> local_tp  = this->_handshake_handler->local_transport_parameters();
 
     uint64_t active_cid_limit = remote_tp->getAsUInt(QUICTransportParameterId::ACTIVE_CONNECTION_ID_LIMIT);
     if (active_cid_limit) {
       this->_alt_con_manager->set_remote_active_cid_limit(active_cid_limit);
     }
 
+    this->set_inactivity_timeout(HRTIME_MSECONDS(std::min(remote_tp->getAsUInt(QUICTransportParameterId::MAX_IDLE_TIMEOUT),
+                                                          local_tp->getAsUInt(QUICTransportParameterId::MAX_IDLE_TIMEOUT))));
+
     if (this->direction() == NET_VCONNECTION_OUT) {
       uint16_t len;
       const uint8_t *pref_addr_buf = remote_tp->getAsBytes(QUICTransportParameterId::PREFERRED_ADDRESS, len);
@@ -2100,12 +2199,7 @@ void
 QUICNetVConnection::_update_peer_cid(const QUICConnectionId &new_cid)
 {
   if (is_debug_tag_set(QUIC_DEBUG_TAG.data())) {
-    char old_cid_str[QUICConnectionId::MAX_HEX_STR_LENGTH];
-    char new_cid_str[QUICConnectionId::MAX_HEX_STR_LENGTH];
-    this->_peer_quic_connection_id.hex(old_cid_str, QUICConnectionId::MAX_HEX_STR_LENGTH);
-    new_cid.hex(new_cid_str, QUICConnectionId::MAX_HEX_STR_LENGTH);
-
-    QUICConDebug("dcid: %s -> %s", old_cid_str, new_cid_str);
+    QUICConDebug("update peer dcid: %s -> %s", this->_peer_quic_connection_id.hex().c_str(), new_cid.hex().c_str());
   }
 
   this->_peer_old_quic_connection_id = this->_peer_quic_connection_id;
@@ -2117,12 +2211,7 @@ void
 QUICNetVConnection::_update_local_cid(const QUICConnectionId &new_cid)
 {
   if (is_debug_tag_set(QUIC_DEBUG_TAG.data())) {
-    char old_cid_str[QUICConnectionId::MAX_HEX_STR_LENGTH];
-    char new_cid_str[QUICConnectionId::MAX_HEX_STR_LENGTH];
-    this->_quic_connection_id.hex(old_cid_str, QUICConnectionId::MAX_HEX_STR_LENGTH);
-    new_cid.hex(new_cid_str, QUICConnectionId::MAX_HEX_STR_LENGTH);
-
-    QUICConDebug("scid: %s -> %s", old_cid_str, new_cid_str);
+    QUICConDebug("update local dcid: %s -> %s", this->_quic_connection_id.hex().c_str(), new_cid.hex().c_str());
   }
 
   this->_quic_connection_id = new_cid;
@@ -2136,12 +2225,7 @@ QUICNetVConnection::_rerandomize_original_cid()
   this->_original_quic_connection_id.randomize();
 
   if (is_debug_tag_set(QUIC_DEBUG_TAG.data())) {
-    char old_cid_str[QUICConnectionId::MAX_HEX_STR_LENGTH];
-    char new_cid_str[QUICConnectionId::MAX_HEX_STR_LENGTH];
-    tmp.hex(old_cid_str, QUICConnectionId::MAX_HEX_STR_LENGTH);
-    this->_original_quic_connection_id.hex(new_cid_str, QUICConnectionId::MAX_HEX_STR_LENGTH);
-
-    QUICConDebug("original cid: %s -> %s", old_cid_str, new_cid_str);
+    QUICConDebug("original cid: %s -> %s", tmp.hex().c_str(), this->_original_quic_connection_id.hex().c_str());
   }
 }
 
@@ -2153,12 +2237,13 @@ QUICNetVConnection::_setup_handshake_protocol(shared_SSL_CTX ctx)
   QUICTLS *tls = new QUICTLS(this->_pp_key_info, ctx.get(), this->direction(), this->options,
                              this->_quic_config->client_session_file(), this->_quic_config->client_keylog_file());
   SSL_set_ex_data(tls->ssl_handle(), QUIC::ssl_quic_qc_index, static_cast<QUICConnection *>(this));
+  TLSSessionResumptionSupport::bind(tls->ssl_handle(), this);
 
   return tls;
 }
 
 QUICConnectionErrorUPtr
-QUICNetVConnection::_state_connection_established_migrate_connection(const QUICPacket &p)
+QUICNetVConnection::_state_connection_established_migrate_connection(const QUICPacketR &p)
 {
   ink_assert(this->_handshake_handler->is_completed());
 
@@ -2208,9 +2293,7 @@ QUICNetVConnection::_state_connection_established_migrate_connection(const QUICP
         this->_validate_new_path(new_path);
       }
     } else {
-      char dcid_str[QUICConnectionId::MAX_HEX_STR_LENGTH];
-      dcid.hex(dcid_str, QUICConnectionId::MAX_HEX_STR_LENGTH);
-      QUICConDebug("Connection migration failed cid=%s", dcid_str);
+      QUICConDebug("Connection migration failed cid=%s", dcid.hex().c_str());
     }
   }
 
@@ -2265,3 +2348,9 @@ QUICNetVConnection::_handle_periodic_ack_event()
     this->_schedule_packet_write_ready();
   }
 }
+
+const IpEndpoint &
+QUICNetVConnection::_getLocalEndpoint()
+{
+  return local_addr;
+}
diff --git a/iocore/net/QUICPacketHandler.cc b/iocore/net/QUICPacketHandler.cc
index 9afee71..a6e214d 100644
--- a/iocore/net/QUICPacketHandler.cc
+++ b/iocore/net/QUICPacketHandler.cc
@@ -22,6 +22,9 @@
 #include "tscore/ink_config.h"
 #include "P_Net.h"
 
+#include "P_QUICPacketHandler.h"
+#include "P_QUICNetProcessor.h"
+#include "P_QUICNet.h"
 #include "P_QUICClosedConCollector.h"
 
 #include "QUICGlobals.h"
@@ -29,6 +32,10 @@
 #include "QUICPacket.h"
 #include "QUICDebugNames.h"
 #include "QUICEvents.h"
+#include "QUICResetTokenTable.h"
+
+#include "QUICMultiCertConfigLoader.h"
+#include "QUICTLS.h"
 
 static constexpr char debug_tag[] = "quic_sec";
 
@@ -42,7 +49,7 @@ static constexpr char debug_tag[] = "quic_sec";
 //
 // QUICPacketHandler
 //
-QUICPacketHandler::QUICPacketHandler()
+QUICPacketHandler::QUICPacketHandler(QUICResetTokenTable &rtable) : _rtable(rtable)
 {
   this->_closed_con_collector        = new QUICClosedConCollector;
   this->_closed_con_collector->mutex = new_ProxyMutex();
@@ -118,11 +125,18 @@ QUICPacketHandler::_send_packet(UDPConnection *udp_con, IpEndpoint &addr, Ptr<IO
   get_UDPNetHandler(static_cast<UnixUDPConnection *>(udp_con)->ethread)->signalActivity();
 }
 
+QUICConnection *
+QUICPacketHandler::_check_stateless_reset(const uint8_t *buf, size_t buf_len)
+{
+  return this->_rtable.lookup({buf + (buf_len - 16)});
+}
+
 //
 // QUICPacketHandlerIn
 //
-QUICPacketHandlerIn::QUICPacketHandlerIn(const NetProcessor::AcceptOptions &opt, QUICConnectionTable &ctable)
-  : NetAccept(opt), QUICPacketHandler(), _ctable(ctable)
+QUICPacketHandlerIn::QUICPacketHandlerIn(const NetProcessor::AcceptOptions &opt, QUICConnectionTable &ctable,
+                                         QUICResetTokenTable &rtable)
+  : NetAccept(opt), QUICPacketHandler(rtable), _ctable(ctable)
 {
   this->mutex = new_ProxyMutex();
   // create Connection Table
@@ -141,7 +155,7 @@ NetAccept *
 QUICPacketHandlerIn::clone() const
 {
   NetAccept *na;
-  na  = new QUICPacketHandlerIn(opt, this->_ctable);
+  na  = new QUICPacketHandlerIn(opt, this->_ctable, this->_rtable);
   *na = *this;
   return na;
 }
@@ -254,7 +268,7 @@ QUICPacketHandlerIn::_recv_packet(int event, UDPPacket *udp_packet)
     }
 
     QUICPacketType type = QUICPacketType::UNINITIALIZED;
-    QUICPacketLongHeader::type(type, buf, buf_len);
+    QUICLongHeaderPacketR::type(type, buf, buf_len);
     if (type == QUICPacketType::INITIAL) {
       // [draft-18] 7.2.
       // When an Initial packet is sent by a client which has not previously received a Retry packet from the server, it populates
@@ -293,22 +307,33 @@ QUICPacketHandlerIn::_recv_packet(int event, UDPPacket *udp_packet)
   // [draft-12] 6.1.2.  Server Packet Handling
   // Servers MUST drop incoming packets under all other circumstances. They SHOULD send a Stateless Reset (Section 6.10.4) if a
   // connection ID is present in the header.
-  if ((!vc && !QUICInvariants::is_long_header(buf)) || (vc && vc->in_closed_queue)) {
-    if (is_debug_tag_set(debug_tag)) {
-      char dcid_str[QUICConnectionId::MAX_HEX_STR_LENGTH];
-      dcid.hex(dcid_str, QUICConnectionId::MAX_HEX_STR_LENGTH);
+  if (!vc && !QUICInvariants::is_long_header(buf)) {
+    auto connection = static_cast<QUICNetVConnection *>(this->_check_stateless_reset(buf, buf_len));
+    if (connection) {
+      QUICDebug("Stateless Reset has been received");
+      connection->thread->schedule_imm(connection, QUIC_EVENT_STATELESS_RESET);
+      return;
+    }
 
-      if (!vc && !QUICInvariants::is_long_header(buf)) {
-        QUICDebugDS(scid, dcid, "sent Stateless Reset : connection not found, dcid=%s", dcid_str);
-      } else if (vc && vc->in_closed_queue) {
-        QUICDebugDS(scid, dcid, "sent Stateless Reset : connection is already closed, dcid=%s", dcid_str);
-      }
+    bool sent =
+      this->_send_stateless_reset(dcid, params->instance_id(), udp_packet->getConnection(), udp_packet->from, buf_len - 1);
+    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());
     }
 
-    QUICStatelessResetToken token(dcid, params->instance_id());
-    auto packet = QUICPacketFactory::create_stateless_reset_packet(dcid, token);
-    this->_send_packet(*packet, udp_packet->getConnection(), udp_packet->from, 1200, nullptr, 0);
+    return;
+
+  } else if (vc && vc->in_closed_queue) {
+    bool sent =
+      this->_send_stateless_reset(dcid, params->instance_id(), udp_packet->getConnection(), udp_packet->from, buf_len - 1);
     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());
+    }
+
     return;
   }
 
@@ -323,13 +348,11 @@ QUICPacketHandlerIn::_recv_packet(int event, UDPPacket *udp_packet)
     QUICConnectionId peer_cid     = scid;
 
     if (is_debug_tag_set("quic_sec")) {
-      char client_dcid_hex_str[QUICConnectionId::MAX_HEX_STR_LENGTH];
-      original_cid.hex(client_dcid_hex_str, QUICConnectionId::MAX_HEX_STR_LENGTH);
-      QUICDebugDS(peer_cid, original_cid, "client initial dcid=%s", client_dcid_hex_str);
+      QUICDebugDS(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->_ctable);
+    vc->init(peer_cid, original_cid, cid_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();
@@ -373,7 +396,7 @@ QUICPacketHandlerIn::_stateless_retry(const uint8_t *buf, uint64_t buf_len, UDPC
                                       QUICConnectionId dcid, QUICConnectionId scid, QUICConnectionId *original_cid)
 {
   QUICPacketType type = QUICPacketType::UNINITIALIZED;
-  QUICPacketLongHeader::type(type, buf, buf_len);
+  QUICPacketR::type(type, buf, buf_len);
 
   if (type != QUICPacketType::INITIAL) {
     return 1;
@@ -383,7 +406,7 @@ QUICPacketHandlerIn::_stateless_retry(const uint8_t *buf, uint64_t buf_len, UDPC
   size_t token_length              = 0;
   uint8_t token_length_field_len   = 0;
   size_t token_length_field_offset = 0;
-  if (!QUICPacketLongHeader::token_length(token_length, token_length_field_len, token_length_field_offset, buf, buf_len)) {
+  if (!QUICInitialPacketR::token_length(token_length, token_length_field_len, token_length_field_offset, buf, buf_len)) {
     return -1;
   }
 
@@ -391,10 +414,11 @@ QUICPacketHandlerIn::_stateless_retry(const uint8_t *buf, uint64_t buf_len, UDPC
     QUICRetryToken token(from, dcid);
     QUICConnectionId local_cid;
     local_cid.randomize();
-    QUICPacketUPtr retry_packet = QUICPacketFactory::create_retry_packet(scid, local_cid, dcid, token);
+    QUICPacketUPtr retry_packet = QUICPacketFactory::create_retry_packet(scid, local_cid, token);
 
-    QUICDebug("[TX] %s packet ODCID=%" PRIx64, QUICDebugNames::packet_type(retry_packet->type()),
-              static_cast<uint64_t>(static_cast<const QUICPacketLongHeader &>(retry_packet->header()).original_dcid()));
+    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]);
     this->_send_packet(*retry_packet, connection, from, 1200, nullptr, 0);
 
     return -2;
@@ -405,8 +429,13 @@ 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));
         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]);
+        this->_send_invalid_token_error(buf, buf_len, connection, from);
         return -3;
       }
     } else {
@@ -418,10 +447,61 @@ QUICPacketHandlerIn::_stateless_retry(const uint8_t *buf, uint64_t buf_len, UDPC
   return 0;
 }
 
+bool
+QUICPacketHandlerIn::_send_stateless_reset(QUICConnectionId dcid, uint32_t instance_id, UDPConnection *udp_con, IpEndpoint &addr,
+                                           size_t maximum_size)
+{
+  QUICStatelessResetToken token(dcid, instance_id);
+  auto packet = QUICPacketFactory::create_stateless_reset_packet(token, maximum_size);
+  if (packet) {
+    this->_send_packet(*packet, udp_con, addr, 1200, nullptr, 0);
+    return true;
+  }
+  return false;
+}
+
+void
+QUICPacketHandlerIn::_send_invalid_token_error(const uint8_t *initial_packet, uint64_t initial_packet_len,
+                                               UDPConnection *connection, IpEndpoint from)
+{
+  QUICConnectionId scid_in_initial;
+  QUICConnectionId dcid_in_initial;
+  QUICInvariants::scid(scid_in_initial, initial_packet, initial_packet_len);
+  QUICInvariants::dcid(dcid_in_initial, initial_packet, initial_packet_len);
+
+  // Create CONNECTION_CLOSE frame
+  auto error = std::make_unique<QUICConnectionError>(QUICTransErrorCode::INVALID_TOKEN);
+  uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE];
+  QUICFrame *frame         = QUICFrameFactory::create_connection_close_frame(frame_buf, *error);
+  Ptr<IOBufferBlock> block = frame->to_io_buffer_block(1200);
+  size_t block_len         = 0;
+  for (Ptr<IOBufferBlock> tmp = block; tmp; tmp = tmp->next) {
+    block_len += tmp->size();
+  }
+  frame->~QUICFrame();
+
+  // Prepare for packet protection
+  QUICPacketProtectionKeyInfo ppki;
+  ppki.set_context(QUICPacketProtectionKeyInfo::Context::SERVER);
+  QUICPacketFactory pf(ppki);
+  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);
+
+  // Create INITIAL packet
+  QUICConnectionId scid;
+  scid.randomize();
+  uint8_t packet_buf[QUICPacket::MAX_INSTANCE_SIZE];
+  QUICPacketUPtr cc_packet = pf.create_initial_packet(packet_buf, scid_in_initial, scid, 0, block, block_len, 0, 0, 1);
+
+  this->_send_packet(*cc_packet, connection, from, 0, &php, scid_in_initial);
+}
+
 //
 // QUICPacketHandlerOut
 //
-QUICPacketHandlerOut::QUICPacketHandlerOut() : Continuation(new_ProxyMutex()), QUICPacketHandler()
+QUICPacketHandlerOut::QUICPacketHandlerOut(QUICResetTokenTable &rtable) : Continuation(new_ProxyMutex()), QUICPacketHandler(rtable)
 {
   SET_HANDLER(&QUICPacketHandlerOut::event_handler);
 }
@@ -466,10 +546,11 @@ QUICPacketHandlerOut::_get_continuation()
 void
 QUICPacketHandlerOut::_recv_packet(int event, UDPPacket *udp_packet)
 {
-  if (is_debug_tag_set(debug_tag)) {
-    IOBufferBlock *block = udp_packet->getIOBlockChain();
-    const uint8_t *buf   = reinterpret_cast<uint8_t *>(block->buf());
+  IOBufferBlock *block = udp_packet->getIOBlockChain();
+  const uint8_t *buf   = reinterpret_cast<uint8_t *>(block->buf());
+  uint64_t buf_len     = block->size();
 
+  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"),
@@ -477,6 +558,24 @@ QUICPacketHandlerOut::_recv_packet(int event, UDPPacket *udp_packet)
                 ats_ip_nptop(&udp_packet->to.sa, ipb_to, sizeof(ipb_to)), udp_packet->getPktLength());
   }
 
+  QUICConnectionId dcid;
+  if (!QUICInvariants::dcid(dcid, buf, buf_len)) {
+    QUICDebug("Ignore packet - payload is too small");
+    udp_packet->free();
+    return;
+  }
+
+  if (!QUICInvariants::is_long_header(buf) && dcid != this->_vc->connection_id()) {
+    auto connection = static_cast<QUICNetVConnection *>(this->_check_stateless_reset(buf, buf_len));
+    if (connection) {
+      if (connection->connection_id() == this->_vc->connection_id()) {
+        QUICDebug("Stateless Reset has been received");
+        this->_vc->thread->schedule_imm(this->_vc, QUIC_EVENT_STATELESS_RESET);
+      }
+      return;
+    }
+  }
+
   this->_vc->handle_received_packet(udp_packet);
   eventProcessor.schedule_imm(this->_vc, ET_CALL, QUIC_EVENT_PACKET_READ_READY, nullptr);
 }
diff --git a/iocore/net/quic/Makefile.am b/iocore/net/quic/Makefile.am
index 8acdb53..75c4daf 100644
--- a/iocore/net/quic/Makefile.am
+++ b/iocore/net/quic/Makefile.am
@@ -30,7 +30,7 @@ AM_CPPFLAGS += \
   -I$(abs_top_srcdir)/mgmt \
   -I$(abs_top_srcdir)/mgmt/utils \
   $(TS_INCLUDES) \
-  @OPENSSL_INCLUDES@
+  @OPENSSL_INCLUDES@ @YAMLCPP_INCLUDES@
 
 noinst_LIBRARIES = libquic.a
 
@@ -40,11 +40,20 @@ QUICPPProtector_impl = QUICPacketPayloadProtector_boringssl.cc
 QUICTLS_impl = QUICTLS_boringssl.cc
 QUICKeyGenerator_impl = QUICKeyGenerator_boringssl.cc
 else
+if ENABLE_QUIC_OLD_API
+QUICPHProtector_impl = QUICPacketHeaderProtector_legacy.cc
+QUICPPProtector_impl = QUICPacketPayloadProtector_legacy.cc
+QUICTLS_impl = QUICTLS_legacy.cc
+QUICKeyGenerator_impl = QUICKeyGenerator_legacy.cc
+else
 QUICPHProtector_impl = QUICPacketHeaderProtector_openssl.cc
 QUICPPProtector_impl = QUICPacketPayloadProtector_openssl.cc
 QUICTLS_impl = QUICTLS_openssl.cc
 QUICKeyGenerator_impl = QUICKeyGenerator_openssl.cc
 endif
+endif
+
+QLog_impl = qlog/QLogEvent.cc qlog/QLogFrame.cc qlog/QLog.cc
 
 libquic_a_SOURCES = \
   QUICGlobals.cc \
@@ -86,6 +95,8 @@ libquic_a_SOURCES = \
   QUICPathManager.cc \
   QUICPathValidator.cc \
   QUICPinger.cc \
+  QUICRetryIntegrityTag.cc \
+  QUICResetTokenTable.cc \
   QUICFrameGenerator.cc \
   QUICFrameRetransmitter.cc \
   QUICAddrVerifyState.cc \
@@ -95,7 +106,8 @@ libquic_a_SOURCES = \
   QUICStreamFactory.cc \
   QUICPadder.cc \
   QUICContext.cc \
-  QUICTokenCreator.cc
+  QUICTokenCreator.cc \
+  $(QLog_impl)
 
 #
 # Check Programs
diff --git a/iocore/net/quic/Mock.h b/iocore/net/quic/Mock.h
index 0c78e30..68e421a 100644
--- a/iocore/net/quic/Mock.h
+++ b/iocore/net/quic/Mock.h
@@ -29,11 +29,15 @@
 #include "QUICStreamManager.h"
 #include "QUICLossDetector.h"
 #include "QUICEvents.h"
+#include "QUICPacketProtectionKeyInfo.h"
+#include "QUICPinger.h"
+#include "QUICPadder.h"
+#include "QUICHandshakeProtocol.h"
 
 class MockQUICContext;
 
 using namespace std::literals;
-std::string_view negotiated_application_name_sv = "h3-23"sv;
+std::string_view negotiated_application_name_sv = "h3-27"sv;
 
 class MockQUICLDConfig : public QUICLDConfig
 {
@@ -95,6 +99,35 @@ class MockQUICCCConfig : public QUICCCConfig
   }
 };
 
+class MockQUICPathManager : public QUICPathManager
+{
+public:
+  virtual ~MockQUICPathManager() {}
+  virtual const QUICPath &
+  get_current_path()
+  {
+    return _path;
+  }
+  virtual const QUICPath &
+  get_verified_path()
+  {
+    return _path;
+  }
+  virtual void
+  open_new_path(const QUICPath &path, ink_hrtime timeout_in)
+  {
+    return;
+  }
+  virtual void
+  set_trusted_path(const QUICPath &path)
+  {
+    return;
+  }
+
+private:
+  QUICPath _path = {{}, {}};
+};
+
 class MockQUICConnectionInfoProvider : public QUICConnectionInfoProvider
 {
   QUICConnectionId
@@ -169,7 +202,7 @@ class MockQUICConnectionInfoProvider : public QUICConnectionInfoProvider
 class MockQUICStreamManager : public QUICStreamManager
 {
 public:
-  MockQUICStreamManager(QUICConnectionInfoProvider *info) : QUICStreamManager(info, nullptr, nullptr) {}
+  MockQUICStreamManager(QUICContext *context) : QUICStreamManager(context, nullptr) {}
 
   // Override
   virtual QUICConnectionErrorUPtr
@@ -349,7 +382,12 @@ public:
   }
 
   void
-  close(QUICConnectionErrorUPtr error) override
+  close_quic_connection(QUICConnectionErrorUPtr error) override
+  {
+  }
+
+  void
+  reset_quic_connection() override
   {
   }
 
@@ -362,7 +400,7 @@ public:
   QUICStreamManager *
   stream_manager() override
   {
-    return &_stream_manager;
+    return nullptr;
   }
 
   bool
@@ -397,9 +435,8 @@ public:
   int _transmit_count   = 0;
   int _retransmit_count = 0;
   Ptr<ProxyMutex> _mutex;
-  int _totalFrameCount                  = 0;
-  int _frameCount[256]                  = {0};
-  MockQUICStreamManager _stream_manager = {this};
+  int _totalFrameCount = 0;
+  int _frameCount[256] = {0};
 
   QUICTransportParametersInEncryptedExtensions dummy_transport_parameters();
   NetVConnectionContext_t _direction;
@@ -443,6 +480,21 @@ public:
   {
     return 0;
   }
+  virtual uint32_t
+  bytes_in_flight() const override
+  {
+    return 0;
+  }
+  virtual uint32_t
+  congestion_window() const override
+  {
+    return 0;
+  }
+  virtual uint32_t
+  current_ssthresh() const override
+  {
+    return 0;
+  }
 
   // for Test
   int
@@ -498,15 +550,16 @@ public:
   }
 };
 
-class MockQUICContext : public QUICContext, public QUICLDContext, public QUICCCContext
+class MockQUICContext : public QUICContext
 {
 public:
-  MockQUICContext()
+  MockQUICContext() : QUICContext()
   {
-    _info      = std::make_unique<MockQUICConnectionInfoProvider>();
-    _key_info  = std::make_unique<MockQUICPacketProtectionKeyInfo>();
-    _ld_config = std::make_unique<MockQUICLDConfig>();
-    _cc_config = std::make_unique<MockQUICCCConfig>();
+    _info         = std::make_unique<MockQUICConnectionInfoProvider>();
+    _key_info     = std::make_unique<MockQUICPacketProtectionKeyInfo>();
+    _path_manager = std::make_unique<MockQUICPathManager>();
+    _ld_config    = std::make_unique<MockQUICLDConfig>();
+    _cc_config    = std::make_unique<MockQUICCCConfig>();
   }
 
   virtual QUICConnectionInfoProvider *
@@ -543,6 +596,12 @@ public:
     return *_cc_config;
   }
 
+  virtual QUICPathManager *
+  path_manager() const override
+  {
+    return _path_manager.get();
+  }
+
 private:
   QUICConfig::scoped_config _config;
   QUICRTTMeasure _rtt_measure;
@@ -550,6 +609,7 @@ private:
   std::unique_ptr<QUICPacketProtectionKeyInfo> _key_info;
   std::unique_ptr<QUICLDConfig> _ld_config;
   std::unique_ptr<QUICCCConfig> _cc_config;
+  std::unique_ptr<MockQUICPathManager> _path_manager;
 };
 
 class MockQUICLossDetector : public QUICLossDetector
@@ -599,9 +659,29 @@ public:
   }
 };
 
-class MockQUICPacket : public QUICPacket
+class MockQUICPacketR : public QUICPacketR
 {
 public:
+  MockQUICPacketR() : QUICPacketR(nullptr, {}, {}) {}
+
+  QUICPacketType
+  type() const override
+  {
+    return QUICPacketType::PROTECTED;
+  }
+
+  QUICConnectionId
+  destination_cid() const override
+  {
+    return QUICConnectionId::ZERO();
+  }
+
+  QUICPacketNumber
+  packet_number() const override
+  {
+    return 0;
+  }
+
   const IpEndpoint &
   from() const override
   {
@@ -637,7 +717,7 @@ public:
   MockQUICHandshakeProtocol(QUICPacketProtectionKeyInfo &pp_key_info) : QUICHandshakeProtocol(pp_key_info) {}
 
   int
-  handshake(QUICHandshakeMsgs *out, const QUICHandshakeMsgs *in) override
+  handshake(QUICHandshakeMsgs **out, const QUICHandshakeMsgs *in) override
   {
     return true;
   }
diff --git a/iocore/net/quic/QUICAckFrameCreator.cc b/iocore/net/quic/QUICAckFrameCreator.cc
index 98ca898..43167da 100644
--- a/iocore/net/quic/QUICAckFrameCreator.cc
+++ b/iocore/net/quic/QUICAckFrameCreator.cc
@@ -187,11 +187,8 @@ QUICAckFrameManager::QUICAckFrameCreator::push_back(QUICPacketNumber packet_numb
     this->_should_send = true;
   }
 
-  // every 2 full-packet should send a ack frame like tcp
-  this->_size_unsend += size;
-  // FIXME: this size should be fixed with PMTU
-  if (this->_size_unsend > 2 * 1480) {
-    this->_size_unsend = 0;
+  // every 2 ack-eliciting packet should send a ack frame
+  if (!ack_only && ++this->_ack_eliciting_count % 2 == 0) {
     this->_should_send = true;
   }
 
@@ -224,7 +221,7 @@ QUICAckFrameManager::QUICAckFrameCreator::clear()
   this->_largest_ack_number          = 0;
   this->_largest_ack_received_time   = 0;
   this->_latest_packet_received_time = 0;
-  this->_size_unsend                 = 0;
+  this->_ack_eliciting_count         = 0;
   this->_should_send                 = false;
   this->_available                   = false;
 }
diff --git a/iocore/net/quic/QUICAckFrameCreator.h b/iocore/net/quic/QUICAckFrameCreator.h
index 58e28e8..42a7540 100644
--- a/iocore/net/quic/QUICAckFrameCreator.h
+++ b/iocore/net/quic/QUICAckFrameCreator.h
@@ -69,7 +69,7 @@ public:
     bool _available                         = false; // packet_number has data to sent
     bool _should_send                       = false; // ack frame should be sent immediately
     bool _has_new_data                      = false; // new data after last sent
-    size_t _size_unsend                     = 0;
+    uint32_t _ack_eliciting_count           = 0;     // every two ack-eliciting packet should send ack immediatly
     uint16_t _max_ack_delay                 = 25;
     QUICPacketNumber _largest_ack_number    = 0;
     QUICPacketNumber _expect_next           = 0;
diff --git a/iocore/net/quic/QUICAltConnectionManager.cc b/iocore/net/quic/QUICAltConnectionManager.cc
index 4e39e93..569581c 100644
--- a/iocore/net/quic/QUICAltConnectionManager.cc
+++ b/iocore/net/quic/QUICAltConnectionManager.cc
@@ -26,25 +26,28 @@
 #include "tscore/ink_defs.h"
 #include "QUICAltConnectionManager.h"
 #include "QUICConnectionTable.h"
+#include "QUICResetTokenTable.h"
 
 static constexpr char V_DEBUG_TAG[] = "v_quic_alt_con";
 
 #define QUICACMVDebug(fmt, ...) Debug(V_DEBUG_TAG, "[%s] " fmt, this->_qc->cids().data(), ##__VA_ARGS__)
 
-QUICAltConnectionManager::QUICAltConnectionManager(QUICConnection *qc, QUICConnectionTable &ctable,
+QUICAltConnectionManager::QUICAltConnectionManager(QUICConnection *qc, QUICConnectionTable &ctable, QUICResetTokenTable &rtable,
                                                    const QUICConnectionId &peer_initial_cid, uint32_t instance_id,
                                                    uint8_t local_active_cid_limit, const IpEndpoint *preferred_endpoint_ipv4,
                                                    const IpEndpoint *preferred_endpoint_ipv6)
-  : _qc(qc), _ctable(ctable), _instance_id(instance_id), _local_active_cid_limit(local_active_cid_limit)
+  : _qc(qc), _ctable(ctable), _rtable(rtable), _instance_id(instance_id), _local_active_cid_limit(local_active_cid_limit)
 {
   // Sequence number of the initial CID is 0
   this->_alt_quic_connection_ids_remote.push_back({0, peer_initial_cid, {}, {true}});
+  this->_alt_quic_connection_ids_local[0].seq_num    = 0;
+  this->_alt_quic_connection_ids_local[0].advertised = true;
 
   if ((preferred_endpoint_ipv4 && !ats_ip_addr_port_eq(*preferred_endpoint_ipv4, qc->five_tuple().source())) ||
       (preferred_endpoint_ipv6 && !ats_ip_addr_port_eq(*preferred_endpoint_ipv6, qc->five_tuple().source()))) {
-    this->_alt_quic_connection_ids_local[0] = this->_generate_next_alt_con_info();
+    this->_alt_quic_connection_ids_local[1] = this->_generate_next_alt_con_info();
     // This alt cid will be advertised via Transport Parameter, so no need to advertise it via NCID frame
-    this->_alt_quic_connection_ids_local[0].advertised = true;
+    this->_alt_quic_connection_ids_local[1].advertised = true;
 
     IpEndpoint empty_endpoint_ipv4;
     IpEndpoint empty_endpoint_ipv6;
@@ -59,8 +62,8 @@ QUICAltConnectionManager::QUICAltConnectionManager(QUICConnection *qc, QUICConne
 
     // FIXME Check nullptr dereference
     this->_local_preferred_address =
-      new QUICPreferredAddress(*preferred_endpoint_ipv4, *preferred_endpoint_ipv6, this->_alt_quic_connection_ids_local[0].id,
-                               this->_alt_quic_connection_ids_local[0].token);
+      new QUICPreferredAddress(*preferred_endpoint_ipv4, *preferred_endpoint_ipv6, this->_alt_quic_connection_ids_local[1].id,
+                               this->_alt_quic_connection_ids_local[1].token);
   }
 }
 
@@ -116,9 +119,7 @@ QUICAltConnectionManager::_generate_next_alt_con_info()
   }
 
   if (is_debug_tag_set(V_DEBUG_TAG)) {
-    char new_cid_str[QUICConnectionId::MAX_HEX_STR_LENGTH];
-    conn_id.hex(new_cid_str, QUICConnectionId::MAX_HEX_STR_LENGTH);
-    QUICACMVDebug("alt-cid=%s", new_cid_str);
+    QUICACMVDebug("alt-cid=%s", conn_id.hex().c_str());
   }
 
   return aci;
@@ -127,7 +128,7 @@ QUICAltConnectionManager::_generate_next_alt_con_info()
 void
 QUICAltConnectionManager::_init_alt_connection_ids()
 {
-  for (int i = 0; i < this->_remote_active_cid_limit; ++i) {
+  for (int i = this->_alt_quic_connection_id_seq_num + 1; i < this->_remote_active_cid_limit; ++i) {
     this->_alt_quic_connection_ids_local[i] = this->_generate_next_alt_con_info();
   }
   this->_need_advertise = true;
@@ -136,16 +137,6 @@ QUICAltConnectionManager::_init_alt_connection_ids()
 bool
 QUICAltConnectionManager::_update_alt_connection_id(uint64_t chosen_seq_num)
 {
-  // Seq 0 is special so it's not in the array
-  if (chosen_seq_num == 0) {
-    return true;
-  }
-
-  // Seq 1 is for Preferred Address
-  if (chosen_seq_num == 1) {
-    return true;
-  }
-
   for (int i = 0; i < this->_remote_active_cid_limit; ++i) {
     if (this->_alt_quic_connection_ids_local[i].seq_num == chosen_seq_num) {
       this->_alt_quic_connection_ids_local[i] = this->_generate_next_alt_con_info();
@@ -166,10 +157,17 @@ QUICAltConnectionManager::_register_remote_connection_id(const QUICNewConnection
     error = std::make_unique<QUICConnectionError>(QUICTransErrorCode::PROTOCOL_VIOLATION, "received zero-length cid",
                                                   QUICFrameType::NEW_CONNECTION_ID);
   } else {
-    int unused = std::count_if(this->_alt_quic_connection_ids_remote.begin(), this->_alt_quic_connection_ids_remote.end(),
-                               [](AltConnectionInfo info) { return info.used == false && info.seq_num != 1; });
+    int unused = 0;
+    for (auto &&x : this->_alt_quic_connection_ids_remote) {
+      if (x.seq_num == frame.sequence()) {
+        return error;
+      }
+      if (x.used == false && x.seq_num != 1) {
+        ++unused;
+      }
+    }
     if (unused > this->_local_active_cid_limit) {
-      error = std::make_unique<QUICConnectionError>(QUICTransErrorCode::PROTOCOL_VIOLATION, "received too many alt CIDs",
+      error = std::make_unique<QUICConnectionError>(QUICTransErrorCode::CONNECTION_ID_LIMIT_ERROR, "received too many alt CIDs",
                                                     QUICFrameType::NEW_CONNECTION_ID);
     } else {
       this->_alt_quic_connection_ids_remote.push_back(
@@ -215,6 +213,7 @@ QUICAltConnectionManager::migrate_to_alt_cid()
       continue;
     }
     info.used = true;
+    this->_rtable.insert(info.token, this->_qc);
     return info.id;
   }
 
@@ -250,6 +249,7 @@ QUICAltConnectionManager::drop_cid(const QUICConnectionId &cid)
     if (it->id == cid) {
       QUICACMVDebug("Dropping advertized CID %" PRIx32 " seq# %" PRIu64, it->id.h32(), it->seq_num);
       this->_retired_seq_nums.push(it->seq_num);
+      this->_rtable.erase(it->token);
       this->_alt_quic_connection_ids_remote.erase(it);
       return;
     }
diff --git a/iocore/net/quic/QUICAltConnectionManager.h b/iocore/net/quic/QUICAltConnectionManager.h
index 09b99ca..f92e90e 100644
--- a/iocore/net/quic/QUICAltConnectionManager.h
+++ b/iocore/net/quic/QUICAltConnectionManager.h
@@ -31,12 +31,14 @@
 #include "QUICConnection.h"
 
 class QUICConnectionTable;
+class QUICResetTokenTable;
 
 class QUICAltConnectionManager : public QUICFrameHandler, public QUICFrameGenerator
 {
 public:
-  QUICAltConnectionManager(QUICConnection *qc, QUICConnectionTable &ctable, const QUICConnectionId &peer_initial_cid,
-                           uint32_t instance_id, uint8_t active_cid_limit, const IpEndpoint *preferred_endpoint_ipv4 = nullptr,
+  QUICAltConnectionManager(QUICConnection *qc, QUICConnectionTable &ctable, QUICResetTokenTable &rtable,
+                           const QUICConnectionId &peer_initial_cid, uint32_t instance_id, uint8_t active_cid_limit,
+                           const IpEndpoint *preferred_endpoint_ipv4 = nullptr,
                            const IpEndpoint *preferred_endpoint_ipv6 = nullptr);
   ~QUICAltConnectionManager();
 
@@ -95,6 +97,7 @@ private:
 
   QUICConnection *_qc = nullptr;
   QUICConnectionTable &_ctable;
+  QUICResetTokenTable &_rtable;
   AltConnectionInfo _alt_quic_connection_ids_local[8]; // 8 is perhaps enough
   std::vector<AltConnectionInfo> _alt_quic_connection_ids_remote;
   std::queue<uint64_t> _retired_seq_nums;
diff --git a/iocore/net/quic/QUICConfig.cc b/iocore/net/quic/QUICConfig.cc
index 1cfe703..cc44d00 100644
--- a/iocore/net/quic/QUICConfig.cc
+++ b/iocore/net/quic/QUICConfig.cc
@@ -49,10 +49,6 @@ quic_new_ssl_ctx()
 
   SSL_CTX_set_max_early_data(ssl_ctx, UINT32_C(0xFFFFFFFF));
 
-  SSL_CTX_add_custom_ext(ssl_ctx, QUICTransportParametersHandler::TRANSPORT_PARAMETER_ID,
-                         SSL_EXT_TLS_ONLY | SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS,
-                         &QUICTransportParametersHandler::add, &QUICTransportParametersHandler::free, nullptr,
-                         &QUICTransportParametersHandler::parse, nullptr);
 #else
   // QUIC Transport Parameters are accesible with SSL_set_quic_transport_params and SSL_get_peer_quic_transport_params
 #endif
@@ -61,6 +57,11 @@ quic_new_ssl_ctx()
   // tatsuhiro-t's custom OpenSSL for QUIC draft-13
   // https://github.com/tatsuhiro-t/openssl/tree/quic-draft-13
   SSL_CTX_set_mode(ssl_ctx, SSL_MODE_QUIC_HACK);
+  SSL_CTX_add_custom_ext(ssl_ctx, QUICTransportParametersHandler::TRANSPORT_PARAMETER_ID,
+                         SSL_EXT_TLS_ONLY | SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS,
+                         &QUICTransportParametersHandler::add, &QUICTransportParametersHandler::free, nullptr,
+                         &QUICTransportParametersHandler::parse, nullptr);
+
 #endif
 
   return ssl_ctx;
@@ -120,11 +121,16 @@ QUICConfigParams::initialize()
   REC_EstablishStaticConfigInt32U(this->_stateless_retry, "proxy.config.quic.server.stateless_retry_enabled");
   REC_EstablishStaticConfigInt32U(this->_vn_exercise_enabled, "proxy.config.quic.client.vn_exercise_enabled");
   REC_EstablishStaticConfigInt32U(this->_cm_exercise_enabled, "proxy.config.quic.client.cm_exercise_enabled");
+  REC_EstablishStaticConfigInt32U(this->_quantum_readiness_test_enabled_out,
+                                  "proxy.config.quic.client.quantum_readiness_test_enabled");
+  REC_EstablishStaticConfigInt32U(this->_quantum_readiness_test_enabled_in,
+                                  "proxy.config.quic.server.quantum_readiness_test_enabled");
 
   REC_ReadConfigStringAlloc(this->_server_supported_groups, "proxy.config.quic.server.supported_groups");
   REC_ReadConfigStringAlloc(this->_client_supported_groups, "proxy.config.quic.client.supported_groups");
   REC_ReadConfigStringAlloc(this->_client_session_file, "proxy.config.quic.client.session_file");
   REC_ReadConfigStringAlloc(this->_client_keylog_file, "proxy.config.quic.client.keylog_file");
+  REC_ReadConfigStringAlloc(this->_qlog_dir, "proxy.config.quic.qlog_dir");
 
   // Transport Parameters
   REC_EstablishStaticConfigInt32U(this->_no_activity_timeout_in, "proxy.config.quic.no_activity_timeout_in");
@@ -159,6 +165,7 @@ QUICConfigParams::initialize()
   REC_EstablishStaticConfigInt32U(this->_max_ack_delay_out, "proxy.config.quic.max_ack_delay_out");
   REC_EstablishStaticConfigInt32U(this->_active_cid_limit_in, "proxy.config.quic.active_cid_limit_in");
   REC_EstablishStaticConfigInt32U(this->_active_cid_limit_out, "proxy.config.quic.active_cid_limit_out");
+  REC_EstablishStaticConfigInt32U(this->_disable_active_migration, "proxy.config.quic.disable_active_migration");
 
   // Loss Detection
   REC_EstablishStaticConfigInt32U(this->_ld_packet_threshold, "proxy.config.quic.loss_detection.packet_threshold");
@@ -245,6 +252,18 @@ QUICConfigParams::cm_exercise_enabled() const
 }
 
 uint32_t
+QUICConfigParams::quantum_readiness_test_enabled_in() const
+{
+  return this->_quantum_readiness_test_enabled_in;
+}
+
+uint32_t
+QUICConfigParams::quantum_readiness_test_enabled_out() const
+{
+  return this->_quantum_readiness_test_enabled_out;
+}
+
+uint32_t
 QUICConfigParams::initial_max_data_in() const
 {
   return this->_initial_max_data_in;
@@ -352,6 +371,12 @@ QUICConfigParams::active_cid_limit_out() const
   return this->_active_cid_limit_out;
 }
 
+bool
+QUICConfigParams::disable_active_migration() const
+{
+  return this->_disable_active_migration;
+}
+
 const char *
 QUICConfigParams::server_supported_groups() const
 {
@@ -447,6 +472,12 @@ QUICConfigParams::client_keylog_file() const
   return this->_client_keylog_file;
 }
 
+const char *
+QUICConfigParams::qlog_dir() const
+{
+  return this->_qlog_dir;
+}
+
 //
 // QUICConfig
 //
diff --git a/iocore/net/quic/QUICConfig.h b/iocore/net/quic/QUICConfig.h
index 67106e6..38a655a 100644
--- a/iocore/net/quic/QUICConfig.h
+++ b/iocore/net/quic/QUICConfig.h
@@ -40,11 +40,14 @@ public:
   uint32_t stateless_retry() const;
   uint32_t vn_exercise_enabled() const;
   uint32_t cm_exercise_enabled() const;
+  uint32_t quantum_readiness_test_enabled_in() const;
+  uint32_t quantum_readiness_test_enabled_out() const;
 
   const char *server_supported_groups() const;
   const char *client_supported_groups() const;
   const char *client_session_file() const;
   const char *client_keylog_file() const;
+  const char *qlog_dir() const;
 
   shared_SSL_CTX client_ssl_ctx() const;
 
@@ -71,6 +74,7 @@ public:
   uint8_t max_ack_delay_out() const;
   uint8_t active_cid_limit_in() const;
   uint8_t active_cid_limit_out() const;
+  bool disable_active_migration() const;
 
   // Loss Detection
   uint32_t ld_packet_threshold() const;
@@ -93,15 +97,18 @@ private:
   // TODO: make configurable
   static const uint8_t _scid_len = 18; //< Length of Source Connection ID
 
-  uint32_t _instance_id         = 0;
-  uint32_t _stateless_retry     = 0;
-  uint32_t _vn_exercise_enabled = 0;
-  uint32_t _cm_exercise_enabled = 0;
+  uint32_t _instance_id                        = 0;
+  uint32_t _stateless_retry                    = 0;
+  uint32_t _vn_exercise_enabled                = 0;
+  uint32_t _cm_exercise_enabled                = 0;
+  uint32_t _quantum_readiness_test_enabled_in  = 0;
+  uint32_t _quantum_readiness_test_enabled_out = 0;
 
   char *_server_supported_groups = nullptr;
   char *_client_supported_groups = nullptr;
   char *_client_session_file     = nullptr;
   char *_client_keylog_file      = nullptr;
+  char *_qlog_dir                = nullptr;
 
   shared_SSL_CTX _client_ssl_ctx = nullptr;
 
@@ -130,6 +137,7 @@ private:
   uint32_t _max_ack_delay_out                       = 0;
   uint32_t _active_cid_limit_in                     = 0;
   uint32_t _active_cid_limit_out                    = 0;
+  uint32_t _disable_active_migration                = 0;
 
   // [draft-17 recovery] 6.4.1.  Constants of interest
   uint32_t _ld_packet_threshold = 3;
diff --git a/iocore/net/quic/QUICCongestionController.h b/iocore/net/quic/QUICCongestionController.h
index 46341dc..5afa8f0 100644
--- a/iocore/net/quic/QUICCongestionController.h
+++ b/iocore/net/quic/QUICCongestionController.h
@@ -23,6 +23,8 @@
 
 #pragma once
 
+#include "QUICFrame.h"
+
 struct QUICPacketInfo {
   // 6.3.1.  Sent Packet Fields
   QUICPacketNumber packet_number;
@@ -42,6 +44,13 @@ struct QUICPacketInfo {
 class QUICCongestionController
 {
 public:
+  enum class State : uint8_t {
+    RECOVERY,
+    CONGESTION_AVOIDANCE,
+    SLOW_START,
+    APPPLICATION_LIMITED,
+  };
+
   virtual ~QUICCongestionController() {}
   virtual void on_packet_sent(size_t bytes_sent)                                                                    = 0;
   virtual void on_packet_acked(const QUICPacketInfo &acked_packet)                                                  = 0;
@@ -50,4 +59,8 @@ public:
   virtual void add_extra_credit()                                                                                   = 0;
   virtual void reset()                                                                                              = 0;
   virtual uint32_t credit() 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 28c5a83..ed75dc6 100644
--- a/iocore/net/quic/QUICConnection.h
+++ b/iocore/net/quic/QUICConnection.h
@@ -53,8 +53,9 @@ public:
 class QUICConnection : public QUICFrameHandler, public QUICConnectionInfoProvider
 {
 public:
-  virtual QUICStreamManager *stream_manager()             = 0;
-  virtual void close(QUICConnectionErrorUPtr error)       = 0;
-  virtual void handle_received_packet(UDPPacket *packeet) = 0;
-  virtual void ping()                                     = 0;
+  virtual QUICStreamManager *stream_manager()                       = 0;
+  virtual void close_quic_connection(QUICConnectionErrorUPtr error) = 0;
+  virtual void reset_quic_connection()                              = 0;
+  virtual void handle_received_packet(UDPPacket *packeet)           = 0;
+  virtual void ping()                                               = 0;
 };
diff --git a/iocore/net/quic/QUICContext.cc b/iocore/net/quic/QUICContext.cc
index 7ccb9e4..4d0d501 100644
--- a/iocore/net/quic/QUICContext.cc
+++ b/iocore/net/quic/QUICContext.cc
@@ -100,48 +100,55 @@ private:
   const QUICConfigParams *_params;
 };
 
-QUICContextImpl::QUICContextImpl(QUICRTTProvider *rtt, QUICConnectionInfoProvider *info,
-                                 QUICPacketProtectionKeyInfoProvider *key_info)
+QUICContext::QUICContext(QUICRTTProvider *rtt, QUICConnectionInfoProvider *info, QUICPacketProtectionKeyInfoProvider *key_info,
+                         QUICPathManager *path_manager)
   : _key_info(key_info),
     _connection_info(info),
     _rtt_provider(rtt),
+    _path_manager(path_manager),
     _ld_config(std::make_unique<QUICLDConfigQCP>(_config)),
     _cc_config(std::make_unique<QUICCCConfigQCP>(_config))
 {
 }
 
 QUICConnectionInfoProvider *
-QUICContextImpl::connection_info() const
+QUICContext::connection_info() const
 {
   return _connection_info;
 }
 
 QUICConfig::scoped_config
-QUICContextImpl::config() const
+QUICContext::config() const
 {
   return _config;
 }
 
 QUICPacketProtectionKeyInfoProvider *
-QUICContextImpl::key_info() const
+QUICContext::key_info() const
 {
   return _key_info;
 }
 
 QUICRTTProvider *
-QUICContextImpl::rtt_provider() const
+QUICContext::rtt_provider() const
 {
   return _rtt_provider;
 }
 
 QUICLDConfig &
-QUICContextImpl::ld_config() const
+QUICContext::ld_config() const
 {
   return *_ld_config;
 }
 
 QUICCCConfig &
-QUICContextImpl::cc_config() const
+QUICContext::cc_config() const
 {
   return *_cc_config;
 }
+
+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 57e6c0b..31e494c 100644
--- a/iocore/net/quic/QUICContext.h
+++ b/iocore/net/quic/QUICContext.h
@@ -25,60 +25,170 @@
 
 #include "QUICConnection.h"
 #include "QUICConfig.h"
+#include "QUICEvents.h"
+#include "QUICCongestionController.h"
 
 class QUICRTTProvider;
 class QUICCongestionController;
 class QUICPacketProtectionKeyInfoProvider;
+class QUICPathManager;
+class QUICPacketR;
+class QUICPacket;
 
 class QUICNetVConnection;
+struct QUICPacketInfo;
 
-class QUICContext
-{
-public:
-  virtual ~QUICContext(){};
-  virtual QUICConnectionInfoProvider *connection_info() const = 0;
-  virtual QUICConfig::scoped_config config() const            = 0;
-};
-
-class QUICLDContext
+// this class is a connection between the callbacks. it should do something
+// TODO: it should do something
+class QUICCallbackContext
 {
-public:
-  virtual ~QUICLDContext() {}
-  virtual QUICConnectionInfoProvider *connection_info() const   = 0;
-  virtual QUICLDConfig &ld_config() const                       = 0;
-  virtual QUICPacketProtectionKeyInfoProvider *key_info() const = 0;
 };
 
-class QUICCCContext
+class QUICCallback
 {
 public:
-  virtual ~QUICCCContext() {}
-  virtual QUICConnectionInfoProvider *connection_info() const = 0;
-  virtual QUICCCConfig &cc_config() const                     = 0;
-  virtual QUICRTTProvider *rtt_provider() const               = 0;
+  virtual ~QUICCallback() {}
+
+  // callback on connection close event
+  virtual void connection_close_callback(QUICCallbackContext &){};
+  // 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){};
+  // callback on packet receive event
+  virtual void packet_recv_callback(QUICCallbackContext &, const QUICPacket &p){};
+  // callback on packet acked event
+  virtual void cc_metrics_update_callback(QUICCallbackContext &, uint64_t congestion_window, uint64_t bytes_in_flight,
+                                          uint64_t sshresh){};
+  // callback on packet receive event
+  virtual void frame_packetize_callback(QUICCallbackContext &, const QUICFrame &p){};
+  // callback on packet receive event
+  virtual void frame_recv_callback(QUICCallbackContext &, const QUICFrame &p){};
+  // callback on packet receive event
+  virtual void congestion_state_updated_callback(QUICCallbackContext &, QUICCongestionController::State p){};
 };
 
-class QUICContextImpl : public QUICContext, public QUICCCContext, public QUICLDContext
+class QUICContext
 {
 public:
-  QUICContextImpl(QUICRTTProvider *rtt, QUICConnectionInfoProvider *info, QUICPacketProtectionKeyInfoProvider *key_info);
-
-  virtual QUICConnectionInfoProvider *connection_info() const override;
-  virtual QUICConfig::scoped_config config() const override;
-  virtual QUICRTTProvider *rtt_provider() const override;
+  QUICContext(QUICRTTProvider *rtt, QUICConnectionInfoProvider *info, QUICPacketProtectionKeyInfoProvider *key_info,
+              QUICPathManager *path_manager);
 
-  // TODO should be more abstract
-  virtual QUICPacketProtectionKeyInfoProvider *key_info() const override;
-
-  virtual QUICLDConfig &ld_config() const override;
-  virtual QUICCCConfig &cc_config() const override;
+  virtual ~QUICContext(){};
+  virtual QUICConnectionInfoProvider *connection_info() const;
+  virtual QUICConfig::scoped_config config() const;
+  virtual QUICLDConfig &ld_config() const;
+  virtual QUICPacketProtectionKeyInfoProvider *key_info() const;
+  virtual QUICCCConfig &cc_config() const;
+  virtual QUICRTTProvider *rtt_provider() const;
+  virtual QUICPathManager *path_manager() const;
+
+  // regist a callback which will be called when specifed event happen.
+  void
+  regist_callback(std::shared_ptr<QUICCallback> cbs)
+  {
+    this->_callbacks.push_back(cbs);
+  }
+
+  enum class CallbackEvent : uint8_t {
+    PACKET_LOST,
+    PACKET_SEND,
+    FRAME_PACKETIZE,
+    PACKET_RECV,
+    FRAME_RECV,
+    METRICS_UPDATE,
+    CONNECTION_CLOSE,
+    CONGESTION_STATE_CHANGED,
+  };
+
+  // FIXME stupid trigger should be fix in more smart way.
+  void
+  trigger(CallbackEvent e, const QUICPacket *p = nullptr)
+  {
+    QUICCallbackContext ctx;
+    switch (e) {
+    case CallbackEvent::PACKET_RECV:
+      for (auto &&it : this->_callbacks) {
+        it->packet_recv_callback(ctx, *p);
+      }
+      break;
+    case CallbackEvent::PACKET_SEND:
+      for (auto &&it : this->_callbacks) {
+        it->packet_send_callback(ctx, *p);
+      }
+      break;
+    case CallbackEvent::CONNECTION_CLOSE:
+      for (auto &&it : this->_callbacks) {
+        it->connection_close_callback(ctx);
+      }
+      break;
+    default:
+      break;
+    }
+  }
+
+  void
+  trigger(CallbackEvent e, const QUICPacketInfo &p)
+  {
+    QUICCallbackContext ctx;
+    for (auto &&it : this->_callbacks) {
+      it->packet_lost_callback(ctx, p);
+    }
+  }
+
+  void
+  trigger(CallbackEvent e, uint64_t congestion_window, uint64_t bytes_in_flight, uint64_t sshresh)
+  {
+    QUICCallbackContext ctx;
+    for (auto &&it : this->_callbacks) {
+      it->cc_metrics_update_callback(ctx, congestion_window, bytes_in_flight, sshresh);
+    }
+  }
+
+  void
+  trigger(CallbackEvent e, QUICCongestionController::State state)
+  {
+    QUICCallbackContext ctx;
+    for (auto &&it : this->_callbacks) {
+      it->congestion_state_updated_callback(ctx, state);
+    }
+  }
+
+  void
+  trigger(CallbackEvent e, const QUICFrame &frame)
+  {
+    QUICCallbackContext ctx;
+    switch (e) {
+    case CallbackEvent::FRAME_PACKETIZE:
+
+      for (auto &&it : this->_callbacks) {
+        it->frame_packetize_callback(ctx, frame);
+      }
+      break;
+
+    case CallbackEvent::FRAME_RECV:
+      for (auto &&it : this->_callbacks) {
+        it->frame_recv_callback(ctx, frame);
+      }
+      break;
+    default:
+      break;
+    }
+  }
+
+protected:
+  // For Mock
+  QUICContext() {}
 
 private:
   QUICConfig::scoped_config _config;
   QUICPacketProtectionKeyInfoProvider *_key_info = nullptr;
   QUICConnectionInfoProvider *_connection_info   = nullptr;
   QUICRTTProvider *_rtt_provider                 = nullptr;
+  QUICPathManager *_path_manager                 = nullptr;
 
   std::unique_ptr<QUICLDConfig> _ld_config = nullptr;
   std::unique_ptr<QUICCCConfig> _cc_config = nullptr;
+
+  std::vector<std::shared_ptr<QUICCallback>> _callbacks;
 };
diff --git a/iocore/net/quic/QUICDebugNames.cc b/iocore/net/quic/QUICDebugNames.cc
index bf9d2d8..6a75032 100644
--- a/iocore/net/quic/QUICDebugNames.cc
+++ b/iocore/net/quic/QUICDebugNames.cc
@@ -90,6 +90,8 @@ QUICDebugNames::frame_type(QUICFrameType type)
     return "RETIRE_CONNECTION_ID";
   case QUICFrameType::NEW_TOKEN:
     return "NEW_TOKEN";
+  case QUICFrameType::HANDSHAKE_DONE:
+    return "HANDSHAKE_DONE";
   case QUICFrameType::UNKNOWN:
   default:
     return "UNKNOWN";
@@ -131,8 +133,12 @@ QUICDebugNames::error_code(uint16_t code)
     return "FRAME_ENCODING_ERROR";
   case static_cast<uint16_t>(QUICTransErrorCode::TRANSPORT_PARAMETER_ERROR):
     return "TRANSPORT_PARAMETER_ERROR";
+  case static_cast<uint16_t>(QUICTransErrorCode::CONNECTION_ID_LIMIT_ERROR):
+    return "CONNECTION_ID_LIMIT_ERROR";
   case static_cast<uint16_t>(QUICTransErrorCode::PROTOCOL_VIOLATION):
     return "PROTOCOL_VIOLATION";
+  case static_cast<uint16_t>(QUICTransErrorCode::INVALID_TOKEN):
+    return "INVALID_TOKEN";
   case static_cast<uint16_t>(QUICTransErrorCode::CRYPTO_BUFFER_EXCEEDED):
     return "CRYPTO_BUFFER_EXCEEDED";
   default:
@@ -179,8 +185,8 @@ QUICDebugNames::transport_parameter_id(QUICTransportParameterId id)
     return "INITIAL_MAX_DATA";
   case QUICTransportParameterId::INITIAL_MAX_STREAMS_BIDI:
     return "INITIAL_MAX_STREAMS_BIDI";
-  case QUICTransportParameterId::IDLE_TIMEOUT:
-    return "IDLE_TIMEOUT";
+  case QUICTransportParameterId::MAX_IDLE_TIMEOUT:
+    return "MAX_IDLE_TIMEOUT";
   case QUICTransportParameterId::PREFERRED_ADDRESS:
     return "PREFERRED_ADDRESS";
   case QUICTransportParameterId::MAX_PACKET_SIZE:
diff --git a/iocore/net/quic/QUICEvents.h b/iocore/net/quic/QUICEvents.h
index c758249..50362e8 100644
--- a/iocore/net/quic/QUICEvents.h
+++ b/iocore/net/quic/QUICEvents.h
@@ -35,4 +35,5 @@ enum {
   QUIC_EVENT_ACK_PERIODIC,
   QUIC_EVENT_SHUTDOWN,
   QUIC_EVENT_LD_SHUTDOWN,
+  QUIC_EVENT_STATELESS_RESET,
 };
diff --git a/iocore/net/quic/QUICFlowController.cc b/iocore/net/quic/QUICFlowController.cc
index b5275d0..60e665c 100644
--- a/iocore/net/quic/QUICFlowController.cc
+++ b/iocore/net/quic/QUICFlowController.cc
@@ -120,8 +120,17 @@ QUICFlowController::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint
 
   if (this->_should_create_frame) {
     frame = this->_create_frame(buf);
-    if (frame && frame->size() <= maximum_frame_size) {
-      this->_should_create_frame = false;
+    if (frame) {
+      if (frame->size() <= maximum_frame_size) {
+        this->_should_create_frame                    = false;
+        QUICFrameInformationUPtr info                 = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
+        info->type                                    = frame->type();
+        info->level                                   = QUICEncryptionLevel::NONE;
+        *(reinterpret_cast<QUICOffset *>(info->data)) = this->_limit;
+        this->_records_frame(frame->id(), std::move(info));
+      } else {
+        frame = nullptr;
+      }
     }
   }
 
@@ -223,48 +232,23 @@ QUICLocalFlowController::_need_to_forward_limit()
 QUICFrame *
 QUICRemoteConnectionFlowController::_create_frame(uint8_t *buf)
 {
-  auto frame                    = QUICFrameFactory::create_data_blocked_frame(buf, this->_offset, this->_issue_frame_id(), this);
-  QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
-  info->type                    = frame->type();
-  info->level                   = QUICEncryptionLevel::NONE;
-  *(reinterpret_cast<QUICOffset *>(info->data)) = this->_offset;
-  this->_records_frame(frame->id(), std::move(info));
-  return frame;
+  return QUICFrameFactory::create_data_blocked_frame(buf, this->_offset, this->_issue_frame_id(), this);
 }
 
 QUICFrame *
 QUICLocalConnectionFlowController::_create_frame(uint8_t *buf)
 {
-  auto frame                    = QUICFrameFactory::create_max_data_frame(buf, this->_limit, this->_issue_frame_id(), this);
-  QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
-  info->type                    = frame->type();
-  info->level                   = QUICEncryptionLevel::NONE;
-  *(reinterpret_cast<QUICOffset *>(info->data)) = this->_limit;
-  this->_records_frame(frame->id(), std::move(info));
-  return frame;
+  return QUICFrameFactory::create_max_data_frame(buf, this->_limit, this->_issue_frame_id(), this);
 }
 
 QUICFrame *
 QUICRemoteStreamFlowController::_create_frame(uint8_t *buf)
 {
-  auto frame =
-    QUICFrameFactory::create_stream_data_blocked_frame(buf, this->_stream_id, this->_offset, this->_issue_frame_id(), this);
-  QUICFrameInformationUPtr info                 = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
-  info->type                                    = frame->type();
-  info->level                                   = QUICEncryptionLevel::NONE;
-  *(reinterpret_cast<QUICOffset *>(info->data)) = this->_offset;
-  this->_records_frame(frame->id(), std::move(info));
-  return frame;
+  return QUICFrameFactory::create_stream_data_blocked_frame(buf, this->_stream_id, this->_offset, this->_issue_frame_id(), this);
 }
 
 QUICFrame *
 QUICLocalStreamFlowController::_create_frame(uint8_t *buf)
 {
-  auto frame = QUICFrameFactory::create_max_stream_data_frame(buf, this->_stream_id, this->_limit, this->_issue_frame_id(), this);
-  QUICFrameInformationUPtr info                 = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
-  info->type                                    = frame->type();
-  info->level                                   = QUICEncryptionLevel::NONE;
-  *(reinterpret_cast<QUICOffset *>(info->data)) = this->_limit;
-  this->_records_frame(frame->id(), std::move(info));
-  return frame;
+  return QUICFrameFactory::create_max_stream_data_frame(buf, this->_stream_id, this->_limit, this->_issue_frame_id(), this);
 }
diff --git a/iocore/net/quic/QUICFrame.cc b/iocore/net/quic/QUICFrame.cc
index 68f03e3..1bffae3 100644
--- a/iocore/net/quic/QUICFrame.cc
+++ b/iocore/net/quic/QUICFrame.cc
@@ -46,7 +46,7 @@ read_varint(uint8_t *&pos, size_t len, uint64_t &field, size_t &field_len)
     return false;
   }
 
-  field = QUICIntUtil::read_QUICVariableInt(pos);
+  field = QUICIntUtil::read_QUICVariableInt(pos, len);
   pos += field_len;
   return true;
 }
@@ -63,10 +63,10 @@ QUICFrame::ack_eliciting() const
 {
   auto type = this->type();
 
-  return type != QUICFrameType::PADDING && type != QUICFrameType::ACK;
+  return type != QUICFrameType::PADDING && type != QUICFrameType::ACK && type != QUICFrameType::CONNECTION_CLOSE;
 }
 
-const QUICPacket *
+const QUICPacketR *
 QUICFrame::packet() const
 {
   return this->_packet;
@@ -112,7 +112,7 @@ QUICFrame::type(const uint8_t *buf)
              buf[0] < static_cast<uint8_t>(QUICFrameType::NEW_CONNECTION_ID)) {
     return QUICFrameType::STREAMS_BLOCKED;
   } else if (static_cast<uint8_t>(QUICFrameType::CONNECTION_CLOSE) <= buf[0] &&
-             buf[0] < static_cast<uint8_t>(QUICFrameType::UNKNOWN)) {
+             buf[0] < static_cast<uint8_t>(QUICFrameType::HANDSHAKE_DONE)) {
     return QUICFrameType::CONNECTION_CLOSE;
   } else {
     return static_cast<QUICFrameType>(buf[0]);
@@ -147,7 +147,7 @@ QUICStreamFrame::QUICStreamFrame(Ptr<IOBufferBlock> &block, QUICStreamId stream_
 {
 }
 
-QUICStreamFrame::QUICStreamFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
+QUICStreamFrame::QUICStreamFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet) : QUICFrame(0, nullptr, packet)
 {
   this->parse(buf, len, packet);
 }
@@ -164,7 +164,7 @@ QUICStreamFrame::QUICStreamFrame(const QUICStreamFrame &o)
 }
 
 void
-QUICStreamFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICStreamFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
@@ -392,7 +392,7 @@ QUICCryptoFrame::QUICCryptoFrame(Ptr<IOBufferBlock> &block, QUICOffset offset, Q
 {
 }
 
-QUICCryptoFrame::QUICCryptoFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
+QUICCryptoFrame::QUICCryptoFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet) : QUICFrame(0, nullptr, packet)
 {
   this->parse(buf, len, packet);
 }
@@ -403,7 +403,7 @@ QUICCryptoFrame::QUICCryptoFrame(const QUICCryptoFrame &o)
 }
 
 void
-QUICCryptoFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICCryptoFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
@@ -541,13 +541,29 @@ QUICCryptoFrame::data() const
 // ACK frame
 //
 
-QUICAckFrame::QUICAckFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
+std::set<QUICAckFrame::PacketNumberRange>
+QUICAckFrame::ranges() const
+{
+  std::set<QUICAckFrame::PacketNumberRange> numbers;
+  QUICPacketNumber x = this->largest_acknowledged();
+  numbers.insert({x, static_cast<uint64_t>(x) - this->ack_block_section()->first_ack_block()});
+  x -= this->ack_block_section()->first_ack_block() + 1;
+  for (auto &&block : *(this->ack_block_section())) {
+    x -= block.gap() + 1;
+    numbers.insert({x, static_cast<uint64_t>(x) - block.length()});
+    x -= block.length() + 1;
+  }
+
+  return numbers;
+}
+
+QUICAckFrame::QUICAckFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet) : QUICFrame(0, nullptr, packet)
 {
   this->parse(buf, len, packet);
 }
 
 void
-QUICAckFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICAckFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
@@ -997,13 +1013,13 @@ QUICRstStreamFrame::QUICRstStreamFrame(QUICStreamId stream_id, QUICAppErrorCode
 {
 }
 
-QUICRstStreamFrame::QUICRstStreamFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
+QUICRstStreamFrame::QUICRstStreamFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet) : QUICFrame(0, nullptr, packet)
 {
   this->parse(buf, len, packet);
 }
 
 void
-QUICRstStreamFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICRstStreamFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
@@ -1128,13 +1144,13 @@ QUICRstStreamFrame::final_offset() const
 // PING frame
 //
 
-QUICPingFrame::QUICPingFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
+QUICPingFrame::QUICPingFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet) : QUICFrame(0, nullptr, packet)
 {
   this->parse(buf, len, packet);
 }
 
 void
-QUICPingFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICPingFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   this->_reset();
   this->_packet = packet;
@@ -1179,13 +1195,13 @@ QUICPingFrame::to_io_buffer_block(size_t limit) const
 //
 // PADDING frame
 //
-QUICPaddingFrame::QUICPaddingFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
+QUICPaddingFrame::QUICPaddingFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet) : QUICFrame(0, nullptr, packet)
 {
   this->parse(buf, len, packet);
 }
 
 void
-QUICPaddingFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICPaddingFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
@@ -1265,7 +1281,7 @@ QUICConnectionCloseFrame::QUICConnectionCloseFrame(uint64_t error_code, uint64_t
 {
 }
 
-QUICConnectionCloseFrame::QUICConnectionCloseFrame(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICConnectionCloseFrame::QUICConnectionCloseFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet)
   : QUICFrame(0, nullptr, packet)
 {
   this->parse(buf, len, packet);
@@ -1286,7 +1302,7 @@ QUICConnectionCloseFrame::_reset()
 }
 
 void
-QUICConnectionCloseFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICConnectionCloseFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
@@ -1488,13 +1504,13 @@ QUICMaxDataFrame::_reset()
   this->_size  = 0;
 }
 
-QUICMaxDataFrame::QUICMaxDataFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
+QUICMaxDataFrame::QUICMaxDataFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet) : QUICFrame(0, nullptr, packet)
 {
   this->parse(buf, len, packet);
 }
 
 void
-QUICMaxDataFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICMaxDataFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
@@ -1588,14 +1604,14 @@ QUICMaxStreamDataFrame::_reset()
   this->_size  = 0;
 }
 
-QUICMaxStreamDataFrame::QUICMaxStreamDataFrame(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICMaxStreamDataFrame::QUICMaxStreamDataFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet)
   : QUICFrame(0, nullptr, packet)
 {
   this->parse(buf, len, packet);
 }
 
 void
-QUICMaxStreamDataFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICMaxStreamDataFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
@@ -1701,13 +1717,13 @@ QUICMaxStreamsFrame::_reset()
   this->_size  = 0;
 }
 
-QUICMaxStreamsFrame::QUICMaxStreamsFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
+QUICMaxStreamsFrame::QUICMaxStreamsFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet) : QUICFrame(0, nullptr, packet)
 {
   this->parse(buf, len, packet);
 }
 
 void
-QUICMaxStreamsFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICMaxStreamsFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
@@ -1775,7 +1791,8 @@ QUICMaxStreamsFrame::maximum_streams() const
 //
 // DATA_BLOCKED frame
 //
-QUICDataBlockedFrame::QUICDataBlockedFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
+QUICDataBlockedFrame::QUICDataBlockedFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet)
+  : QUICFrame(0, nullptr, packet)
 {
   this->parse(buf, len, packet);
 }
@@ -1792,7 +1809,7 @@ QUICDataBlockedFrame::_reset()
 }
 
 void
-QUICDataBlockedFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICDataBlockedFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
@@ -1866,7 +1883,7 @@ QUICDataBlockedFrame::offset() const
 //
 // STREAM_DATA_BLOCKED frame
 //
-QUICStreamDataBlockedFrame::QUICStreamDataBlockedFrame(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICStreamDataBlockedFrame::QUICStreamDataBlockedFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet)
   : QUICFrame(0, nullptr, packet)
 {
   this->parse(buf, len, packet);
@@ -1885,7 +1902,7 @@ QUICStreamDataBlockedFrame::_reset()
 }
 
 void
-QUICStreamDataBlockedFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICStreamDataBlockedFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
@@ -1974,7 +1991,7 @@ QUICStreamDataBlockedFrame::offset() const
 //
 // STREAMS_BLOCKED frame
 //
-QUICStreamIdBlockedFrame::QUICStreamIdBlockedFrame(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICStreamIdBlockedFrame::QUICStreamIdBlockedFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet)
   : QUICFrame(0, nullptr, packet)
 {
   this->parse(buf, len, packet);
@@ -1992,7 +2009,7 @@ QUICStreamIdBlockedFrame::_reset()
 }
 
 void
-QUICStreamIdBlockedFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICStreamIdBlockedFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
@@ -2060,7 +2077,7 @@ QUICStreamIdBlockedFrame::stream_id() const
 //
 // NEW_CONNECTION_ID frame
 //
-QUICNewConnectionIdFrame::QUICNewConnectionIdFrame(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICNewConnectionIdFrame::QUICNewConnectionIdFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet)
   : QUICFrame(0, nullptr, packet)
 {
   this->parse(buf, len, packet);
@@ -2080,7 +2097,7 @@ QUICNewConnectionIdFrame::_reset()
 }
 
 void
-QUICNewConnectionIdFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICNewConnectionIdFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
@@ -2190,11 +2207,10 @@ QUICNewConnectionIdFrame::to_io_buffer_block(size_t limit) const
 int
 QUICNewConnectionIdFrame::debug_msg(char *msg, size_t msg_len) const
 {
-  char cid_str[QUICConnectionId::MAX_HEX_STR_LENGTH];
-  this->connection_id().hex(cid_str, QUICConnectionId::MAX_HEX_STR_LENGTH);
-
-  return snprintf(msg, msg_len, "NEW_CONNECTION_ID size=%zu seq=%" PRIu64 " rpt=%" PRIu64 " cid=0x%s", this->size(),
-                  this->sequence(), this->retire_prior_to(), cid_str);
+  return snprintf(msg, msg_len, "NEW_CONNECTION_ID size=%zu seq=%" PRIu64 " rpt=%" PRIu64 " cid=0x%s srt=%02x%02x%02x%02x",
+                  this->size(), this->sequence(), this->retire_prior_to(), this->connection_id().hex().c_str(),
+                  this->stateless_reset_token().buf()[0], this->stateless_reset_token().buf()[1],
+                  this->stateless_reset_token().buf()[2], this->stateless_reset_token().buf()[3]);
 }
 
 uint64_t
@@ -2243,13 +2259,14 @@ QUICStopSendingFrame::_reset()
   this->_size  = 0;
 }
 
-QUICStopSendingFrame::QUICStopSendingFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
+QUICStopSendingFrame::QUICStopSendingFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet)
+  : QUICFrame(0, nullptr, packet)
 {
   this->parse(buf, len, packet);
 }
 
 void
-QUICStopSendingFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICStopSendingFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
@@ -2336,7 +2353,7 @@ QUICStopSendingFrame::stream_id() const
 //
 // PATH_CHALLENGE frame
 //
-QUICPathChallengeFrame::QUICPathChallengeFrame(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICPathChallengeFrame::QUICPathChallengeFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet)
   : QUICFrame(0, nullptr, packet)
 {
   this->parse(buf, len, packet);
@@ -2353,7 +2370,7 @@ QUICPathChallengeFrame::_reset()
 }
 
 void
-QUICPathChallengeFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICPathChallengeFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
@@ -2435,7 +2452,7 @@ QUICPathChallengeFrame::data() const
 //
 // PATH_RESPONSE frame
 //
-QUICPathResponseFrame::QUICPathResponseFrame(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICPathResponseFrame::QUICPathResponseFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet)
   : QUICFrame(0, nullptr, packet)
 {
   this->parse(buf, len, packet);
@@ -2478,7 +2495,7 @@ QUICPathResponseFrame::to_io_buffer_block(size_t limit) const
 }
 
 void
-QUICPathResponseFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICPathResponseFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
@@ -2530,7 +2547,7 @@ QUICPathResponseFrame::data() const
 //
 // QUICNewTokenFrame
 //
-QUICNewTokenFrame::QUICNewTokenFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
+QUICNewTokenFrame::QUICNewTokenFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet) : QUICFrame(0, nullptr, packet)
 {
   this->parse(buf, len, packet);
 }
@@ -2548,7 +2565,7 @@ QUICNewTokenFrame::_reset()
 }
 
 void
-QUICNewTokenFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICNewTokenFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
@@ -2632,7 +2649,7 @@ QUICNewTokenFrame::token() const
 //
 // RETIRE_CONNECTION_ID frame
 //
-QUICRetireConnectionIdFrame::QUICRetireConnectionIdFrame(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICRetireConnectionIdFrame::QUICRetireConnectionIdFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet)
   : QUICFrame(0, nullptr, packet)
 {
   this->parse(buf, len, packet);
@@ -2651,7 +2668,7 @@ QUICRetireConnectionIdFrame::_reset()
 }
 
 void
-QUICRetireConnectionIdFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICRetireConnectionIdFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
@@ -2723,6 +2740,59 @@ QUICRetireConnectionIdFrame::seq_num() const
 }
 
 //
+// HANDSHAKE_DONE frame
+//
+
+QUICHandshakeDoneFrame::QUICHandshakeDoneFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet)
+  : QUICFrame(0, nullptr, packet)
+{
+  this->parse(buf, len, packet);
+}
+
+void
+QUICHandshakeDoneFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
+{
+  this->_reset();
+  this->_packet = packet;
+  this->_valid  = true;
+  this->_size   = 1;
+}
+
+QUICFrameType
+QUICHandshakeDoneFrame::type() const
+{
+  return QUICFrameType::HANDSHAKE_DONE;
+}
+
+size_t
+QUICHandshakeDoneFrame::size() const
+{
+  return 1;
+}
+
+Ptr<IOBufferBlock>
+QUICHandshakeDoneFrame::to_io_buffer_block(size_t limit) const
+{
+  Ptr<IOBufferBlock> block;
+  size_t n = 0;
+
+  if (limit < this->size()) {
+    return block;
+  }
+
+  block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(this->size(), BUFFER_SIZE_INDEX_32K));
+  uint8_t *block_start = reinterpret_cast<uint8_t *>(block->start());
+
+  // Type
+  block_start[0] = static_cast<uint8_t>(QUICFrameType::HANDSHAKE_DONE);
+  n += 1;
+
+  block->fill(n);
+  return block;
+}
+
+//
 // UNKNOWN
 //
 QUICFrameType
@@ -2746,7 +2816,7 @@ QUICUnknownFrame::to_io_buffer_block(size_t limit) const
 }
 
 void
-QUICUnknownFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICUnknownFrame::parse(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   this->_packet = packet;
 }
@@ -2762,7 +2832,7 @@ QUICUnknownFrame::debug_msg(char *msg, size_t msg_len) const
 //
 
 QUICFrame *
-QUICFrameFactory::create(uint8_t *buf, const uint8_t *src, size_t len, const QUICPacket *packet)
+QUICFrameFactory::create(uint8_t *buf, const uint8_t *src, size_t len, const QUICPacketR *packet)
 {
   switch (QUICFrame::type(src)) {
   case QUICFrameType::STREAM:
@@ -2822,6 +2892,9 @@ QUICFrameFactory::create(uint8_t *buf, const uint8_t *src, size_t len, const QUI
   case QUICFrameType::RETIRE_CONNECTION_ID:
     new (buf) QUICRetireConnectionIdFrame(src, len, packet);
     return reinterpret_cast<QUICFrame *>(buf);
+  case QUICFrameType::HANDSHAKE_DONE:
+    new (buf) QUICHandshakeDoneFrame(src, len, packet);
+    return reinterpret_cast<QUICFrame *>(buf);
   default:
     // Unknown frame
     Debug("quic_frame_factory", "Unknown frame type %x", src[0]);
@@ -2830,7 +2903,7 @@ QUICFrameFactory::create(uint8_t *buf, const uint8_t *src, size_t len, const QUI
 }
 
 const QUICFrame &
-QUICFrameFactory::fast_create(const uint8_t *buf, size_t len, const QUICPacket *packet)
+QUICFrameFactory::fast_create(const uint8_t *buf, size_t len, const QUICPacketR *packet)
 {
   if (QUICFrame::type(buf) == QUICFrameType::UNKNOWN) {
     return this->_unknown_frame;
@@ -3024,6 +3097,13 @@ QUICFrameFactory::create_retire_connection_id_frame(uint8_t *buf, uint64_t seq_n
   return reinterpret_cast<QUICRetireConnectionIdFrame *>(buf);
 }
 
+QUICHandshakeDoneFrame *
+QUICFrameFactory::create_handshake_done_frame(uint8_t *buf, QUICFrameId id, QUICFrameGenerator *owner)
+{
+  new (buf) QUICHandshakeDoneFrame(id, owner);
+  return reinterpret_cast<QUICHandshakeDoneFrame *>(buf);
+}
+
 QUICFrameId
 QUICFrameInfo::id() const
 {
diff --git a/iocore/net/quic/QUICFrame.h b/iocore/net/quic/QUICFrame.h
index 8d78510..e933e68 100644
--- a/iocore/net/quic/QUICFrame.h
+++ b/iocore/net/quic/QUICFrame.h
@@ -30,13 +30,14 @@
 #include "I_IOBuffer.h"
 #include <vector>
 #include <iterator>
+#include <set>
 
 #include "QUICTypes.h"
 
 class QUICFrame;
 class QUICStreamFrame;
 class QUICCryptoFrame;
-class QUICPacket;
+class QUICPacketR;
 class QUICFrameGenerator;
 
 using QUICFrameId = uint64_t;
@@ -57,16 +58,16 @@ public:
   virtual bool is_flow_controlled() const;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const = 0;
   virtual int debug_msg(char *msg, size_t msg_len) const;
-  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet){};
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet){};
   virtual QUICFrameGenerator *generated_by();
   bool valid() const;
   bool ack_eliciting() const;
-  const QUICPacket *packet() const;
+  const QUICPacketR *packet() const;
   LINK(QUICFrame, link);
 
 protected:
   virtual void _reset(){};
-  QUICFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr, const QUICPacket *packet = nullptr)
+  QUICFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr, const QUICPacketR *packet = nullptr)
     : _id(id), _owner(owner), _packet(packet)
   {
   }
@@ -74,7 +75,7 @@ protected:
   bool _valid                = false;
   QUICFrameId _id            = 0;
   QUICFrameGenerator *_owner = nullptr;
-  const QUICPacket *_packet  = nullptr;
+  const QUICPacketR *_packet = nullptr;
 };
 
 //
@@ -85,7 +86,7 @@ class QUICStreamFrame : public QUICFrame
 {
 public:
   QUICStreamFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICStreamFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
+  QUICStreamFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
   QUICStreamFrame(Ptr<IOBufferBlock> &block, QUICStreamId streamid, QUICOffset offset, bool last = false,
                   bool has_offset_field = true, bool has_length_field = true, QUICFrameId id = 0,
                   QUICFrameGenerator *owner = nullptr);
@@ -96,7 +97,7 @@ public:
   virtual bool is_flow_controlled() const override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
-  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
 
   QUICStreamId stream_id() const;
   QUICOffset offset() const;
@@ -131,7 +132,7 @@ class QUICCryptoFrame : public QUICFrame
 {
 public:
   QUICCryptoFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICCryptoFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
+  QUICCryptoFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
   QUICCryptoFrame(Ptr<IOBufferBlock> &block, QUICOffset offset, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr);
   QUICCryptoFrame(const QUICCryptoFrame &o);
 
@@ -139,7 +140,7 @@ public:
   virtual size_t size() const override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
-  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
 
   QUICOffset offset() const;
   uint64_t data_length() const;
@@ -265,9 +266,10 @@ public:
   };
 
   QUICAckFrame(QUICFrameId id = 0) : QUICFrame(id) {}
-  QUICAckFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
+  QUICAckFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
   QUICAckFrame(QUICPacketNumber largest_acknowledged, uint64_t ack_delay, uint64_t first_ack_block, QUICFrameId id = 0,
                QUICFrameGenerator *owner = nullptr);
+  std::set<PacketNumberRange> ranges() const;
 
   // There's no reasont restrict copy, but we need to write the copy constructor. Otherwise it will crash on destruct.
   QUICAckFrame(const QUICAckFrame &) = delete;
@@ -276,7 +278,7 @@ public:
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
-  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
 
   QUICPacketNumber largest_acknowledged() const;
@@ -304,7 +306,7 @@ class QUICRstStreamFrame : public QUICFrame
 {
 public:
   QUICRstStreamFrame(QUICFrameId id = 0) : QUICFrame(id) {}
-  QUICRstStreamFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
+  QUICRstStreamFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
   QUICRstStreamFrame(QUICStreamId stream_id, QUICAppErrorCode error_code, QUICOffset final_offset, QUICFrameId id = 0,
                      QUICFrameGenerator *owner = nullptr);
 
@@ -312,7 +314,7 @@ public:
   virtual size_t size() const override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
-  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
 
   QUICStreamId stream_id() const;
   QUICAppErrorCode error_code() const;
@@ -334,11 +336,11 @@ class QUICPingFrame : public QUICFrame
 {
 public:
   QUICPingFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICPingFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
+  QUICPingFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
-  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
 
 private:
 };
@@ -351,12 +353,12 @@ class QUICPaddingFrame : public QUICFrame
 {
 public:
   QUICPaddingFrame(size_t size) : _size(size) {}
-  QUICPaddingFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
+  QUICPaddingFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
   virtual bool is_probing_frame() const override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
-  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
 
 private:
   // padding frame is a resident of padding frames
@@ -372,7 +374,7 @@ class QUICConnectionCloseFrame : public QUICFrame
 {
 public:
   QUICConnectionCloseFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICConnectionCloseFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
+  QUICConnectionCloseFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
   // Constructor for transport error codes
   QUICConnectionCloseFrame(uint64_t error_code, QUICFrameType frame_type, uint64_t reason_phrase_length, const char *reason_phrase,
                            QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr);
@@ -383,7 +385,7 @@ public:
   virtual size_t size() const override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
-  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
 
   uint16_t error_code() const;
   QUICFrameType frame_type() const;
@@ -408,13 +410,13 @@ class QUICMaxDataFrame : public QUICFrame
 {
 public:
   QUICMaxDataFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICMaxDataFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
+  QUICMaxDataFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
   QUICMaxDataFrame(uint64_t maximum_data, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr);
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
-  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
 
   uint64_t maximum_data() const;
 
@@ -432,12 +434,12 @@ class QUICMaxStreamDataFrame : public QUICFrame
 {
 public:
   QUICMaxStreamDataFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICMaxStreamDataFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
+  QUICMaxStreamDataFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
   QUICMaxStreamDataFrame(QUICStreamId stream_id, uint64_t maximum_stream_data, QUICFrameId id = 0,
                          QUICFrameGenerator *owner = nullptr);
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
-  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
 
@@ -459,12 +461,12 @@ class QUICMaxStreamsFrame : public QUICFrame
 {
 public:
   QUICMaxStreamsFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICMaxStreamsFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
+  QUICMaxStreamsFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
   QUICMaxStreamsFrame(QUICStreamId maximum_streams, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr);
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
-  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
   uint64_t maximum_streams() const;
 
 private:
@@ -480,13 +482,13 @@ class QUICDataBlockedFrame : public QUICFrame
 {
 public:
   QUICDataBlockedFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICDataBlockedFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
+  QUICDataBlockedFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
   QUICDataBlockedFrame(QUICOffset offset, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr)
     : QUICFrame(id, owner), _offset(offset){};
 
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
-  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
 
@@ -506,14 +508,14 @@ class QUICStreamDataBlockedFrame : public QUICFrame
 {
 public:
   QUICStreamDataBlockedFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICStreamDataBlockedFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
+  QUICStreamDataBlockedFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
   QUICStreamDataBlockedFrame(QUICStreamId s, QUICOffset o, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr)
     : QUICFrame(id, owner), _stream_id(s), _offset(o){};
 
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
-  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
 
   QUICStreamId stream_id() const;
@@ -533,7 +535,7 @@ class QUICStreamIdBlockedFrame : public QUICFrame
 {
 public:
   QUICStreamIdBlockedFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICStreamIdBlockedFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
+  QUICStreamIdBlockedFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
   QUICStreamIdBlockedFrame(QUICStreamId s, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr)
     : QUICFrame(id, owner), _stream_id(s)
   {
@@ -541,7 +543,7 @@ public:
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
-  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
 
   QUICStreamId stream_id() const;
 
@@ -559,7 +561,7 @@ class QUICNewConnectionIdFrame : public QUICFrame
 {
 public:
   QUICNewConnectionIdFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICNewConnectionIdFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
+  QUICNewConnectionIdFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
   QUICNewConnectionIdFrame(uint64_t seq, uint64_t ret, const QUICConnectionId &cid, QUICStatelessResetToken token,
                            QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr)
     : QUICFrame(id, owner), _sequence(seq), _retire_prior_to(ret), _connection_id(cid), _stateless_reset_token(token){};
@@ -567,7 +569,7 @@ public:
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
-  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
 
   uint64_t sequence() const;
@@ -592,13 +594,13 @@ class QUICStopSendingFrame : public QUICFrame
 {
 public:
   QUICStopSendingFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICStopSendingFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
+  QUICStopSendingFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
   QUICStopSendingFrame(QUICStreamId stream_id, QUICAppErrorCode error_code, QUICFrameId id = 0,
                        QUICFrameGenerator *owner = nullptr);
 
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
-  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
 
   QUICStreamId stream_id() const;
@@ -620,7 +622,7 @@ class QUICPathChallengeFrame : public QUICFrame
 public:
   static constexpr uint8_t DATA_LEN = 8;
   QUICPathChallengeFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICPathChallengeFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
+  QUICPathChallengeFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
   QUICPathChallengeFrame(ats_unique_buf data, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr)
     : QUICFrame(id, owner), _data(std::move(data))
   {
@@ -628,7 +630,7 @@ public:
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
   virtual bool is_probing_frame() const override;
-  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
 
@@ -649,7 +651,7 @@ class QUICPathResponseFrame : public QUICFrame
 public:
   static constexpr uint8_t DATA_LEN = 8;
   QUICPathResponseFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICPathResponseFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
+  QUICPathResponseFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
   QUICPathResponseFrame(ats_unique_buf data, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr)
     : QUICFrame(id, owner), _data(std::move(data))
   {
@@ -657,7 +659,7 @@ public:
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
   virtual bool is_probing_frame() const override;
-  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
 
@@ -677,7 +679,7 @@ class QUICNewTokenFrame : public QUICFrame
 {
 public:
   QUICNewTokenFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICNewTokenFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
+  QUICNewTokenFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
   QUICNewTokenFrame(ats_unique_buf token, size_t token_length, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr)
     : QUICFrame(id, owner), _token_length(token_length), _token(std::move(token))
   {
@@ -685,7 +687,7 @@ public:
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
-  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
 
   uint64_t token_length() const;
   const uint8_t *token() const;
@@ -705,7 +707,7 @@ class QUICRetireConnectionIdFrame : public QUICFrame
 {
 public:
   QUICRetireConnectionIdFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICRetireConnectionIdFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
+  QUICRetireConnectionIdFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
   QUICRetireConnectionIdFrame(uint64_t seq_num, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr)
     : QUICFrame(id, owner), _seq_num(seq_num)
   {
@@ -713,7 +715,7 @@ public:
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
-  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
 
   uint64_t seq_num() const;
@@ -725,15 +727,31 @@ private:
 };
 
 //
+// HANDSHAKE_DONE
+//
+
+class QUICHandshakeDoneFrame : public QUICFrame
+{
+public:
+  QUICHandshakeDoneFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
+  QUICHandshakeDoneFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
+  virtual QUICFrameType type() const override;
+  virtual size_t size() const override;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
+};
+
+//
 // UNKNOWN
 //
 
 class QUICUnknownFrame : public QUICFrame
 {
+public:
   QUICFrameType type() const override;
   size_t size() const override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
-  void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  void parse(const uint8_t *buf, size_t len, const QUICPacketR *packet) override;
   int debug_msg(char *msg, size_t msg_len) const override;
 };
 
@@ -746,13 +764,13 @@ public:
   /*
    * This is used for creating a QUICFrame object based on received data.
    */
-  static QUICFrame *create(uint8_t *buf, const uint8_t *src, size_t len, const QUICPacket *packet);
+  static QUICFrame *create(uint8_t *buf, const uint8_t *src, size_t len, const QUICPacketR *packet);
 
   /*
    * This works almost the same as create() but it reuses created objects for performance.
    * If you create a frame object which has the same frame type that you created before, the object will be reset by new data.
    */
-  const QUICFrame &fast_create(const uint8_t *buf, size_t len, const QUICPacket *packet);
+  const QUICFrame &fast_create(const uint8_t *buf, size_t len, const QUICPacketR *packet);
 
   /*
    * Creates a STREAM frame.
@@ -879,6 +897,11 @@ public:
    */
   static QUICPaddingFrame *create_padding_frame(uint8_t *buf, size_t size, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr);
 
+  /*
+   * Creates a HANDSHAKE_DONE frame
+   */
+  static QUICHandshakeDoneFrame *create_handshake_done_frame(uint8_t *buf, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr);
+
 private:
   // FIXME Actual number of frame types is several but some of the values are not sequential.
   QUICFrame *_reusable_frames[256] = {nullptr};
diff --git a/iocore/net/quic/QUICFrameDispatcher.cc b/iocore/net/quic/QUICFrameDispatcher.cc
index b807ff5..9509933 100644
--- a/iocore/net/quic/QUICFrameDispatcher.cc
+++ b/iocore/net/quic/QUICFrameDispatcher.cc
@@ -42,8 +42,9 @@ QUICFrameDispatcher::add_handler(QUICFrameHandler *handler)
 }
 
 QUICConnectionErrorUPtr
-QUICFrameDispatcher::receive_frames(QUICEncryptionLevel level, const uint8_t *payload, uint16_t size, bool &ack_only,
-                                    bool &is_flow_controlled, bool *has_non_probing_frame, const QUICPacket *packet)
+QUICFrameDispatcher::receive_frames(QUICContext &ctx, QUICEncryptionLevel level, const uint8_t *payload, uint16_t size,
+                                    bool &ack_only, bool &is_flow_controlled, bool *has_non_probing_frame,
+                                    const QUICPacketR *packet)
 {
   uint16_t cursor               = 0;
   ack_only                      = true;
@@ -63,6 +64,8 @@ QUICFrameDispatcher::receive_frames(QUICEncryptionLevel level, const uint8_t *pa
 
     QUICFrameType type = frame.type();
 
+    ctx.trigger(QUICContext::CallbackEvent::FRAME_RECV, frame);
+
     if (type == QUICFrameType::STREAM) {
       is_flow_controlled = true;
     }
diff --git a/iocore/net/quic/QUICFrameDispatcher.h b/iocore/net/quic/QUICFrameDispatcher.h
index e7ce785..435103b 100644
--- a/iocore/net/quic/QUICFrameDispatcher.h
+++ b/iocore/net/quic/QUICFrameDispatcher.h
@@ -28,15 +28,16 @@
 #include "QUICConnection.h"
 #include "QUICFrame.h"
 #include "QUICFrameHandler.h"
+#include "QUICContext.h"
 
 class QUICFrameDispatcher
 {
 public:
   QUICFrameDispatcher(QUICConnectionInfoProvider *info);
 
-  QUICConnectionErrorUPtr receive_frames(QUICEncryptionLevel level, const uint8_t *payload, uint16_t size,
+  QUICConnectionErrorUPtr receive_frames(QUICContext &context, QUICEncryptionLevel level, const uint8_t *payload, uint16_t size,
                                          bool &should_send_ackbool, bool &is_flow_controlled, bool *has_non_probing_frame,
-                                         const QUICPacket *packet);
+                                         const QUICPacketR *packet);
 
   void add_handler(QUICFrameHandler *handler);
 
diff --git a/iocore/net/quic/QUICHandshake.cc b/iocore/net/quic/QUICHandshake.cc
index c88377f..08c3061 100644
--- a/iocore/net/quic/QUICHandshake.cc
+++ b/iocore/net/quic/QUICHandshake.cc
@@ -27,6 +27,7 @@
 
 #include "QUICEvents.h"
 #include "QUICGlobals.h"
+#include "QUICHandshakeProtocol.h"
 #include "QUICPacketFactory.h"
 #include "QUICVersionNegotiator.h"
 #include "QUICConfig.h"
@@ -80,8 +81,6 @@
   }
 
 static constexpr int UDP_MAXIMUM_PAYLOAD_SIZE = 65527;
-// TODO: fix size
-static constexpr int MAX_HANDSHAKE_MSG_LEN = 65527;
 
 QUICHandshake::QUICHandshake(QUICConnection *qc, QUICHandshakeProtocol *hsp) : QUICHandshake(qc, hsp, {}, false) {}
 
@@ -119,7 +118,7 @@ QUICHandshake::start(const QUICTPConfig &tp_config, QUICPacketFactory *packet_fa
 }
 
 QUICConnectionErrorUPtr
-QUICHandshake::start(const QUICTPConfig &tp_config, const QUICPacket &initial_packet, QUICPacketFactory *packet_factory,
+QUICHandshake::start(const QUICTPConfig &tp_config, const QUICInitialPacketR &initial_packet, QUICPacketFactory *packet_factory,
                      const QUICPreferredAddress *pref_addr)
 {
   // Negotiate version
@@ -143,7 +142,7 @@ QUICHandshake::start(const QUICTPConfig &tp_config, const QUICPacket &initial_pa
 }
 
 QUICConnectionErrorUPtr
-QUICHandshake::negotiate_version(const QUICPacket &vn, QUICPacketFactory *packet_factory)
+QUICHandshake::negotiate_version(const QUICVersionNegotiationPacketR &vn, QUICPacketFactory *packet_factory)
 {
   // Client side only
   ink_assert(this->_qc->direction() == NET_VCONNECTION_OUT);
@@ -186,6 +185,16 @@ QUICHandshake::is_completed() const
 }
 
 bool
+QUICHandshake::is_confirmed() const
+{
+  if (this->_qc->direction() == NET_VCONNECTION_IN) {
+    return this->is_completed();
+  } else {
+    return this->_is_handshake_done_received;
+  }
+}
+
+bool
 QUICHandshake::is_stateless_retry_enabled() const
 {
   return this->_stateless_retry;
@@ -298,6 +307,7 @@ QUICHandshake::interests()
 {
   return {
     QUICFrameType::CRYPTO,
+    QUICFrameType::HANDSHAKE_DONE,
   };
 }
 
@@ -308,8 +318,15 @@ QUICHandshake::handle_frame(QUICEncryptionLevel level, const QUICFrame &frame)
   switch (frame.type()) {
   case QUICFrameType::CRYPTO:
     error = this->_crypto_streams[static_cast<int>(level)].recv(static_cast<const QUICCryptoFrame &>(frame));
-    if (error != nullptr) {
-      return error;
+    if (error == nullptr) {
+      error = this->do_handshake();
+    }
+    break;
+  case QUICFrameType::HANDSHAKE_DONE:
+    if (this->_qc->direction() == NET_VCONNECTION_IN) {
+      error = std::make_unique<QUICConnectionError>(QUICTransErrorCode::PROTOCOL_VIOLATION);
+    } else {
+      this->_is_handshake_done_received = true;
     }
     break;
   default:
@@ -318,7 +335,7 @@ QUICHandshake::handle_frame(QUICEncryptionLevel level, const QUICFrame &frame)
     break;
   }
 
-  return this->do_handshake();
+  return error;
 }
 
 bool
@@ -328,7 +345,8 @@ QUICHandshake::will_generate_frame(QUICEncryptionLevel level, size_t current_pac
     return false;
   }
 
-  return this->_crypto_streams[static_cast<int>(level)].will_generate_frame(level, current_packet_size, ack_eliciting, seq_num);
+  return (this->_qc->direction() == NET_VCONNECTION_IN && !this->_is_handshake_done_sent) ||
+         this->_crypto_streams[static_cast<int>(level)].will_generate_frame(level, current_packet_size, ack_eliciting, seq_num);
 }
 
 QUICFrame *
@@ -338,8 +356,23 @@ QUICHandshake::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t
   QUICFrame *frame = nullptr;
 
   if (this->_is_level_matched(level)) {
+    // CRYPTO
     frame = this->_crypto_streams[static_cast<int>(level)].generate_frame(buf, level, connection_credit, maximum_frame_size,
                                                                           current_packet_size, seq_num);
+    if (frame) {
+      return frame;
+    }
+  }
+
+  if (level == QUICEncryptionLevel::ONE_RTT) {
+    // HANDSHAKE_DONE
+    if (!this->_is_handshake_done_sent && this->is_completed()) {
+      frame = QUICFrameFactory::create_handshake_done_frame(buf, this->_issue_frame_id(), this);
+    }
+    if (frame) {
+      this->_is_handshake_done_sent = true;
+      return frame;
+    }
   }
 
   return frame;
@@ -351,7 +384,7 @@ QUICHandshake::_load_local_server_transport_parameters(const QUICTPConfig &tp_co
   QUICTransportParametersInEncryptedExtensions *tp = new QUICTransportParametersInEncryptedExtensions();
 
   // MUSTs
-  tp->set(QUICTransportParameterId::IDLE_TIMEOUT, static_cast<uint16_t>(tp_config.no_activity_timeout()));
+  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(),
             this->_qc->first_connection_id().length());
@@ -376,6 +409,9 @@ QUICHandshake::_load_local_server_transport_parameters(const QUICTPConfig &tp_co
   if (tp_config.initial_max_stream_data_uni() != 0) {
     tp->set(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI, tp_config.initial_max_stream_data_uni());
   }
+  if (tp_config.disable_active_migration()) {
+    tp->set(QUICTransportParameterId::DISABLE_ACTIVE_MIGRATION, nullptr, 0);
+  }
   if (pref_addr != nullptr) {
     uint8_t pref_addr_buf[QUICPreferredAddress::MAX_LEN];
     uint16_t len;
@@ -392,6 +428,11 @@ QUICHandshake::_load_local_server_transport_parameters(const QUICTPConfig &tp_co
 
   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);
+  }
+
   this->_local_transport_parameters = std::shared_ptr<QUICTransportParameters>(tp);
   this->_hs_protocol->set_local_transport_parameters(this->_local_transport_parameters);
 }
@@ -402,7 +443,7 @@ QUICHandshake::_load_local_client_transport_parameters(const QUICTPConfig &tp_co
   QUICTransportParametersInClientHello *tp = new QUICTransportParametersInClientHello();
 
   // MUSTs
-  tp->set(QUICTransportParameterId::IDLE_TIMEOUT, static_cast<uint16_t>(tp_config.no_activity_timeout()));
+  tp->set(QUICTransportParameterId::MAX_IDLE_TIMEOUT, static_cast<uint16_t>(tp_config.no_activity_timeout()));
 
   // MAYs
   if (tp_config.initial_max_data() != 0) {
@@ -428,6 +469,11 @@ QUICHandshake::_load_local_client_transport_parameters(const QUICTPConfig &tp_co
     tp->set(QUICTransportParameterId::ACTIVE_CONNECTION_ID_LIMIT, tp_config.active_cid_limit());
   }
 
+  // Additional parameters
+  for (auto &&param : tp_config.additional_tp()) {
+    tp->set(param.first, param.second.first, param.second.second);
+  }
+
   this->_local_transport_parameters = std::shared_ptr<QUICTransportParameters>(tp);
   this->_hs_protocol->set_local_transport_parameters(std::unique_ptr<QUICTransportParameters>(tp));
 }
@@ -452,18 +498,13 @@ QUICHandshake::do_handshake()
       // TODO: check size
       if (bytes_avail > 0) {
         stream->read(in.buf + in.offsets[index], bytes_avail);
-        in.offsets[index] = bytes_avail;
-        in.offsets[4] += bytes_avail;
       }
+      in.offsets[index + 1] = in.offsets[index] + bytes_avail;
     }
   }
 
-  QUICHandshakeMsgs out;
-  uint8_t out_buf[MAX_HANDSHAKE_MSG_LEN] = {0};
-  out.buf                                = out_buf;
-  out.max_buf_len                        = MAX_HANDSHAKE_MSG_LEN;
-
-  int result = this->_hs_protocol->handshake(&out, &in);
+  QUICHandshakeMsgs *out = nullptr;
+  int result             = this->_hs_protocol->handshake(&out, &in);
   if (this->_remote_transport_parameters == nullptr) {
     if (!this->check_remote_transport_parameters()) {
       result = 0;
@@ -471,18 +512,25 @@ QUICHandshake::do_handshake()
   }
 
   if (result == 1) {
-    for (auto level : QUIC_ENCRYPTION_LEVELS) {
-      int index                = static_cast<int>(level);
-      QUICCryptoStream *stream = &this->_crypto_streams[index];
-      size_t len               = out.offsets[index + 1] - out.offsets[index];
-      // TODO: check size
-      if (len > 0) {
-        stream->write(out.buf + out.offsets[index], len);
+    if (out) {
+      for (auto level : QUIC_ENCRYPTION_LEVELS) {
+        int index                = static_cast<int>(level);
+        QUICCryptoStream *stream = &this->_crypto_streams[index];
+        size_t len               = out->offsets[index + 1] - out->offsets[index];
+        // TODO: check size
+        if (len > 0) {
+          stream->write(out->buf + out->offsets[index], len);
+        }
       }
     }
-  } else if (out.error_code != 0) {
+  } else {
     this->_hs_protocol->abort_handshake();
-    error = std::make_unique<QUICConnectionError>(QUICErrorClass::TRANSPORT, out.error_code);
+    if (this->_hs_protocol->has_crypto_error()) {
+      error = std::make_unique<QUICConnectionError>(QUICErrorClass::TRANSPORT, this->_hs_protocol->crypto_error());
+    } else {
+      error = std::make_unique<QUICConnectionError>(QUICErrorClass::TRANSPORT,
+                                                    static_cast<uint16_t>(QUICTransErrorCode::PROTOCOL_VIOLATION));
+    }
   }
 
   return error;
@@ -495,7 +543,7 @@ QUICHandshake::_abort_handshake(QUICTransErrorCode code)
 
   this->_hs_protocol->abort_handshake();
 
-  this->_qc->close(QUICConnectionErrorUPtr(new QUICConnectionError(code)));
+  this->_qc->close_quic_connection(QUICConnectionErrorUPtr(new QUICConnectionError(code)));
 }
 
 /*
@@ -514,3 +562,10 @@ QUICHandshake::_is_level_matched(QUICEncryptionLevel level)
 {
   return true;
 }
+
+void
+QUICHandshake::_on_frame_lost(QUICFrameInformationUPtr &info)
+{
+  ink_assert(info->type == QUICFrameType::HANDSHAKE_DONE);
+  this->_is_handshake_done_sent = false;
+}
diff --git a/iocore/net/quic/QUICHandshake.h b/iocore/net/quic/QUICHandshake.h
index 49ef437..5af42d8 100644
--- a/iocore/net/quic/QUICHandshake.h
+++ b/iocore/net/quic/QUICHandshake.h
@@ -35,6 +35,7 @@
  */
 class QUICVersionNegotiator;
 class QUICPacketFactory;
+class QUICHandshakeProtocol;
 class SSLNextProtocolSet;
 
 class QUICHandshake : public QUICFrameHandler, public QUICFrameGenerator
@@ -57,12 +58,12 @@ public:
 
   // for client side
   QUICConnectionErrorUPtr start(const QUICTPConfig &tp_config, QUICPacketFactory *packet_factory, bool vn_exercise_enabled);
-  QUICConnectionErrorUPtr negotiate_version(const QUICPacket &packet, QUICPacketFactory *packet_factory);
+  QUICConnectionErrorUPtr negotiate_version(const QUICVersionNegotiationPacketR &packet, QUICPacketFactory *packet_factory);
   void reset();
 
   // for server side
-  QUICConnectionErrorUPtr start(const QUICTPConfig &tp_config, const QUICPacket &initial_packet, QUICPacketFactory *packet_factory,
-                                const QUICPreferredAddress *pref_addr);
+  QUICConnectionErrorUPtr start(const QUICTPConfig &tp_config, const QUICInitialPacketR &initial_packet,
+                                QUICPacketFactory *packet_factory, const QUICPreferredAddress *pref_addr);
 
   QUICConnectionErrorUPtr do_handshake();
 
@@ -77,6 +78,7 @@ public:
 
   bool is_version_negotiated() const;
   bool is_completed() const;
+  bool is_confirmed() const;
   bool is_stateless_retry_enabled() const;
   bool has_remote_tp() const;
 
@@ -102,4 +104,10 @@ private:
   std::shared_ptr<const QUICTransportParameters> _remote_transport_parameters = nullptr;
 
   void _abort_handshake(QUICTransErrorCode code);
+
+  bool _is_handshake_done_sent     = false;
+  bool _is_handshake_done_received = false;
+
+  // QUICFrameGenerator
+  void _on_frame_lost(QUICFrameInformationUPtr &info) override;
 };
diff --git a/iocore/net/quic/QUICHandshakeProtocol.h b/iocore/net/quic/QUICHandshakeProtocol.h
index fd4c68b..2d56e02 100644
--- a/iocore/net/quic/QUICHandshakeProtocol.h
+++ b/iocore/net/quic/QUICHandshakeProtocol.h
@@ -43,7 +43,7 @@ public:
   QUICHandshakeProtocol(QUICPacketProtectionKeyInfo &pp_key_info) : _pp_key_info(pp_key_info) {}
   virtual ~QUICHandshakeProtocol(){};
 
-  virtual int handshake(QUICHandshakeMsgs *out, const QUICHandshakeMsgs *in)              = 0;
+  virtual int handshake(QUICHandshakeMsgs **out, const QUICHandshakeMsgs *in)             = 0;
   virtual void reset()                                                                    = 0;
   virtual bool is_handshake_finished() const                                              = 0;
   virtual bool is_ready_to_derive() const                                                 = 0;
@@ -58,6 +58,8 @@ public:
 
   virtual QUICEncryptionLevel current_encryption_level() const = 0;
   virtual void abort_handshake()                               = 0;
+  virtual bool has_crypto_error() const                        = 0;
+  virtual uint64_t crypto_error() const                        = 0;
 
 protected:
   QUICPacketProtectionKeyInfo &_pp_key_info;
diff --git a/iocore/net/quic/QUICIntUtil.cc b/iocore/net/quic/QUICIntUtil.cc
index e9840ca..26159d7 100644
--- a/iocore/net/quic/QUICIntUtil.cc
+++ b/iocore/net/quic/QUICIntUtil.cc
@@ -102,11 +102,11 @@ QUICVariableInt::decode(uint64_t &dst, size_t &len, const uint8_t *src, size_t s
 }
 
 uint64_t
-QUICIntUtil::read_QUICVariableInt(const uint8_t *buf)
+QUICIntUtil::read_QUICVariableInt(const uint8_t *buf, size_t buf_len)
 {
   uint64_t dst = 0;
   size_t len   = 0;
-  QUICVariableInt::decode(dst, len, buf, 8);
+  QUICVariableInt::decode(dst, len, buf, buf_len);
   return dst;
 }
 
diff --git a/iocore/net/quic/QUICIntUtil.h b/iocore/net/quic/QUICIntUtil.h
index c259bca..5116304 100644
--- a/iocore/net/quic/QUICIntUtil.h
+++ b/iocore/net/quic/QUICIntUtil.h
@@ -38,7 +38,7 @@ public:
 class QUICIntUtil
 {
 public:
-  static uint64_t read_QUICVariableInt(const uint8_t *buf);
+  static uint64_t read_QUICVariableInt(const uint8_t *buf, size_t buf_len);
   static void write_QUICVariableInt(uint64_t data, uint8_t *buf, size_t *len);
   static uint64_t read_nbytes_as_uint(const uint8_t *buf, uint8_t n);
   static void write_uint_as_nbytes(uint64_t value, uint8_t n, uint8_t *buf, size_t *len);
diff --git a/iocore/net/quic/QUICKeyGenerator.cc b/iocore/net/quic/QUICKeyGenerator.cc
index 98d091d..24c2b84 100644
--- a/iocore/net/quic/QUICKeyGenerator.cc
+++ b/iocore/net/quic/QUICKeyGenerator.cc
@@ -45,8 +45,8 @@ 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)
 {
-  const QUIC_EVP_CIPHER *cipher = this->_get_cipher_for_initial();
-  const EVP_MD *md              = EVP_sha256();
+  const EVP_CIPHER *cipher = this->_get_cipher_for_initial();
+  const EVP_MD *md         = EVP_sha256();
   uint8_t secret[512];
   size_t secret_len = sizeof(secret);
   QUICHKDF hkdf(md);
@@ -79,14 +79,14 @@ QUICKeyGenerator::generate(uint8_t *hp_key, uint8_t *pp_key, uint8_t *iv, size_t
 
 void
 QUICKeyGenerator::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 QUIC_EVP_CIPHER *cipher, QUICHKDF &hkdf)
+                             size_t secret_len, const EVP_CIPHER *cipher, QUICHKDF &hkdf)
 {
   this->_generate(hp_key, pp_key, iv, iv_len, hkdf, secret, secret_len, cipher);
 }
 
 int
 QUICKeyGenerator::_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 QUIC_EVP_CIPHER *cipher)
+                            size_t secret_len, const EVP_CIPHER *cipher)
 {
   // Generate key, iv, and hp_key
   //   key    = HKDF-Expand-Label(S, "quic key", "", key_length)
diff --git a/iocore/net/quic/QUICKeyGenerator.h b/iocore/net/quic/QUICKeyGenerator.h
index a82ad43..c2ff89f 100644
--- a/iocore/net/quic/QUICKeyGenerator.h
+++ b/iocore/net/quic/QUICKeyGenerator.h
@@ -46,7 +46,7 @@ public:
   void generate(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 QUIC_EVP_CIPHER *cipher, QUICHKDF &hkdf);
+                  const EVP_CIPHER *cipher, QUICHKDF &hkdf);
 
 private:
   Context _ctx = Context::SERVER;
@@ -55,15 +55,15 @@ private:
   size_t _last_secret_len = 0;
 
   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 QUIC_EVP_CIPHER *cipher);
+                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_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;
   int _generate_hp(uint8_t *out, size_t *out_len, QUICHKDF &hkdf, const uint8_t *secret, size_t secret_len, size_t hp_length) const;
-  size_t _get_key_len(const QUIC_EVP_CIPHER *cipher) const;
-  size_t _get_iv_len(const QUIC_EVP_CIPHER *cipher) const;
-  const QUIC_EVP_CIPHER *_get_cipher_for_initial() const;
-  const QUIC_EVP_CIPHER *_get_cipher_for_protected_packet(const SSL *ssl) const;
+  size_t _get_key_len(const EVP_CIPHER *cipher) const;
+  size_t _get_iv_len(const EVP_CIPHER *cipher) const;
+  const EVP_CIPHER *_get_cipher_for_initial() const;
+  const EVP_CIPHER *_get_cipher_for_protected_packet(const SSL *ssl) const;
 };
diff --git a/iocore/net/quic/QUICKeyGenerator_boringssl.cc b/iocore/net/quic/QUICKeyGenerator_boringssl.cc
index e2204bb..4406bbd 100644
--- a/iocore/net/quic/QUICKeyGenerator_boringssl.cc
+++ b/iocore/net/quic/QUICKeyGenerator_boringssl.cc
@@ -25,33 +25,34 @@
 
 #include <openssl/ssl.h>
 size_t
-QUICKeyGenerator::_get_key_len(const QUIC_EVP_CIPHER *cipher) const
+QUICKeyGenerator::_get_key_len(const EVP_CIPHER *cipher) const
 {
-  return EVP_AEAD_key_length(cipher);
+  return EVP_CIPHER_key_length(cipher);
 }
 
 size_t
-QUICKeyGenerator::_get_iv_len(const QUIC_EVP_CIPHER *cipher) const
+QUICKeyGenerator::_get_iv_len(const EVP_CIPHER *cipher) const
 {
-  return EVP_AEAD_nonce_length(cipher);
+  return EVP_CIPHER_iv_length(cipher);
 }
 
-const QUIC_EVP_CIPHER *
+const EVP_CIPHER *
 QUICKeyGenerator::_get_cipher_for_initial() const
 {
-  return EVP_aead_aes_128_gcm();
+  return EVP_aes_128_gcm();
 }
 
-const QUIC_EVP_CIPHER *
+const EVP_CIPHER *
 QUICKeyGenerator::_get_cipher_for_protected_packet(const SSL *ssl) const
 {
   switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) {
   case TLS1_CK_AES_128_GCM_SHA256:
-    return EVP_aead_aes_128_gcm();
+    return EVP_aes_128_gcm();
   case TLS1_CK_AES_256_GCM_SHA384:
-    return EVP_aead_aes_256_gcm();
+    return EVP_aes_256_gcm();
   case TLS1_CK_CHACHA20_POLY1305_SHA256:
-    return EVP_aead_chacha20_poly1305();
+    return nullptr;
+    // return EVP_aead_chacha20_poly1305();
   default:
     ink_assert(false);
     return nullptr;
diff --git a/iocore/net/quic/QUICKeyGenerator_openssl.cc b/iocore/net/quic/QUICKeyGenerator_legacy.cc
similarity index 90%
copy from iocore/net/quic/QUICKeyGenerator_openssl.cc
copy to iocore/net/quic/QUICKeyGenerator_legacy.cc
index 5d9d029..03b46e1 100644
--- a/iocore/net/quic/QUICKeyGenerator_openssl.cc
+++ b/iocore/net/quic/QUICKeyGenerator_legacy.cc
@@ -26,24 +26,24 @@
 #include <openssl/ssl.h>
 
 size_t
-QUICKeyGenerator::_get_key_len(const QUIC_EVP_CIPHER *cipher) const
+QUICKeyGenerator::_get_key_len(const EVP_CIPHER *cipher) const
 {
   return EVP_CIPHER_key_length(cipher);
 }
 
 size_t
-QUICKeyGenerator::_get_iv_len(const QUIC_EVP_CIPHER *cipher) const
+QUICKeyGenerator::_get_iv_len(const EVP_CIPHER *cipher) const
 {
   return EVP_CIPHER_iv_length(cipher);
 }
 
-const QUIC_EVP_CIPHER *
+const EVP_CIPHER *
 QUICKeyGenerator::_get_cipher_for_initial() const
 {
   return EVP_aes_128_gcm();
 }
 
-const QUIC_EVP_CIPHER *
+const EVP_CIPHER *
 QUICKeyGenerator::_get_cipher_for_protected_packet(const SSL *ssl) const
 {
   switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) {
diff --git a/iocore/net/quic/QUICKeyGenerator_openssl.cc b/iocore/net/quic/QUICKeyGenerator_openssl.cc
index 5d9d029..e7bb227 100644
--- a/iocore/net/quic/QUICKeyGenerator_openssl.cc
+++ b/iocore/net/quic/QUICKeyGenerator_openssl.cc
@@ -24,26 +24,25 @@
 #include "QUICKeyGenerator.h"
 
 #include <openssl/ssl.h>
-
 size_t
-QUICKeyGenerator::_get_key_len(const QUIC_EVP_CIPHER *cipher) const
+QUICKeyGenerator::_get_key_len(const EVP_CIPHER *cipher) const
 {
   return EVP_CIPHER_key_length(cipher);
 }
 
 size_t
-QUICKeyGenerator::_get_iv_len(const QUIC_EVP_CIPHER *cipher) const
+QUICKeyGenerator::_get_iv_len(const EVP_CIPHER *cipher) const
 {
   return EVP_CIPHER_iv_length(cipher);
 }
 
-const QUIC_EVP_CIPHER *
+const EVP_CIPHER *
 QUICKeyGenerator::_get_cipher_for_initial() const
 {
   return EVP_aes_128_gcm();
 }
 
-const QUIC_EVP_CIPHER *
+const EVP_CIPHER *
 QUICKeyGenerator::_get_cipher_for_protected_packet(const SSL *ssl) const
 {
   switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) {
@@ -61,3 +60,21 @@ QUICKeyGenerator::_get_cipher_for_protected_packet(const SSL *ssl) const
     return nullptr;
   }
 }
+
+// SSL_HANDSHAKE_MAC_SHA256, SSL_HANDSHAKE_MAC_SHA384 are defind in `ssl/internal.h` of BoringSSL
+/*
+const EVP_MD *
+QUICKeyGenerator::get_handshake_digest(const SSL *ssl)
+{
+  switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) {
+  case TLS1_CK_AES_128_GCM_SHA256:
+  case TLS1_CK_CHACHA20_POLY1305_SHA256:
+    return EVP_sha256();
+  case TLS1_CK_AES_256_GCM_SHA384:
+    return EVP_sha384();
+  default:
+    ink_assert(false);
+    return nullptr;
+  }
+}
+*/
diff --git a/iocore/net/quic/QUICLossDetector.cc b/iocore/net/quic/QUICLossDetector.cc
index 4d6397b..6a0138d 100644
--- a/iocore/net/quic/QUICLossDetector.cc
+++ b/iocore/net/quic/QUICLossDetector.cc
@@ -38,7 +38,7 @@
 #define QUICLDVDebug(fmt, ...) \
   Debug("v_quic_loss_detector", "[%s] " fmt, this->_context.connection_info()->cids().data(), ##__VA_ARGS__)
 
-QUICLossDetector::QUICLossDetector(QUICLDContext &context, QUICCongestionController *cc, QUICRTTMeasure *rtt_measure,
+QUICLossDetector::QUICLossDetector(QUICContext &context, QUICCongestionController *cc, QUICRTTMeasure *rtt_measure,
                                    QUICPinger *pinger, QUICPadder *padder)
   : _rtt_measure(rtt_measure), _pinger(pinger), _padder(padder), _cc(cc), _context(context)
 {
@@ -444,6 +444,7 @@ QUICLossDetector::_detect_lost_packets(QUICPacketNumberSpace pn_space)
   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.
@@ -487,7 +488,7 @@ QUICLossDetector::_send_packet(QUICEncryptionLevel level, bool padded)
   if (padded) {
     this->_padder->request(level);
   } else {
-    this->_pinger->request();
+    this->_pinger->request(level);
   }
   this->_cc->add_extra_credit();
 }
@@ -497,23 +498,25 @@ QUICLossDetector::_send_one_or_two_packet()
 {
   this->_send_packet(QUICEncryptionLevel::ONE_RTT);
   this->_send_packet(QUICEncryptionLevel::ONE_RTT);
-  ink_assert(this->_pinger->count() >= 2);
+  ink_assert(this->_pinger->count(QUICEncryptionLevel::ONE_RTT) >= 2);
   QUICLDDebug("[%s] send ping frame %" PRIu64, QUICDebugNames::encryption_level(QUICEncryptionLevel::ONE_RTT),
-              this->_pinger->count());
+              this->_pinger->count(QUICEncryptionLevel::ONE_RTT));
 }
 
 void
 QUICLossDetector::_send_one_handshake_packets()
 {
   this->_send_packet(QUICEncryptionLevel::HANDSHAKE);
-  QUICLDDebug("[%s] send handshake packet", QUICDebugNames::encryption_level(QUICEncryptionLevel::HANDSHAKE));
+  QUICLDDebug("[%s] send handshake packet: ping count=%" PRIu64, QUICDebugNames::encryption_level(QUICEncryptionLevel::HANDSHAKE),
+              this->_pinger->count(QUICEncryptionLevel::HANDSHAKE));
 }
 
 void
 QUICLossDetector::_send_one_padded_packets()
 {
   this->_send_packet(QUICEncryptionLevel::INITIAL, true);
-  QUICLDDebug("[%s] send PADDING frame", QUICDebugNames::encryption_level(QUICEncryptionLevel::INITIAL));
+  QUICLDDebug("[%s] send PADDING frame: ping count=%" PRIu64, QUICDebugNames::encryption_level(QUICEncryptionLevel::INITIAL),
+              this->_pinger->count(QUICEncryptionLevel::INITIAL));
 }
 
 // ===== Functions below are helper functions =====
diff --git a/iocore/net/quic/QUICLossDetector.h b/iocore/net/quic/QUICLossDetector.h
index 5e4005b..42afd81 100644
--- a/iocore/net/quic/QUICLossDetector.h
+++ b/iocore/net/quic/QUICLossDetector.h
@@ -59,7 +59,7 @@ public:
 class QUICNewRenoCongestionController : public QUICCongestionController
 {
 public:
-  QUICNewRenoCongestionController(QUICCCContext &context);
+  QUICNewRenoCongestionController(QUICContext &context);
   virtual ~QUICNewRenoCongestionController() {}
   void on_packet_sent(size_t bytes_sent) override;
   void on_packet_acked(const QUICPacketInfo &acked_packet) override;
@@ -71,9 +71,9 @@ public:
   bool is_app_limited();
 
   // Debug
-  uint32_t bytes_in_flight() const;
-  uint32_t congestion_window() const;
-  uint32_t current_ssthresh() const;
+  uint32_t bytes_in_flight() const override;
+  uint32_t congestion_window() const override;
+  uint32_t current_ssthresh() const override;
 
   void add_extra_credit() override;
 
@@ -105,13 +105,13 @@ private:
 
   bool _in_congestion_recovery(ink_hrtime sent_time);
 
-  QUICCCContext &_context;
+  QUICContext &_context;
 };
 
 class QUICLossDetector : public Continuation, public QUICFrameHandler
 {
 public:
-  QUICLossDetector(QUICLDContext &context, QUICCongestionController *cc, QUICRTTMeasure *rtt_measure, QUICPinger *pinger,
+  QUICLossDetector(QUICContext &context, QUICCongestionController *cc, QUICRTTMeasure *rtt_measure, QUICPinger *pinger,
                    QUICPadder *padder);
   ~QUICLossDetector();
 
@@ -185,7 +185,7 @@ private:
   QUICPadder *_padder           = nullptr;
   QUICCongestionController *_cc = nullptr;
 
-  QUICLDContext &_context;
+  QUICContext &_context;
 };
 
 class QUICRTTMeasure : public QUICRTTProvider
diff --git a/iocore/net/quic/QUICNewRenoCongestionController.cc b/iocore/net/quic/QUICNewRenoCongestionController.cc
index 080c910..db6ec90 100644
--- a/iocore/net/quic/QUICNewRenoCongestionController.cc
+++ b/iocore/net/quic/QUICNewRenoCongestionController.cc
@@ -38,7 +38,7 @@
         this->_context.connection_info()->cids().data(), this->_congestion_window, this->_bytes_in_flight, this->_ssthresh, \
         this->_extra_packets_count, ##__VA_ARGS__)
 
-QUICNewRenoCongestionController::QUICNewRenoCongestionController(QUICCCContext &context)
+QUICNewRenoCongestionController::QUICNewRenoCongestionController(QUICContext &context)
   : _cc_mutex(new_ProxyMutex()), _context(context)
 {
   auto &cc_config                          = context.cc_config();
@@ -94,10 +94,13 @@ QUICNewRenoCongestionController::on_packet_acked(const QUICPacketInfo &acked_pac
 
   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 chaged");
   } 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");
   }
@@ -116,6 +119,9 @@ QUICNewRenoCongestionController::_congestion_event(ink_hrtime sent_time)
     this->_congestion_window *= this->_k_loss_reduction_factor;
     this->_congestion_window = std::max(this->_congestion_window, this->_k_minimum_window);
     this->_ssthresh          = this->_congestion_window;
+    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);
   }
 }
 
diff --git a/iocore/net/quic/QUICPacket.cc b/iocore/net/quic/QUICPacket.cc
index 534ed27..494dd32 100644
--- a/iocore/net/quic/QUICPacket.cc
+++ b/iocore/net/quic/QUICPacket.cc
@@ -23,258 +23,438 @@
 
 #include "QUICPacket.h"
 
+#include <algorithm>
+
 #include <tscore/ink_assert.h>
 #include <tscore/Diags.h>
 
 #include "QUICIntUtil.h"
 #include "QUICDebugNames.h"
+#include "QUICRetryIntegrityTag.h"
 
 using namespace std::literals;
-static constexpr std::string_view tag  = "quic_packet"sv;
-static constexpr uint64_t aead_tag_len = 16;
+static constexpr uint64_t aead_tag_len             = 16;
+static constexpr int LONG_HDR_OFFSET_CONNECTION_ID = 6;
+static constexpr int LONG_HDR_OFFSET_VERSION       = 1;
 
 #define QUICDebug(dcid, scid, fmt, ...) \
   Debug(tag.data(), "[%08" PRIx32 "-%08" PRIx32 "] " fmt, dcid.h32(), scid.h32(), ##__VA_ARGS__);
 
-ClassAllocator<QUICPacket> quicPacketAllocator("quicPacketAllocator");
-ClassAllocator<QUICPacketLongHeader> quicPacketLongHeaderAllocator("quicPacketLongHeaderAllocator");
-ClassAllocator<QUICPacketShortHeader> quicPacketShortHeaderAllocator("quicPacketShortHeaderAllocator");
-
-static constexpr int LONG_HDR_OFFSET_CONNECTION_ID = 6;
-static constexpr int LONG_HDR_OFFSET_VERSION       = 1;
-
 //
-// QUICPacketHeader
+// QUICPacket
 //
-const uint8_t *
-QUICPacketHeader::buf()
-{
-  if (this->_buf) {
-    return this->_buf.get();
-  } else {
-    // TODO Reuse serialzied data if nothing has changed
-    this->store(this->_serialized, &this->_buf_len);
-    if (this->_buf_len > MAX_PACKET_HEADER_LEN) {
-      ink_assert(!"Serialized packet header is too long");
-    }
+QUICPacket::QUICPacket() {}
 
-    this->_buf_len += this->_payload_length;
-    return this->_serialized;
-  }
-}
+QUICPacket::QUICPacket(bool ack_eliciting, bool probing) : _is_ack_eliciting(ack_eliciting), _is_probing_packet(probing) {}
 
-const IpEndpoint &
-QUICPacketHeader::from() const
+QUICPacket::~QUICPacket() {}
+
+QUICKeyPhase
+QUICPacket::key_phase() const
 {
-  return this->_from;
+  ink_assert(!"This function should not be called");
+  return QUICKeyPhase::INITIAL;
 }
 
-const IpEndpoint &
-QUICPacketHeader::to() const
+bool
+QUICPacket::is_ack_eliciting() const
 {
-  return this->_to;
+  return this->_is_ack_eliciting;
 }
 
 bool
-QUICPacketHeader::is_crypto_packet() const
+QUICPacket::is_probing_packet() const
 {
-  return false;
+  return this->_is_probing_packet;
 }
 
 uint16_t
-QUICPacketHeader::packet_size() const
+QUICPacket::header_size() const
 {
-  return this->_buf_len;
-}
+  uint16_t size = 0;
 
-QUICPacketHeaderUPtr
-QUICPacketHeader::load(const IpEndpoint from, const IpEndpoint to, ats_unique_buf buf, size_t len, QUICPacketNumber base)
-{
-  QUICPacketHeaderUPtr header = QUICPacketHeaderUPtr(nullptr, &QUICPacketHeaderDeleter::delete_null_header);
-  if (QUICInvariants::is_long_header(buf.get())) {
-    QUICPacketLongHeader *long_header = quicPacketLongHeaderAllocator.alloc();
-    new (long_header) QUICPacketLongHeader(from, to, std::move(buf), len, base);
-    header = QUICPacketHeaderUPtr(long_header, &QUICPacketHeaderDeleter::delete_long_header);
-  } else {
-    QUICPacketShortHeader *short_header = quicPacketShortHeaderAllocator.alloc();
-    new (short_header) QUICPacketShortHeader(from, to, std::move(buf), len, base);
-    header = QUICPacketHeaderUPtr(short_header, &QUICPacketHeaderDeleter::delete_short_header);
+  for (auto b = this->header_block(); b; b = b->next) {
+    size += b->size();
   }
-  return header;
+
+  return size;
 }
 
-QUICPacketHeaderUPtr
-QUICPacketHeader::build(QUICPacketType type, QUICKeyPhase key_phase, QUICConnectionId destination_cid, QUICConnectionId source_cid,
-                        QUICPacketNumber packet_number, QUICPacketNumber base_packet_number, QUICVersion version, bool crypto,
-                        ats_unique_buf payload, size_t len)
+uint16_t
+QUICPacket::payload_length() const
 {
-  QUICPacketLongHeader *long_header = quicPacketLongHeaderAllocator.alloc();
-  new (long_header) QUICPacketLongHeader(type, key_phase, destination_cid, source_cid, packet_number, base_packet_number, version,
-                                         crypto, std::move(payload), len);
-  return QUICPacketHeaderUPtr(long_header, &QUICPacketHeaderDeleter::delete_long_header);
+  uint16_t size = 0;
+
+  for (auto b = this->payload_block(); b; b = b->next) {
+    size += b->size();
+  }
+
+  return size;
 }
 
-QUICPacketHeaderUPtr
-QUICPacketHeader::build(QUICPacketType type, QUICKeyPhase key_phase, QUICConnectionId destination_cid, QUICConnectionId source_cid,
-                        QUICPacketNumber packet_number, QUICPacketNumber base_packet_number, QUICVersion version, bool crypto,
-                        ats_unique_buf payload, size_t len, ats_unique_buf token, size_t token_len)
+uint16_t
+QUICPacket::size() const
 {
-  QUICPacketLongHeader *long_header = quicPacketLongHeaderAllocator.alloc();
-  new (long_header) QUICPacketLongHeader(type, key_phase, destination_cid, source_cid, packet_number, base_packet_number, version,
-                                         crypto, std::move(payload), len, std::move(token), token_len);
-  return QUICPacketHeaderUPtr(long_header, &QUICPacketHeaderDeleter::delete_long_header);
+  return this->header_size() + this->payload_length();
 }
 
-QUICPacketHeaderUPtr
-QUICPacketHeader::build(QUICPacketType type, QUICKeyPhase key_phase, QUICVersion version, QUICConnectionId destination_cid,
-                        QUICConnectionId source_cid, QUICConnectionId original_dcid, ats_unique_buf retry_token,
-                        size_t retry_token_len)
+void
+QUICPacket::store(uint8_t *buf, size_t *len) const
 {
-  QUICPacketLongHeader *long_header = quicPacketLongHeaderAllocator.alloc();
-  new (long_header) QUICPacketLongHeader(type, key_phase, version, destination_cid, source_cid, original_dcid,
-                                         std::move(retry_token), retry_token_len);
-  return QUICPacketHeaderUPtr(long_header, &QUICPacketHeaderDeleter::delete_long_header);
+  size_t written = 0;
+  Ptr<IOBufferBlock> block;
+
+  block = this->header_block();
+  while (block) {
+    memcpy(buf + written, block->start(), block->size());
+    written += block->size();
+    block = block->next;
+  }
+
+  block = this->payload_block();
+  while (block) {
+    memcpy(buf + written, block->start(), block->size());
+    written += block->size();
+    block = block->next;
+  }
+
+  *len = written;
 }
 
-QUICPacketHeaderUPtr
-QUICPacketHeader::build(QUICPacketType type, QUICKeyPhase key_phase, QUICPacketNumber packet_number,
-                        QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len)
+uint8_t
+QUICPacket::calc_packet_number_len(QUICPacketNumber num, QUICPacketNumber base)
 {
-  QUICPacketShortHeader *short_header = quicPacketShortHeaderAllocator.alloc();
-  new (short_header) QUICPacketShortHeader(type, key_phase, packet_number, base_packet_number, std::move(payload), len);
-  return QUICPacketHeaderUPtr(short_header, &QUICPacketHeaderDeleter::delete_short_header);
+  uint64_t d  = (num - base) * 2;
+  uint8_t len = 0;
+
+  if (d > 0xFFFFFF) {
+    len = 4;
+  } else if (d > 0xFFFF) {
+    len = 3;
+  } else if (d > 0xFF) {
+    len = 2;
+  } else {
+    len = 1;
+  }
+
+  return len;
 }
 
-QUICPacketHeaderUPtr
-QUICPacketHeader::build(QUICPacketType type, QUICKeyPhase key_phase, QUICConnectionId connection_id, QUICPacketNumber packet_number,
-                        QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len)
+bool
+QUICPacket::encode_packet_number(QUICPacketNumber &dst, QUICPacketNumber src, size_t len)
 {
-  QUICPacketShortHeader *short_header = quicPacketShortHeaderAllocator.alloc();
-  new (short_header)
-    QUICPacketShortHeader(type, key_phase, connection_id, packet_number, base_packet_number, std::move(payload), len);
-  return QUICPacketHeaderUPtr(short_header, &QUICPacketHeaderDeleter::delete_short_header);
+  uint64_t mask = 0;
+  switch (len) {
+  case 1:
+    mask = 0xFF;
+    break;
+  case 2:
+    mask = 0xFFFF;
+    break;
+  case 3:
+    mask = 0xFFFFFF;
+    break;
+  case 4:
+    mask = 0xFFFFFFFF;
+    break;
+  default:
+    ink_assert(!"len must be 1, 2, or 4");
+    return false;
+  }
+  dst = src & mask;
+
+  return true;
 }
 
-QUICPacketHeaderUPtr
-QUICPacketHeader::clone() const
+bool
+QUICPacket::decode_packet_number(QUICPacketNumber &dst, QUICPacketNumber src, size_t len, QUICPacketNumber largest_acked)
 {
-  return QUICPacketHeaderUPtr(nullptr, &QUICPacketHeaderDeleter::delete_null_header);
+  ink_assert(len == 1 || len == 2 || len == 3 || len == 4);
+
+  uint64_t maximum_diff = 0;
+  switch (len) {
+  case 1:
+    maximum_diff = 0x100;
+    break;
+  case 2:
+    maximum_diff = 0x10000;
+    break;
+  case 3:
+    maximum_diff = 0x1000000;
+    break;
+  case 4:
+    maximum_diff = 0x100000000;
+    break;
+  default:
+    ink_assert(!"len must be 1, 2, 3 or 4");
+  }
+  QUICPacketNumber base       = largest_acked & (~(maximum_diff - 1));
+  QUICPacketNumber candidate1 = base + src;
+  QUICPacketNumber candidate2 = base + src + maximum_diff;
+  QUICPacketNumber expected   = largest_acked + 1;
+
+  if (((candidate1 > expected) ? (candidate1 - expected) : (expected - candidate1)) <
+      ((candidate2 > expected) ? (candidate2 - expected) : (expected - candidate2))) {
+    dst = candidate1;
+  } else {
+    dst = candidate2;
+  }
+
+  return true;
 }
 
 //
-// QUICPacketLongHeader
+// QUICPacketR
 //
 
-QUICPacketLongHeader::QUICPacketLongHeader(const IpEndpoint from, const IpEndpoint to, ats_unique_buf buf, size_t len,
-                                           QUICPacketNumber base)
-  : QUICPacketHeader(from, to, std::move(buf), len, base)
+QUICPacketR::QUICPacketR(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to) : _udp_con(udp_con), _from(from), _to(to) {}
+
+UDPConnection *
+QUICPacketR::udp_con() const
 {
-  this->_key_phase = QUICTypeUtil::key_phase(this->type());
-  uint8_t *raw_buf = this->_buf.get();
+  return this->_udp_con;
+}
 
-  uint8_t dcil = 0;
-  uint8_t scil = 0;
-  QUICPacketLongHeader::dcil(dcil, raw_buf, len);
-  QUICPacketLongHeader::scil(scil, raw_buf, len);
+const IpEndpoint &
+QUICPacketR::from() const
+{
+  return this->_from;
+}
 
-  size_t offset          = LONG_HDR_OFFSET_CONNECTION_ID;
-  this->_destination_cid = {raw_buf + offset, dcil};
-  offset += dcil + 1;
-  this->_source_cid = {raw_buf + offset, scil};
-  offset += scil;
+const IpEndpoint &
+QUICPacketR::to() const
+{
+  return this->_to;
+}
 
-  if (this->type() != QUICPacketType::VERSION_NEGOTIATION) {
-    if (this->type() == QUICPacketType::RETRY) {
-      uint8_t odcil = raw_buf[offset];
-      offset += 1;
+bool
+QUICPacketR::type(QUICPacketType &type, const uint8_t *packet, size_t packet_len)
+{
+  if (packet_len < 1) {
+    return false;
+  }
 
-      this->_original_dcid = {raw_buf + offset, odcil};
-      offset += odcil;
-    } else {
-      if (this->type() == QUICPacketType::INITIAL) {
-        // Token Length Field
-        this->_token_len = QUICIntUtil::read_QUICVariableInt(raw_buf + offset);
-        offset += QUICVariableInt::size(raw_buf + offset);
-        // Token Field
-        this->_token_offset = offset;
-        offset += this->_token_len;
-      }
+  if (QUICInvariants::is_long_header(packet)) {
+    return QUICLongHeaderPacketR::type(type, packet, packet_len);
+  } else {
+    type = QUICPacketType::PROTECTED;
+    return true;
+  }
+}
+
+bool
+QUICPacketR::read_essential_info(Ptr<IOBufferBlock> block, QUICPacketType &type, QUICVersion &version, QUICConnectionId &dcid,
+                                 QUICConnectionId &scid, QUICPacketNumber &packet_number, QUICPacketNumber base_packet_number,
+                                 QUICKeyPhase &key_phase)
+{
+  uint8_t tmp[47 + 64];
+  IOBufferReader reader;
+  reader.block = block;
+  int64_t len  = std::min(static_cast<int64_t>(sizeof(tmp)), reader.read_avail());
 
-      // Length Field
-      offset += QUICVariableInt::size(raw_buf + offset);
+  if (len < 10) {
+    return false;
+  }
 
-      // PN Field
-      int pn_len = QUICTypeUtil::read_QUICPacketNumberLen(raw_buf);
-      QUICPacket::decode_packet_number(this->_packet_number, QUICTypeUtil::read_QUICPacketNumber(raw_buf + offset, pn_len), pn_len,
-                                       this->_base_packet_number);
-      offset += pn_len;
+  reader.memcpy(tmp, 1, 0);
+  if (QUICInvariants::is_long_header(tmp)) {
+    reader.memcpy(tmp, len, 0);
+    type = static_cast<QUICPacketType>((0x30 & tmp[0]) >> 4);
+    QUICInvariants::version(version, tmp, len);
+    if (version == 0x00) {
+      type = QUICPacketType::VERSION_NEGOTIATION;
+    }
+    if (!QUICInvariants::dcid(dcid, tmp, len) || !QUICInvariants::scid(scid, tmp, len)) {
+      return false;
+    }
+    if (type != QUICPacketType::RETRY) {
+      int packet_number_len = QUICTypeUtil::read_QUICPacketNumberLen(tmp);
+      size_t length_offset  = 7 + dcid.length() + scid.length();
+      if (length_offset >= static_cast<uint64_t>(len)) {
+        return false;
+      }
+      uint64_t value;
+      size_t field_len;
+      QUICVariableInt::decode(value, field_len, tmp + length_offset);
+      switch (type) {
+      case QUICPacketType::INITIAL:
+        length_offset += field_len + value;
+        if (length_offset >= static_cast<uint64_t>(len)) {
+          return false;
+        }
+        QUICVariableInt::decode(value, field_len, tmp + length_offset);
+        if (length_offset + field_len >= static_cast<uint64_t>(len)) {
+          return false;
+        }
+        if (length_offset + field_len + packet_number_len > static_cast<uint64_t>(len)) {
+          return false;
+        }
+        packet_number = QUICTypeUtil::read_QUICPacketNumber(tmp + length_offset + field_len, packet_number_len);
+        key_phase     = QUICKeyPhase::INITIAL;
+        break;
+      case QUICPacketType::ZERO_RTT_PROTECTED:
+        if (length_offset + field_len + packet_number_len >= static_cast<uint64_t>(len)) {
+          return false;
+        }
+        packet_number = QUICTypeUtil::read_QUICPacketNumber(tmp + length_offset + field_len, packet_number_len);
+        key_phase     = QUICKeyPhase::ZERO_RTT;
+        break;
+      case QUICPacketType::HANDSHAKE:
+        if (length_offset + field_len + packet_number_len >= static_cast<uint64_t>(len)) {
+          return false;
+        }
+        packet_number = QUICTypeUtil::read_QUICPacketNumber(tmp + length_offset + field_len, packet_number_len);
+        key_phase     = QUICKeyPhase::INITIAL;
+        break;
+      case QUICPacketType::VERSION_NEGOTIATION:
+        break;
+      default:
+        break;
+      }
+    } else {
+      packet_number = 0;
+    }
+  } else {
+    len = std::min(static_cast<int64_t>(25), len);
+    reader.memcpy(tmp, len, 0);
+    type = QUICPacketType::PROTECTED;
+    QUICInvariants::dcid(dcid, tmp, len);
+    int packet_number_len = QUICTypeUtil::read_QUICPacketNumberLen(tmp);
+    if (tmp[0] & 0x04) {
+      key_phase = QUICKeyPhase::PHASE_1;
+    } else {
+      key_phase = QUICKeyPhase::PHASE_0;
     }
+    packet_number = QUICTypeUtil::read_QUICPacketNumber(tmp + 1 + dcid.length(), packet_number_len);
   }
+  return true;
+}
 
-  this->_payload_offset = offset;
-  this->_payload_length = len - this->_payload_offset;
+//
+// QUICLongHeaderPacket
+//
+QUICLongHeaderPacket::QUICLongHeaderPacket(QUICVersion version, QUICConnectionId dcid, QUICConnectionId scid, bool ack_eliciting,
+                                           bool probing, bool crypto)
+  : QUICPacket(ack_eliciting, probing), _version(version), _dcid(dcid), _scid(scid), _is_crypto_packet(crypto)
+{
 }
 
-QUICPacketLongHeader::QUICPacketLongHeader(QUICPacketType type, QUICKeyPhase key_phase, const QUICConnectionId &destination_cid,
-                                           const QUICConnectionId &source_cid, QUICPacketNumber packet_number,
-                                           QUICPacketNumber base_packet_number, QUICVersion version, bool crypto,
-                                           ats_unique_buf buf, size_t len, ats_unique_buf token, size_t token_len)
-  : QUICPacketHeader(type, packet_number, base_packet_number, true, version, std::move(buf), len, key_phase),
-    _destination_cid(destination_cid),
-    _source_cid(source_cid),
-    _token_len(token_len),
-    _token(std::move(token)),
-    _is_crypto_packet(crypto)
+QUICConnectionId
+QUICLongHeaderPacket::destination_cid() const
 {
-  if (this->_type == QUICPacketType::VERSION_NEGOTIATION) {
-    this->_buf_len =
-      LONG_HDR_OFFSET_CONNECTION_ID + this->_destination_cid.length() + 1 + this->_source_cid.length() + this->_payload_length;
-  } else {
-    this->buf();
-  }
+  return this->_dcid;
 }
 
-QUICPacketLongHeader::QUICPacketLongHeader(QUICPacketType type, QUICKeyPhase key_phase, QUICVersion version,
-                                           const QUICConnectionId &destination_cid, const QUICConnectionId &source_cid,
-                                           const QUICConnectionId &original_dcid, ats_unique_buf retry_token,
-                                           size_t retry_token_len)
-  : QUICPacketHeader(type, 0, 0, true, version, std::move(retry_token), retry_token_len, key_phase),
-    _destination_cid(destination_cid),
-    _source_cid(source_cid),
-    _original_dcid(original_dcid)
+QUICConnectionId
+QUICLongHeaderPacket::source_cid() const
+{
+  return this->_scid;
+}
 
+uint16_t
+QUICLongHeaderPacket::payload_length() const
 {
-  // this->_buf_len will be set
-  this->buf();
+  return this->_payload_length;
 }
 
-QUICPacketType
-QUICPacketLongHeader::type() const
+QUICVersion
+QUICLongHeaderPacket::version() const
+{
+  return this->_version;
+}
+
+size_t
+QUICLongHeaderPacket::_write_common_header(uint8_t *buf) const
 {
-  if (this->_buf) {
-    QUICPacketType type = QUICPacketType::UNINITIALIZED;
-    QUICPacketLongHeader::type(type, this->_buf.get(), this->_buf_len);
-    return type;
+  size_t n;
+  size_t len = 0;
+
+  buf[0] = 0xC0;
+  buf[0] += static_cast<uint8_t>(this->type()) << 4;
+  len += 1;
+
+  QUICTypeUtil::write_QUICVersion(this->_version, buf + len, &n);
+  len += n;
+
+  // DICD
+  if (this->_dcid != QUICConnectionId::ZERO()) {
+    // Len
+    buf[len] = this->_dcid.length();
+    len += 1;
+
+    // ID
+    QUICTypeUtil::write_QUICConnectionId(this->_dcid, buf + len, &n);
+    len += n;
+  } else {
+    buf[len] = 0;
+    len += 1;
+  }
+
+  // SCID
+  if (this->_scid != QUICConnectionId::ZERO()) {
+    // Len
+    buf[len] = this->_scid.length();
+    len += 1;
+
+    // ID
+    QUICTypeUtil::write_QUICConnectionId(this->_scid, buf + len, &n);
+    len += n;
   } else {
-    return this->_type;
+    buf[len] = 0;
+    len += 1;
   }
+
+  return len;
 }
 
 bool
-QUICPacketLongHeader::is_crypto_packet() const
+QUICLongHeaderPacket::is_crypto_packet() const
 {
   return this->_is_crypto_packet;
 }
 
+//
+// QUICLongHeaderPacketR
+//
+QUICLongHeaderPacketR::QUICLongHeaderPacketR(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to, Ptr<IOBufferBlock> blocks)
+  : QUICPacketR(udp_con, from, to)
+{
+  IOBufferReader reader;
+  uint8_t data[47];
+
+  reader.block     = blocks;
+  int64_t data_len = reader.read(data, sizeof(data));
+
+  QUICLongHeaderPacketR::version(this->_version, data, data_len);
+}
+
+QUICVersion
+QUICLongHeaderPacketR::version() const
+{
+  return this->_version;
+}
+
+QUICConnectionId
+QUICLongHeaderPacketR::source_cid() const
+{
+  return this->_scid;
+}
+
+QUICConnectionId
+QUICLongHeaderPacketR::destination_cid() const
+{
+  return this->_dcid;
+}
+
 bool
-QUICPacketLongHeader::type(QUICPacketType &type, const uint8_t *packet, size_t packet_len)
+QUICLongHeaderPacketR::type(QUICPacketType &type, const uint8_t *packet, size_t packet_len)
 {
   if (packet_len < 1) {
     return false;
   }
 
   QUICVersion version;
-  if (QUICPacketLongHeader::version(version, packet, packet_len) && version == 0x00) {
+  if (QUICLongHeaderPacketR::version(version, packet, packet_len) && version == 0x00) {
     type = QUICPacketType::VERSION_NEGOTIATION;
   } else {
     uint8_t raw_type = (packet[0] & 0x30) >> 4;
@@ -284,7 +464,7 @@ QUICPacketLongHeader::type(QUICPacketType &type, const uint8_t *packet, size_t p
 }
 
 bool
-QUICPacketLongHeader::version(QUICVersion &version, const uint8_t *packet, size_t packet_len)
+QUICLongHeaderPacketR::version(QUICVersion &version, const uint8_t *packet, size_t packet_len)
 {
   if (packet_len < 5) {
     return false;
@@ -295,101 +475,70 @@ QUICPacketLongHeader::version(QUICVersion &version, const uint8_t *packet, size_
 }
 
 bool
-QUICPacketLongHeader::dcil(uint8_t &dcil, const uint8_t *packet, size_t packet_len)
-{
-  if (QUICInvariants::dcil(dcil, packet, packet_len)) {
-    return true;
-  } else {
-    return false;
-  }
-}
-
-bool
-QUICPacketLongHeader::scil(uint8_t &scil, const uint8_t *packet, size_t packet_len)
-{
-  if (QUICInvariants::scil(scil, packet, packet_len)) {
-    return true;
-  } else {
-    return false;
-  }
-}
-
-bool
-QUICPacketLongHeader::token_length(size_t &token_length, uint8_t &field_len, size_t &token_length_filed_offset,
-                                   const uint8_t *packet, size_t packet_len)
+QUICLongHeaderPacketR::key_phase(QUICKeyPhase &phase, const uint8_t *packet, size_t packet_len)
 {
   QUICPacketType type = QUICPacketType::UNINITIALIZED;
-  QUICPacketLongHeader::type(type, packet, packet_len);
-
-  if (type != QUICPacketType::INITIAL) {
-    token_length = 0;
-    field_len    = 0;
-
-    return true;
-  }
-
-  uint8_t dcil, scil;
-  QUICPacketLongHeader::dcil(dcil, packet, packet_len);
-  QUICPacketLongHeader::scil(scil, packet, packet_len);
-
-  token_length_filed_offset = LONG_HDR_OFFSET_CONNECTION_ID + dcil + 1 + scil;
-  if (token_length_filed_offset >= packet_len) {
-    return false;
-  }
-
-  token_length = QUICIntUtil::read_QUICVariableInt(packet + token_length_filed_offset);
-  field_len    = QUICVariableInt::size(packet + token_length_filed_offset);
-
+  QUICLongHeaderPacketR::type(type, packet, packet_len);
+  phase = QUICTypeUtil::key_phase(type);
   return true;
 }
 
 bool
-QUICPacketLongHeader::length(size_t &length, uint8_t &length_field_len, size_t &length_field_offset, const uint8_t *packet,
-                             size_t packet_len)
+QUICLongHeaderPacketR::length(size_t &length, uint8_t &length_field_len, size_t &length_field_offset, const uint8_t *packet,
+                              size_t packet_len)
 {
+  // FIXME This is not great because each packet types have different formats.
+  // We should remove this function and have length() on each packet type classes instead.
+
   uint8_t dcil;
-  if (!QUICPacketLongHeader::dcil(dcil, packet, packet_len)) {
+  if (!QUICInvariants::dcil(dcil, packet, packet_len)) {
     return false;
   }
 
   uint8_t scil;
-  if (!QUICPacketLongHeader::scil(scil, packet, packet_len)) {
+  if (!QUICInvariants::scil(scil, packet, packet_len)) {
     return false;
   }
 
-  // Token Length (i) + Token (*) (for INITIAL packet)
-  size_t token_length              = 0;
-  uint8_t token_length_field_len   = 0;
-  size_t token_length_field_offset = 0;
-  if (!QUICPacketLongHeader::token_length(token_length, token_length_field_len, token_length_field_offset, packet, packet_len)) {
-    return false;
+  length_field_offset = LONG_HDR_OFFSET_CONNECTION_ID + dcil + 1 + scil;
+
+  QUICPacketType type = QUICPacketType::UNINITIALIZED;
+  QUICLongHeaderPacketR::type(type, packet, packet_len);
+  if (type == QUICPacketType::INITIAL) {
+    // Token Length (i) + Token (*) (for INITIAL packet)
+    size_t token_length              = 0;
+    uint8_t token_length_field_len   = 0;
+    size_t token_length_field_offset = 0;
+    if (!QUICInitialPacketR::token_length(token_length, token_length_field_len, token_length_field_offset, packet, packet_len)) {
+      return false;
+    }
+    length_field_offset += token_length_field_len + token_length;
   }
 
   // Length (i)
-  length_field_offset = LONG_HDR_OFFSET_CONNECTION_ID + dcil + 1 + scil + token_length_field_len + token_length;
   if (length_field_offset >= packet_len) {
     return false;
   }
 
   length_field_len = QUICVariableInt::size(packet + length_field_offset);
-  length           = QUICIntUtil::read_QUICVariableInt(packet + length_field_offset);
+  length           = QUICIntUtil::read_QUICVariableInt(packet + length_field_offset, packet_len - length_field_offset);
 
   return true;
 }
 
 bool
-QUICPacketLongHeader::packet_number_offset(size_t &pn_offset, const uint8_t *packet, size_t packet_len)
+QUICLongHeaderPacketR::packet_length(size_t &packet_len, const uint8_t *buf, size_t buf_len)
 {
   size_t length;
   uint8_t length_field_len;
   size_t length_field_offset;
 
-  if (!QUICPacketLongHeader::length(length, length_field_len, length_field_offset, packet, packet_len)) {
+  if (!QUICLongHeaderPacketR::length(length, length_field_len, length_field_offset, buf, buf_len)) {
     return false;
   }
-  pn_offset = length_field_offset + length_field_len;
+  packet_len = length + length_field_offset + length_field_len;
 
-  if (pn_offset >= packet_len) {
+  if (packet_len > buf_len) {
     return false;
   }
 
@@ -397,639 +546,1297 @@ QUICPacketLongHeader::packet_number_offset(size_t &pn_offset, const uint8_t *pac
 }
 
 bool
-QUICPacketLongHeader::packet_length(size_t &packet_len, const uint8_t *buf, size_t buf_len)
+QUICLongHeaderPacketR::packet_number_offset(size_t &pn_offset, const uint8_t *packet, size_t packet_len)
 {
-  size_t length;
+  size_t dummy;
   uint8_t length_field_len;
   size_t length_field_offset;
 
-  if (!QUICPacketLongHeader::length(length, length_field_len, length_field_offset, buf, buf_len)) {
+  if (!QUICLongHeaderPacketR::length(dummy, length_field_len, length_field_offset, packet, packet_len)) {
     return false;
   }
-  packet_len = length + length_field_offset + length_field_len;
+  pn_offset = length_field_offset + length_field_len;
 
-  if (packet_len > buf_len) {
+  if (pn_offset >= packet_len) {
     return false;
   }
 
   return true;
 }
 
-bool
-QUICPacketLongHeader::key_phase(QUICKeyPhase &phase, const uint8_t *packet, size_t packet_len)
+//
+// QUICShortHeaderPacket
+//
+QUICShortHeaderPacket::QUICShortHeaderPacket(QUICConnectionId dcid, QUICPacketNumber packet_number,
+                                             QUICPacketNumber base_packet_number, QUICKeyPhase key_phase, bool ack_eliciting,
+                                             bool probing)
+  : QUICPacket(ack_eliciting, probing), _dcid(dcid), _packet_number(packet_number), _key_phase(key_phase)
 {
-  QUICPacketType type = QUICPacketType::UNINITIALIZED;
-  QUICPacketLongHeader::type(type, packet, packet_len);
-  phase = QUICTypeUtil::key_phase(type);
-  return true;
+  this->_packet_number_len = QUICPacket::calc_packet_number_len(packet_number, base_packet_number);
 }
 
-QUICConnectionId
-QUICPacketLongHeader::destination_cid() const
+QUICPacketType
+QUICShortHeaderPacket::type() const
 {
-  return this->_destination_cid;
+  return QUICPacketType::PROTECTED;
 }
 
-QUICConnectionId
-QUICPacketLongHeader::source_cid() const
+QUICKeyPhase
+QUICShortHeaderPacket::key_phase() const
 {
-  return this->_source_cid;
+  return this->_key_phase;
 }
 
 QUICConnectionId
-QUICPacketLongHeader::original_dcid() const
+QUICShortHeaderPacket::destination_cid() const
 {
-  return this->_original_dcid;
+  return this->_dcid;
 }
 
 QUICPacketNumber
-QUICPacketLongHeader::packet_number() const
+QUICShortHeaderPacket::packet_number() const
 {
   return this->_packet_number;
 }
 
-bool
-QUICPacketLongHeader::has_version() const
+uint16_t
+QUICShortHeaderPacket::payload_length() const
 {
-  return true;
+  return this->_payload_length;
 }
 
-bool
-QUICPacketLongHeader::is_valid() const
+Ptr<IOBufferBlock>
+QUICShortHeaderPacket::header_block() const
 {
-  if (this->_buf && this->_buf_len != this->_payload_offset + this->_payload_length) {
-    QUICDebug(this->_source_cid, this->_destination_cid,
-              "Invalid packet: packet_size(%zu) should be header_size(%zu) + payload_size(%zu)", this->_buf_len,
-              this->_payload_offset, this->_payload_length);
-    Warning("Invalid packet: packet_size(%zu) should be header_size(%zu) + payload_size(%zu)", this->_buf_len,
-            this->_payload_offset, this->_payload_length);
+  Ptr<IOBufferBlock> block;
+  size_t written_len = 0;
 
-    return false;
-  }
+  block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(1 + QUICConnectionId::MAX_LENGTH + 4, BUFFER_SIZE_INDEX_32K));
+  uint8_t *buf = reinterpret_cast<uint8_t *>(block->start());
 
-  return true;
-}
+  size_t n;
+  buf[0] = 0x40;
 
-QUICVersion
-QUICPacketLongHeader::version() const
-{
-  if (this->_buf) {
-    QUICVersion version = 0;
-    QUICPacketLongHeader::version(version, this->_buf.get(), this->_buf_len);
-    return version;
-  } else {
-    return this->_version;
-  }
-}
+  // Type
+  buf[0] = 0x40;
 
-const uint8_t *
-QUICPacketLongHeader::payload() const
-{
-  if (this->_buf) {
-    uint8_t *raw = this->_buf.get();
-    return raw + this->_payload_offset;
-  } else {
-    return this->_payload.get();
+  // TODO Spin Bit
+
+  // KeyPhase
+  if (this->_key_phase == QUICKeyPhase::PHASE_1) {
+    buf[0] |= 0x04;
   }
-}
 
-uint16_t
-QUICPacketHeader::payload_size() const
-{
-  return this->_payload_length;
-}
+  written_len += 1;
 
-const uint8_t *
-QUICPacketLongHeader::token() const
-{
-  if (this->_buf) {
-    uint8_t *raw = this->_buf.get();
-    return raw + this->_token_offset;
-  } else {
-    return this->_token.get();
+  // Destination Connection ID
+  if (this->_dcid != QUICConnectionId::ZERO()) {
+    QUICTypeUtil::write_QUICConnectionId(this->_dcid, buf + written_len, &n);
+    written_len += n;
   }
-}
 
-size_t
-QUICPacketLongHeader::token_len() const
-{
-  return this->_token_len;
-}
+  // Packet Number
+  QUICPacketNumber dst = 0;
+  size_t dst_len       = this->_packet_number_len;
+  QUICPacket::encode_packet_number(dst, this->_packet_number, dst_len);
+  QUICTypeUtil::write_QUICPacketNumber(dst, dst_len, buf + written_len, &n);
+  written_len += n;
 
-QUICKeyPhase
-QUICPacketLongHeader::key_phase() const
-{
-  return this->_key_phase;
+  // Packet Number Length
+  QUICTypeUtil::write_QUICPacketNumberLen(n, buf);
+
+  block->fill(written_len);
+
+  return block;
 }
 
-uint16_t
-QUICPacketLongHeader::size() const
+Ptr<IOBufferBlock>
+QUICShortHeaderPacket::payload_block() const
 {
-  return this->_buf_len - this->_payload_length;
+  return this->_payload_block;
 }
 
 void
-QUICPacketLongHeader::store(uint8_t *buf, size_t *len) const
+QUICShortHeaderPacket::attach_payload(Ptr<IOBufferBlock> payload, bool unprotected)
+{
+  this->_payload_block   = payload;
+  this->_payload_length  = 0;
+  Ptr<IOBufferBlock> tmp = payload;
+  while (tmp) {
+    this->_payload_length += tmp->size();
+    tmp = tmp->next;
+  }
+  if (unprotected) {
+    this->_payload_length += aead_tag_len;
+  }
+}
+
+//
+// QUICShortHeaderPacketR
+//
+QUICShortHeaderPacketR::QUICShortHeaderPacketR(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to, Ptr<IOBufferBlock> blocks,
+                                               QUICPacketNumber base_packet_number)
+  : QUICPacketR(udp_con, from, to)
 {
-  size_t n;
-  *len   = 0;
-  buf[0] = 0xC0;
-  buf[0] += static_cast<uint8_t>(this->_type) << 4;
-  if (this->_type == QUICPacketType::VERSION_NEGOTIATION) {
-    buf[0] |= rand();
+  size_t len = 0;
+  for (auto b = blocks; b; b = b->next) {
+    len += b->size();
   }
-  *len += 1;
 
-  QUICTypeUtil::write_QUICVersion(this->_version, buf + *len, &n);
-  *len += n;
+  Ptr<IOBufferBlock> concatinated_block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  concatinated_block->alloc(iobuffer_size_to_index(len, BUFFER_SIZE_INDEX_32K));
+  concatinated_block->fill(len);
 
-  // DICD
-  if (this->_destination_cid != QUICConnectionId::ZERO()) {
-    // Len
-    buf[*len] = this->_destination_cid.length();
-    *len += 1;
+  uint8_t *raw_buf = reinterpret_cast<uint8_t *>(concatinated_block->start());
 
-    // ID
-    QUICTypeUtil::write_QUICConnectionId(this->_destination_cid, buf + *len, &n);
-    *len += n;
-  } else {
-    buf[*len] = 0;
-    *len += 1;
+  size_t copied_len = 0;
+  for (auto b = blocks; b; b = b->next) {
+    memcpy(raw_buf + copied_len, b->start(), b->size());
+    copied_len += b->size();
   }
 
-  // SCID
-  if (this->_source_cid != QUICConnectionId::ZERO()) {
-    // Len
-    buf[*len] = this->_source_cid.length();
-    *len += 1;
-
-    // ID
-    QUICTypeUtil::write_QUICConnectionId(this->_source_cid, buf + *len, &n);
-    *len += n;
+  if (raw_buf[0] & 0x04) {
+    this->_key_phase = QUICKeyPhase::PHASE_1;
   } else {
-    buf[*len] = 0;
-    *len += 1;
+    this->_key_phase = QUICKeyPhase::PHASE_0;
   }
 
-  if (this->_type != QUICPacketType::VERSION_NEGOTIATION) {
-    if (this->_type == QUICPacketType::RETRY) {
-      // Original Destination Connection ID
-      if (this->_original_dcid != QUICConnectionId::ZERO()) {
-        // Len
-        buf[*len] = this->_original_dcid.length();
-        *len += 1;
-
-        // ID
-        QUICTypeUtil::write_QUICConnectionId(this->_original_dcid, buf + *len, &n);
-        *len += n;
-      } else {
-        buf[*len] = 0;
-        *len += 1;
-      }
-    } else {
-      if (this->_type == QUICPacketType::INITIAL) {
-        // Token Length Field
-        QUICIntUtil::write_QUICVariableInt(this->_token_len, buf + *len, &n);
-        *len += n;
-
-        // Token Field
-        memcpy(buf + *len, this->token(), this->token_len());
-        *len += this->token_len();
-      }
-
-      QUICPacketNumber pn = 0;
-      size_t pn_len       = 4;
-      QUICPacket::encode_packet_number(pn, this->_packet_number, pn_len);
-
-      if (pn > 0x7FFFFF) {
-        pn_len = 4;
-      } else if (pn > 0x7FFF) {
-        pn_len = 3;
-      } else if (pn > 0x7F) {
-        pn_len = 2;
-      } else {
-        pn_len = 1;
-      }
-
-      if (this->_type != QUICPacketType::RETRY) {
-        // PN Len field
-        QUICTypeUtil::write_QUICPacketNumberLen(pn_len, buf);
-      }
-
-      // Length Field
-      QUICIntUtil::write_QUICVariableInt(pn_len + this->_payload_length + aead_tag_len, buf + *len, &n);
-      *len += n;
+  QUICInvariants::dcid(this->_dcid, raw_buf, len);
 
-      // PN Field
-      QUICTypeUtil::write_QUICPacketNumber(pn, pn_len, buf + *len, &n);
-      *len += n;
-    }
+  int offset               = 1 + this->_dcid.length();
+  this->_packet_number_len = QUICTypeUtil::read_QUICPacketNumberLen(raw_buf);
+  QUICPacketNumber src     = QUICTypeUtil::read_QUICPacketNumber(raw_buf + offset, this->_packet_number_len);
+  QUICPacket::decode_packet_number(this->_packet_number, src, this->_packet_number_len, base_packet_number);
+  offset += this->_packet_number_len;
 
-    // Payload will be stored
-  }
+  this->_header_block          = concatinated_block->clone();
+  this->_header_block->_end    = this->_header_block->_start + offset;
+  this->_header_block->next    = nullptr;
+  this->_payload_block         = concatinated_block->clone();
+  this->_payload_block->_start = this->_payload_block->_start + offset;
 }
 
-//
-// QUICPacketShortHeader
-//
-
-QUICPacketShortHeader::QUICPacketShortHeader(const IpEndpoint from, const IpEndpoint to, ats_unique_buf buf, size_t len,
-                                             QUICPacketNumber base)
-  : QUICPacketHeader(from, to, std::move(buf), len, base)
+QUICPacketType
+QUICShortHeaderPacketR::type() const
 {
-  QUICInvariants::dcid(this->_connection_id, this->_buf.get(), len);
-
-  int offset               = 1 + this->_connection_id.length();
-  this->_packet_number_len = QUICTypeUtil::read_QUICPacketNumberLen(this->_buf.get());
-  QUICPacketNumber src     = QUICTypeUtil::read_QUICPacketNumber(this->_buf.get() + offset, this->_packet_number_len);
-  QUICPacket::decode_packet_number(this->_packet_number, src, this->_packet_number_len, this->_base_packet_number);
-  this->_payload_length = len - (1 + QUICConnectionId::SCID_LEN + this->_packet_number_len);
+  return QUICPacketType::PROTECTED;
 }
 
-QUICPacketShortHeader::QUICPacketShortHeader(QUICPacketType type, QUICKeyPhase key_phase, QUICPacketNumber packet_number,
-                                             QUICPacketNumber base_packet_number, ats_unique_buf buf, size_t len)
+QUICKeyPhase
+QUICShortHeaderPacketR::key_phase() const
 {
-  this->_type               = type;
-  this->_key_phase          = key_phase;
-  this->_packet_number      = packet_number;
-  this->_base_packet_number = base_packet_number;
-  this->_packet_number_len  = QUICPacket::calc_packet_number_len(packet_number, base_packet_number);
-  this->_payload            = std::move(buf);
-  this->_payload_length     = len;
+  return this->_key_phase;
 }
 
-QUICPacketShortHeader::QUICPacketShortHeader(QUICPacketType type, QUICKeyPhase key_phase, const QUICConnectionId &connection_id,
-                                             QUICPacketNumber packet_number, QUICPacketNumber base_packet_number,
-                                             ats_unique_buf buf, size_t len)
+QUICPacketNumber
+QUICShortHeaderPacketR::packet_number() const
 {
-  this->_type               = type;
-  this->_key_phase          = key_phase;
-  this->_connection_id      = connection_id;
-  this->_packet_number      = packet_number;
-  this->_base_packet_number = base_packet_number;
-  this->_packet_number_len  = QUICPacket::calc_packet_number_len(packet_number, base_packet_number);
-  this->_payload            = std::move(buf);
-  this->_payload_length     = len;
+  return this->_packet_number;
 }
 
-QUICPacketType
-QUICPacketShortHeader::type() const
+QUICConnectionId
+QUICShortHeaderPacketR::destination_cid() const
 {
-  QUICKeyPhase key_phase = this->key_phase();
-
-  switch (key_phase) {
-  case QUICKeyPhase::PHASE_0: {
-    return QUICPacketType::PROTECTED;
-  }
-  case QUICKeyPhase::PHASE_1: {
-    return QUICPacketType::PROTECTED;
-  }
-  default:
-    return QUICPacketType::STATELESS_RESET;
-  }
+  return this->_dcid;
 }
 
-QUICConnectionId
-QUICPacketShortHeader::destination_cid() const
+Ptr<IOBufferBlock>
+QUICShortHeaderPacketR::header_block() const
 {
-  if (this->_buf) {
-    QUICConnectionId dcid = QUICConnectionId::ZERO();
-    QUICInvariants::dcid(dcid, this->_buf.get(), this->_buf_len);
-    return dcid;
-  } else {
-    return _connection_id;
-  }
+  return this->_header_block;
 }
 
-QUICPacketNumber
-QUICPacketShortHeader::packet_number() const
+Ptr<IOBufferBlock>
+QUICShortHeaderPacketR::payload_block() const
 {
-  return this->_packet_number;
+  return this->_payload_block;
 }
 
-bool
-QUICPacketShortHeader::has_version() const
+void
+QUICShortHeaderPacketR::attach_payload(Ptr<IOBufferBlock> payload, bool unprotected)
 {
-  return false;
+  this->_payload_block = payload;
 }
 
 bool
-QUICPacketShortHeader::is_valid() const
+QUICShortHeaderPacketR::packet_number_offset(size_t &pn_offset, const uint8_t *packet, size_t packet_len, int dcil)
 {
+  pn_offset = 1 + dcil;
   return true;
 }
 
-QUICVersion
-QUICPacketShortHeader::version() const
+//
+// QUICStatelessResetPacket
+//
+QUICStatelessResetPacket::QUICStatelessResetPacket(QUICStatelessResetToken token, size_t maximum_size)
+  : QUICPacket(), _token(token), _maximum_size(maximum_size)
 {
-  return 0;
 }
 
-const uint8_t *
-QUICPacketShortHeader::payload() const
+QUICPacketType
+QUICStatelessResetPacket::type() const
 {
-  if (this->_buf) {
-    return this->_buf.get() + this->size();
-  } else {
-    return this->_payload.get();
-  }
+  return QUICPacketType::STATELESS_RESET;
 }
 
-QUICKeyPhase
-QUICPacketShortHeader::key_phase() const
+QUICConnectionId
+QUICStatelessResetPacket::destination_cid() const
 {
-  if (this->_buf) {
-    QUICKeyPhase phase = QUICKeyPhase::INITIAL;
-    QUICPacketShortHeader::key_phase(phase, this->_buf.get(), this->_buf_len);
-    return phase;
-  } else {
-    return this->_key_phase;
-  }
+  ink_assert(!"You should not need DCID of Stateless Reset Packet");
+  return QUICConnectionId::ZERO();
 }
 
-bool
-QUICPacketShortHeader::key_phase(QUICKeyPhase &phase, const uint8_t *packet, size_t packet_len)
+Ptr<IOBufferBlock>
+QUICStatelessResetPacket::header_block() const
 {
-  if (packet_len < 1) {
-    return false;
+  // Required shortest length is 38 bits however less than 41 bytes in total indicates this is stateless reset.
+  constexpr uint8_t MIN_UNPREDICTABLE_FIELD_LEN = 5 + 20;
+
+  std::random_device rnd;
+
+  Ptr<IOBufferBlock> block;
+  size_t written_len = 0;
+
+  size_t random_extra_length = rnd() & 0x07; // Extra 0 to 7 bytes
+
+  if (MIN_UNPREDICTABLE_FIELD_LEN + random_extra_length > this->_maximum_size) {
+    return block;
   }
-  if (packet[0] & 0x04) {
-    phase = QUICKeyPhase::PHASE_1;
-  } else {
-    phase = QUICKeyPhase::PHASE_0;
+
+  block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(MIN_UNPREDICTABLE_FIELD_LEN + random_extra_length, BUFFER_SIZE_INDEX_32K));
+  uint8_t *buf = reinterpret_cast<uint8_t *>(block->start());
+
+  // Generate random octets
+  for (int i = 0; i < MIN_UNPREDICTABLE_FIELD_LEN; ++i) {
+    buf[i] = static_cast<uint8_t>(rnd() & 0xFF);
   }
-  return true;
+  buf[0] = (buf[0] | 0x40) & 0x7f;
+  written_len += MIN_UNPREDICTABLE_FIELD_LEN;
+
+  block->fill(written_len);
+
+  return block;
 }
 
-bool
-QUICPacketShortHeader::packet_number_offset(size_t &pn_offset, const uint8_t *packet, size_t packet_len, int dcil)
+Ptr<IOBufferBlock>
+QUICStatelessResetPacket::payload_block() const
 {
-  pn_offset = 1 + dcil;
-  return true;
+  Ptr<IOBufferBlock> block;
+  size_t written_len = 0;
+
+  block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(QUICStatelessResetToken::LEN, BUFFER_SIZE_INDEX_32K));
+  uint8_t *buf = reinterpret_cast<uint8_t *>(block->start());
+
+  memcpy(buf, this->_token.buf(), QUICStatelessResetToken::LEN);
+  written_len += QUICStatelessResetToken::LEN;
+
+  block->fill(written_len);
+
+  return block;
 }
 
-/**
- * Header Length (doesn't include payload length)
- */
-uint16_t
-QUICPacketShortHeader::size() const
+QUICPacketNumber
+QUICStatelessResetPacket::packet_number() const
 {
-  uint16_t len = 1;
-  if (this->_connection_id != QUICConnectionId::ZERO()) {
-    len += this->_connection_id.length();
-  }
-  len += this->_packet_number_len;
+  ink_assert(!"You should not need packet number of Stateless Reset Packet");
+  return 0;
+}
 
-  return len;
+QUICStatelessResetToken
+QUICStatelessResetPacket::token() const
+{
+  return this->_token;
 }
 
-void
-QUICPacketShortHeader::store(uint8_t *buf, size_t *len) const
+//
+// QUICStatelessResetPacketR
+//
+QUICStatelessResetPacketR::QUICStatelessResetPacketR(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to,
+                                                     Ptr<IOBufferBlock> blocks)
+  : QUICPacketR(udp_con, from, to)
 {
-  size_t n;
-  *len   = 0;
-  buf[0] = 0x40;
-  if (this->_key_phase == QUICKeyPhase::PHASE_1) {
-    buf[0] |= 0x04;
-  }
-  *len += 1;
+}
 
-  if (this->_connection_id != QUICConnectionId::ZERO()) {
-    QUICTypeUtil::write_QUICConnectionId(this->_connection_id, buf + *len, &n);
-    *len += n;
-  }
+QUICPacketType
+QUICStatelessResetPacketR::type() const
+{
+  return QUICPacketType::STATELESS_RESET;
+}
 
-  QUICPacketNumber dst = 0;
-  size_t dst_len       = this->_packet_number_len;
-  QUICPacket::encode_packet_number(dst, this->_packet_number, dst_len);
-  QUICTypeUtil::write_QUICPacketNumber(dst, dst_len, buf + *len, &n);
-  *len += n;
+QUICPacketNumber
+QUICStatelessResetPacketR::packet_number() const
+{
+  ink_assert(!"You should not need packet number of Stateless Reset Packet");
+  return 0;
+}
 
-  QUICTypeUtil::write_QUICPacketNumberLen(n, buf);
+QUICConnectionId
+QUICStatelessResetPacketR::destination_cid() const
+{
+  ink_assert(!"You should not need DCID of Stateless Reset Packet");
+  return QUICConnectionId::ZERO();
 }
 
 //
-// QUICPacket
+// QUICVersionNegotiationPacket
 //
-
-QUICPacket::QUICPacket() {}
-
-QUICPacket::QUICPacket(UDPConnection *udp_con, QUICPacketHeaderUPtr header, ats_unique_buf payload, size_t payload_len)
-  : _udp_con(udp_con), _header(std::move(header)), _payload(std::move(payload)), _payload_size(payload_len)
+QUICVersionNegotiationPacket::QUICVersionNegotiationPacket(QUICConnectionId dcid, QUICConnectionId scid,
+                                                           const QUICVersion versions[], int nversions)
+  : QUICLongHeaderPacket(0, dcid, scid, false, false, false), _versions(versions), _nversions(nversions)
 {
 }
 
-QUICPacket::QUICPacket(QUICPacketHeaderUPtr header, ats_unique_buf payload, size_t payload_len, bool ack_eliciting, bool probing)
-  : _header(std::move(header)),
-    _payload(std::move(payload)),
-    _payload_size(payload_len),
-    _is_ack_eliciting(ack_eliciting),
-    _is_probing_packet(probing)
+QUICPacketType
+QUICVersionNegotiationPacket::type() const
 {
+  return QUICPacketType::VERSION_NEGOTIATION;
 }
 
-QUICPacket::~QUICPacket()
+QUICVersion
+QUICVersionNegotiationPacket::version() const
 {
-  this->_header = nullptr;
+  return 0;
 }
 
-const IpEndpoint &
-QUICPacket::from() const
+QUICPacketNumber
+QUICVersionNegotiationPacket::packet_number() const
 {
-  return this->_header->from();
+  ink_assert(!"You should not need packet number of Version Negotiation Packet");
+  return 0;
 }
 
-const IpEndpoint &
-QUICPacket::to() const
+uint16_t
+QUICVersionNegotiationPacket::payload_length() const
 {
-  return this->_header->to();
+  uint16_t size = 0;
+
+  for (auto b = this->payload_block(); b; b = b->next) {
+    size += b->size();
+  }
+
+  return size;
 }
 
-UDPConnection *
-QUICPacket::udp_con() const
+Ptr<IOBufferBlock>
+QUICVersionNegotiationPacket::header_block() const
 {
-  return this->_udp_con;
+  Ptr<IOBufferBlock> block;
+  size_t written_len = 0;
+
+  block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(2048, BUFFER_SIZE_INDEX_32K));
+  uint8_t *buf = reinterpret_cast<uint8_t *>(block->start());
+
+  // Common Long Header
+  written_len += this->_write_common_header(buf + written_len);
+
+  // Overwrite the first byte
+  buf[0] = 0x80 | rand();
+
+  block->fill(written_len);
+
+  return block;
 }
 
-/**
- * When packet is "Short Header Packet", QUICPacket::type() will return 1-RTT Protected (key phase 0)
- * or 1-RTT Protected (key phase 1)
- */
-QUICPacketType
-QUICPacket::type() const
+Ptr<IOBufferBlock>
+QUICVersionNegotiationPacket::payload_block() const
 {
-  return this->_header->type();
+  Ptr<IOBufferBlock> block;
+  uint8_t *buf;
+  size_t written_len = 0;
+  size_t n;
+
+  block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(sizeof(QUICVersion) * (this->_nversions + 1), BUFFER_SIZE_INDEX_32K));
+  buf = reinterpret_cast<uint8_t *>(block->start());
+
+  for (auto i = 0; i < this->_nversions; ++i) {
+    QUICTypeUtil::write_QUICVersion(*(this->_versions + i), buf + written_len, &n);
+    written_len += n;
+  }
+
+  // [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);
+  written_len += n;
+
+  block->fill(written_len);
+
+  return block;
 }
 
-QUICConnectionId
-QUICPacket::destination_cid() const
+const QUICVersion *
+QUICVersionNegotiationPacket::versions() const
 {
-  return this->_header->destination_cid();
+  return this->_versions;
 }
 
-QUICConnectionId
-QUICPacket::source_cid() const
+int
+QUICVersionNegotiationPacket::nversions() const
 {
-  return this->_header->source_cid();
+  return this->_nversions;
 }
 
-QUICPacketNumber
-QUICPacket::packet_number() const
+//
+// QUICVersionNegotiationPacketR
+//
+QUICVersionNegotiationPacketR::QUICVersionNegotiationPacketR(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to,
+                                                             Ptr<IOBufferBlock> blocks)
+  : QUICLongHeaderPacketR(udp_con, from, to, blocks)
 {
-  return this->_header->packet_number();
+  size_t len = 0;
+  for (auto b = blocks; b; b = b->next) {
+    len += b->size();
+  }
+
+  Ptr<IOBufferBlock> concatinated_block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  concatinated_block->alloc(iobuffer_size_to_index(len, BUFFER_SIZE_INDEX_32K));
+  concatinated_block->fill(len);
+
+  uint8_t *raw_buf = reinterpret_cast<uint8_t *>(concatinated_block->start());
+
+  size_t copied_len = 0;
+  for (auto b = blocks; b; b = b->next) {
+    memcpy(raw_buf + copied_len, b->start(), b->size());
+    copied_len += b->size();
+  }
+
+  uint8_t dcil = 0;
+  uint8_t scil = 0;
+  QUICInvariants::dcil(dcil, raw_buf, len);
+  QUICInvariants::scil(scil, raw_buf, len);
+
+  size_t offset = LONG_HDR_OFFSET_CONNECTION_ID;
+  this->_dcid   = {raw_buf + offset, dcil};
+  offset += dcil + 1;
+  this->_scid = {raw_buf + offset, scil};
+  offset += scil;
+
+  this->_versions  = raw_buf + offset;
+  this->_nversions = (len - offset) / sizeof(QUICVersion);
+
+  this->_header_block          = concatinated_block->clone();
+  this->_header_block->_end    = this->_header_block->_start + offset;
+  this->_header_block->next    = nullptr;
+  this->_payload_block         = concatinated_block->clone();
+  this->_payload_block->_start = this->_payload_block->_start + offset;
 }
 
-bool
-QUICPacket::is_crypto_packet() const
+QUICPacketType
+QUICVersionNegotiationPacketR::type() const
 {
-  return this->_header->is_crypto_packet();
+  return QUICPacketType::VERSION_NEGOTIATION;
 }
 
-const QUICPacketHeader &
-QUICPacket::header() const
+QUICPacketNumber
+QUICVersionNegotiationPacketR::packet_number() const
 {
-  return *this->_header;
+  ink_assert(!"You should not need packet number of Version Negotiation Packet");
+  return 0;
 }
 
-const uint8_t *
-QUICPacket::payload() const
+QUICConnectionId
+QUICVersionNegotiationPacketR::destination_cid() const
 {
-  return this->_payload.get();
+  return this->_dcid;
 }
 
-QUICVersion
-QUICPacket::version() const
+Ptr<IOBufferBlock>
+QUICVersionNegotiationPacketR::header_block() const
 {
-  return this->_header->version();
+  return this->_header_block;
 }
 
-bool
-QUICPacket::is_ack_eliciting() const
+Ptr<IOBufferBlock>
+QUICVersionNegotiationPacketR::payload_block() const
 {
-  return this->_is_ack_eliciting;
+  return this->_payload_block;
 }
 
-bool
-QUICPacket::is_probing_packet() const
+const QUICVersion
+QUICVersionNegotiationPacketR::supported_version(uint8_t index) const
 {
-  return this->_is_probing_packet;
+  return QUICTypeUtil::read_QUICVersion(this->_versions + sizeof(QUICVersion) * index);
 }
 
-uint16_t
-QUICPacket::size() const
+int
+QUICVersionNegotiationPacketR::nversions() const
 {
-  // This includes not only header size and payload size but also AEAD tag length
-  uint16_t size = this->_header->packet_size();
-  if (size == 0) {
-    size = this->header_size() + this->payload_length();
-  }
-  return size;
+  return this->_nversions;
 }
 
-uint16_t
-QUICPacket::header_size() const
+//
+// QUICInitialPacket
+//
+QUICInitialPacket::QUICInitialPacket(QUICVersion version, QUICConnectionId dcid, QUICConnectionId scid, size_t token_len,
+                                     ats_unique_buf token, size_t length, QUICPacketNumber packet_number, bool ack_eliciting,
+                                     bool probing, bool crypto)
+  : QUICLongHeaderPacket(version, dcid, scid, ack_eliciting, probing, crypto),
+    _token_len(token_len),
+    _token(std::move(token)),
+    _packet_number(packet_number)
 {
-  return this->_header->size();
 }
 
-uint16_t
-QUICPacket::payload_length() const
+QUICPacketType
+QUICInitialPacket::type() const
 {
-  return this->_payload_size;
+  return QUICPacketType::INITIAL;
 }
 
 QUICKeyPhase
-QUICPacket::key_phase() const
+QUICInitialPacket::key_phase() const
 {
-  return this->_header->key_phase();
+  return QUICKeyPhase::INITIAL;
 }
 
-void
-QUICPacket::store(uint8_t *buf, size_t *len) const
+QUICPacketNumber
+QUICInitialPacket::packet_number() const
 {
-  memcpy(buf, this->_header->buf(), this->_header->size());
-  memcpy(buf + this->_header->size(), this->payload(), this->payload_length());
-  *len = this->_header->size() + this->payload_length();
+  return this->_packet_number;
 }
 
-uint8_t
-QUICPacket::calc_packet_number_len(QUICPacketNumber num, QUICPacketNumber base)
+Ptr<IOBufferBlock>
+QUICInitialPacket::header_block() const
 {
-  uint64_t d  = (num - base) * 2;
-  uint8_t len = 0;
+  Ptr<IOBufferBlock> block;
+  size_t written_len = 0;
+  size_t n;
 
-  if (d > 0xFFFFFF) {
-    len = 4;
-  } else if (d > 0xFFFF) {
-    len = 3;
-  } else if (d > 0xFF) {
-    len = 2;
+  block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(2048, BUFFER_SIZE_INDEX_32K));
+  uint8_t *buf = reinterpret_cast<uint8_t *>(block->start());
+
+  // Common Long Header
+  written_len += this->_write_common_header(buf + written_len);
+
+  // Token Length
+  QUICIntUtil::write_QUICVariableInt(this->_token_len, buf + written_len, &n);
+  written_len += n;
+
+  // Token
+  memcpy(buf + written_len, this->_token.get(), this->_token_len);
+  written_len += this->_token_len;
+
+  QUICPacketNumber pn = 0;
+  size_t pn_len       = 4;
+  QUICPacket::encode_packet_number(pn, this->_packet_number, pn_len);
+
+  if (pn > 0x7FFFFF) {
+    pn_len = 4;
+  } else if (pn > 0x7FFF) {
+    pn_len = 3;
+  } else if (pn > 0x7F) {
+    pn_len = 2;
   } else {
-    len = 1;
+    pn_len = 1;
   }
 
-  return len;
+  // PN Len
+  QUICTypeUtil::write_QUICPacketNumberLen(pn_len, buf);
+
+  // Length
+  QUICIntUtil::write_QUICVariableInt(pn_len + this->_payload_length, buf + written_len, &n);
+  written_len += n;
+
+  // PN Field
+  QUICTypeUtil::write_QUICPacketNumber(pn, pn_len, buf + written_len, &n);
+  written_len += n;
+
+  block->fill(written_len);
+
+  return block;
+}
+
+Ptr<IOBufferBlock>
+QUICInitialPacket::payload_block() const
+{
+  return this->_payload_block;
+}
+
+void
+QUICInitialPacket::attach_payload(Ptr<IOBufferBlock> payload, bool unprotected)
+{
+  this->_payload_block   = payload;
+  this->_payload_length  = 0;
+  Ptr<IOBufferBlock> tmp = payload;
+  while (tmp) {
+    this->_payload_length += tmp->size();
+    tmp = tmp->next;
+  }
+  if (unprotected) {
+    this->_payload_length += aead_tag_len;
+  }
+}
+
+//
+// QUICInitialPacketR
+//
+QUICInitialPacketR::QUICInitialPacketR(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to, Ptr<IOBufferBlock> blocks,
+                                       QUICPacketNumber base_packet_number)
+  : QUICLongHeaderPacketR(udp_con, from, to, blocks)
+{
+  size_t len = 0;
+  for (auto b = blocks; b; b = b->next) {
+    len += b->size();
+  }
+
+  Ptr<IOBufferBlock> concatinated_block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  concatinated_block->alloc(iobuffer_size_to_index(len, BUFFER_SIZE_INDEX_32K));
+  concatinated_block->fill(len);
+
+  uint8_t *raw_buf = reinterpret_cast<uint8_t *>(concatinated_block->start());
+
+  size_t copied_len = 0;
+  for (auto b = blocks; b; b = b->next) {
+    memcpy(raw_buf + copied_len, b->start(), b->size());
+    copied_len += b->size();
+  }
+
+  uint8_t dcil = 0;
+  uint8_t scil = 0;
+  QUICInvariants::dcil(dcil, raw_buf, len);
+  QUICInvariants::scil(scil, raw_buf, len);
+
+  size_t offset = LONG_HDR_OFFSET_CONNECTION_ID;
+  this->_dcid   = {raw_buf + offset, dcil};
+  offset += dcil + 1;
+  this->_scid = {raw_buf + offset, scil};
+  offset += scil;
+
+  // Token Length Field
+  uint64_t token_len = QUICIntUtil::read_QUICVariableInt(raw_buf + offset, len - offset);
+  offset += QUICVariableInt::size(raw_buf + offset);
+
+  // Token Field
+  if (token_len) {
+    this->_token = new QUICAddressValidationToken(raw_buf + offset, token_len);
+    offset += token_len;
+  } else {
+    this->_token = new QUICAddressValidationToken(nullptr, 0);
+  }
+
+  // Length Field
+  offset += QUICVariableInt::size(raw_buf + offset);
+
+  // PN Field
+  int pn_len = QUICTypeUtil::read_QUICPacketNumberLen(raw_buf);
+  QUICPacket::decode_packet_number(this->_packet_number, QUICTypeUtil::read_QUICPacketNumber(raw_buf + offset, pn_len), pn_len,
+                                   base_packet_number);
+  offset += pn_len;
+
+  this->_header_block          = concatinated_block->clone();
+  this->_header_block->_end    = this->_header_block->_start + offset;
+  this->_header_block->next    = nullptr;
+  this->_payload_block         = concatinated_block->clone();
+  this->_payload_block->_start = this->_payload_block->_start + offset;
+}
+
+QUICInitialPacketR::~QUICInitialPacketR()
+{
+  delete this->_token;
+}
+
+QUICPacketType
+QUICInitialPacketR::type() const
+{
+  return QUICPacketType::INITIAL;
+}
+
+QUICPacketNumber
+QUICInitialPacketR::packet_number() const
+{
+  return this->_packet_number;
+}
+
+QUICKeyPhase
+QUICInitialPacketR::key_phase() const
+{
+  return QUICKeyPhase::INITIAL;
+}
+
+Ptr<IOBufferBlock>
+QUICInitialPacketR::header_block() const
+{
+  return this->_header_block;
+}
+
+Ptr<IOBufferBlock>
+QUICInitialPacketR::payload_block() const
+{
+  return this->_payload_block;
+}
+
+void
+QUICInitialPacketR::attach_payload(Ptr<IOBufferBlock> payload, bool unprotected)
+{
+  this->_payload_block = payload;
+}
+
+const QUICAddressValidationToken &
+QUICInitialPacketR::token() const
+{
+  return *(this->_token);
 }
 
 bool
-QUICPacket::encode_packet_number(QUICPacketNumber &dst, QUICPacketNumber src, size_t len)
+QUICInitialPacketR::token_length(size_t &token_length, uint8_t &field_len, size_t &token_length_filed_offset, const uint8_t *packet,
+                                 size_t packet_len)
 {
-  uint64_t mask = 0;
-  switch (len) {
-  case 1:
-    mask = 0xFF;
-    break;
-  case 2:
-    mask = 0xFFFF;
-    break;
-  case 3:
-    mask = 0xFFFFFF;
-    break;
-  case 4:
-    mask = 0xFFFFFFFF;
-    break;
-  default:
-    ink_assert(!"len must be 1, 2, or 4");
+  QUICPacketType type = QUICPacketType::UNINITIALIZED;
+  QUICPacketR::type(type, packet, packet_len);
+
+  ink_assert(type == QUICPacketType::INITIAL);
+
+  uint8_t dcil, scil;
+  QUICInvariants::dcil(dcil, packet, packet_len);
+  QUICInvariants::scil(scil, packet, packet_len);
+
+  token_length_filed_offset = LONG_HDR_OFFSET_CONNECTION_ID + dcil + 1 + scil;
+  if (token_length_filed_offset >= packet_len) {
     return false;
   }
-  dst = src & mask;
+
+  token_length = QUICIntUtil::read_QUICVariableInt(packet + token_length_filed_offset, packet_len - token_length_filed_offset);
+  field_len    = QUICVariableInt::size(packet + token_length_filed_offset);
 
   return true;
 }
 
-bool
-QUICPacket::decode_packet_number(QUICPacketNumber &dst, QUICPacketNumber src, size_t len, QUICPacketNumber largest_acked)
+//
+// QUICZeroRttPacket
+//
+QUICZeroRttPacket::QUICZeroRttPacket(QUICVersion version, QUICConnectionId dcid, QUICConnectionId scid, size_t length,
+                                     QUICPacketNumber packet_number, bool ack_eliciting, bool probing)
+  : QUICLongHeaderPacket(version, dcid, scid, ack_eliciting, probing, false), _packet_number(packet_number)
 {
-  ink_assert(len == 1 || len == 2 || len == 3 || len == 4);
+}
 
-  uint64_t maximum_diff = 0;
-  switch (len) {
-  case 1:
-    maximum_diff = 0x100;
-    break;
-  case 2:
-    maximum_diff = 0x10000;
-    break;
-  case 3:
-    maximum_diff = 0x1000000;
-    break;
-  case 4:
-    maximum_diff = 0x100000000;
-    break;
-  default:
-    ink_assert(!"len must be 1, 2, 3 or 4");
+QUICPacketType
+QUICZeroRttPacket::type() const
+{
+  return QUICPacketType::ZERO_RTT_PROTECTED;
+}
+
+QUICKeyPhase
+QUICZeroRttPacket::key_phase() const
+{
+  return QUICKeyPhase::ZERO_RTT;
+}
+
+QUICPacketNumber
+QUICZeroRttPacket::packet_number() const
+{
+  return this->_packet_number;
+}
+
+Ptr<IOBufferBlock>
+QUICZeroRttPacket::header_block() const
+{
+  Ptr<IOBufferBlock> block;
+  size_t written_len = 0;
+  size_t n;
+
+  block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(2048, BUFFER_SIZE_INDEX_32K));
+  uint8_t *buf = reinterpret_cast<uint8_t *>(block->start());
+
+  // Common Long Header
+  written_len += this->_write_common_header(buf + written_len);
+
+  QUICPacketNumber pn = 0;
+  size_t pn_len       = 4;
+  QUICPacket::encode_packet_number(pn, this->_packet_number, pn_len);
+
+  if (pn > 0x7FFFFF) {
+    pn_len = 4;
+  } else if (pn > 0x7FFF) {
+    pn_len = 3;
+  } else if (pn > 0x7F) {
+    pn_len = 2;
+  } else {
+    pn_len = 1;
   }
-  QUICPacketNumber base       = largest_acked & (~(maximum_diff - 1));
-  QUICPacketNumber candidate1 = base + src;
-  QUICPacketNumber candidate2 = base + src + maximum_diff;
-  QUICPacketNumber expected   = largest_acked + 1;
 
-  if (((candidate1 > expected) ? (candidate1 - expected) : (expected - candidate1)) <
-      ((candidate2 > expected) ? (candidate2 - expected) : (expected - candidate2))) {
-    dst = candidate1;
+  // PN Len
+  QUICTypeUtil::write_QUICPacketNumberLen(pn_len, buf);
+
+  // Length
+  QUICIntUtil::write_QUICVariableInt(pn_len + this->_payload_length, buf + written_len, &n);
+  written_len += n;
+
+  // PN Field
+  QUICTypeUtil::write_QUICPacketNumber(pn, pn_len, buf + written_len, &n);
+  written_len += n;
+
+  block->fill(written_len);
+
+  return block;
+}
+
+Ptr<IOBufferBlock>
+QUICZeroRttPacket::payload_block() const
+{
+  return this->_payload_block;
+}
+
+void
+QUICZeroRttPacket::attach_payload(Ptr<IOBufferBlock> payload, bool unprotected)
+{
+  this->_payload_block   = payload;
+  this->_payload_length  = 0;
+  Ptr<IOBufferBlock> tmp = payload;
+  while (tmp) {
+    this->_payload_length += tmp->size();
+    tmp = tmp->next;
+  }
+  if (unprotected) {
+    this->_payload_length += aead_tag_len;
+  }
+}
+
+//
+// QUICZeroRttPacketR
+//
+QUICZeroRttPacketR::QUICZeroRttPacketR(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to, Ptr<IOBufferBlock> blocks,
+                                       QUICPacketNumber base_packet_number)
+  : QUICLongHeaderPacketR(udp_con, from, to, blocks)
+{
+  size_t len = 0;
+  for (auto b = blocks; b; b = b->next) {
+    len += b->size();
+  }
+
+  Ptr<IOBufferBlock> concatinated_block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  concatinated_block->alloc(iobuffer_size_to_index(len, BUFFER_SIZE_INDEX_32K));
+  concatinated_block->fill(len);
+
+  uint8_t *raw_buf = reinterpret_cast<uint8_t *>(concatinated_block->start());
+
+  size_t copied_len = 0;
+  for (auto b = blocks; b; b = b->next) {
+    memcpy(raw_buf + copied_len, b->start(), b->size());
+    copied_len += b->size();
+  }
+
+  uint8_t dcil = 0;
+  uint8_t scil = 0;
+  QUICInvariants::dcil(dcil, raw_buf, len);
+  QUICInvariants::scil(scil, raw_buf, len);
+
+  size_t offset = LONG_HDR_OFFSET_CONNECTION_ID;
+  this->_dcid   = {raw_buf + offset, dcil};
+  offset += dcil + 1;
+  this->_scid = {raw_buf + offset, scil};
+  offset += scil;
+
+  // Length Field
+  offset += QUICVariableInt::size(raw_buf + offset);
+
+  // PN Field
+  int pn_len = QUICTypeUtil::read_QUICPacketNumberLen(raw_buf);
+  QUICPacket::decode_packet_number(this->_packet_number, QUICTypeUtil::read_QUICPacketNumber(raw_buf + offset, pn_len), pn_len,
+                                   base_packet_number);
+  offset += pn_len;
+
+  this->_header_block          = concatinated_block->clone();
+  this->_header_block->_end    = this->_header_block->_start + offset;
+  this->_header_block->next    = nullptr;
+  this->_payload_block         = concatinated_block->clone();
+  this->_payload_block->_start = this->_payload_block->_start + offset;
+}
+
+QUICPacketType
+QUICZeroRttPacketR::type() const
+{
+  return QUICPacketType::ZERO_RTT_PROTECTED;
+}
+
+QUICPacketNumber
+QUICZeroRttPacketR::packet_number() const
+{
+  return this->_packet_number;
+}
+
+QUICKeyPhase
+QUICZeroRttPacketR::key_phase() const
+{
+  return QUICKeyPhase::ZERO_RTT;
+}
+
+Ptr<IOBufferBlock>
+QUICZeroRttPacketR::header_block() const
+{
+  return this->_header_block;
+}
+
+Ptr<IOBufferBlock>
+QUICZeroRttPacketR::payload_block() const
+{
+  return this->_payload_block;
+}
+
+void
+QUICZeroRttPacketR::attach_payload(Ptr<IOBufferBlock> payload, bool unprotected)
+{
+  this->_payload_block = payload;
+}
+
+//
+// QUICHandshakePacket
+//
+QUICHandshakePacket::QUICHandshakePacket(QUICVersion version, QUICConnectionId dcid, QUICConnectionId scid, size_t length,
+                                         QUICPacketNumber packet_number, bool ack_eliciting, bool probing, bool crypto)
+  : QUICLongHeaderPacket(version, dcid, scid, ack_eliciting, probing, crypto), _packet_number(packet_number)
+{
+}
+
+QUICPacketType
+QUICHandshakePacket::type() const
+{
+  return QUICPacketType::HANDSHAKE;
+}
+
+QUICKeyPhase
+QUICHandshakePacket::key_phase() const
+{
+  return QUICKeyPhase::HANDSHAKE;
+}
+
+QUICPacketNumber
+QUICHandshakePacket::packet_number() const
+{
+  return this->_packet_number;
+}
+
+Ptr<IOBufferBlock>
+QUICHandshakePacket::header_block() const
+{
+  Ptr<IOBufferBlock> block;
+  size_t written_len = 0;
+  size_t n;
+
+  block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(2048, BUFFER_SIZE_INDEX_32K));
+  uint8_t *buf = reinterpret_cast<uint8_t *>(block->start());
+
+  // Common Long Header
+  written_len += this->_write_common_header(buf + written_len);
+
+  QUICPacketNumber pn = 0;
+  size_t pn_len       = 4;
+  QUICPacket::encode_packet_number(pn, this->_packet_number, pn_len);
+
+  if (pn > 0x7FFFFF) {
+    pn_len = 4;
+  } else if (pn > 0x7FFF) {
+    pn_len = 3;
+  } else if (pn > 0x7F) {
+    pn_len = 2;
   } else {
-    dst = candidate2;
+    pn_len = 1;
   }
 
-  return true;
+  // PN Len
+  QUICTypeUtil::write_QUICPacketNumberLen(pn_len, buf);
+
+  // Length
+  QUICIntUtil::write_QUICVariableInt(pn_len + this->_payload_length, buf + written_len, &n);
+  written_len += n;
+
+  // PN Field
+  QUICTypeUtil::write_QUICPacketNumber(pn, pn_len, buf + written_len, &n);
+  written_len += n;
+
+  block->fill(written_len);
+
+  return block;
+}
+
+Ptr<IOBufferBlock>
+QUICHandshakePacket::payload_block() const
+{
+  return this->_payload_block;
+}
+
+void
+QUICHandshakePacket::attach_payload(Ptr<IOBufferBlock> payload, bool unprotected)
+{
+  this->_payload_block   = payload;
+  this->_payload_length  = 0;
+  Ptr<IOBufferBlock> tmp = payload;
+  while (tmp) {
+    this->_payload_length += tmp->size();
+    tmp = tmp->next;
+  }
+  if (unprotected) {
+    this->_payload_length += aead_tag_len;
+  }
+}
+
+//
+// QUICHandshakePacketR
+//
+QUICHandshakePacketR::QUICHandshakePacketR(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to, Ptr<IOBufferBlock> blocks,
+                                           QUICPacketNumber base_packet_number)
+  : QUICLongHeaderPacketR(udp_con, from, to, blocks)
+{
+  size_t len = 0;
+  for (auto b = blocks; b; b = b->next) {
+    len += b->size();
+  }
+
+  Ptr<IOBufferBlock> concatinated_block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  concatinated_block->alloc(iobuffer_size_to_index(len, BUFFER_SIZE_INDEX_32K));
+  concatinated_block->fill(len);
+
+  uint8_t *raw_buf = reinterpret_cast<uint8_t *>(concatinated_block->start());
+
+  size_t copied_len = 0;
+  for (auto b = blocks; b; b = b->next) {
+    memcpy(raw_buf + copied_len, b->start(), b->size());
+    copied_len += b->size();
+  }
+
+  uint8_t dcil = 0;
+  uint8_t scil = 0;
+  QUICInvariants::dcil(dcil, raw_buf, len);
+  QUICInvariants::scil(scil, raw_buf, len);
+
+  size_t offset = LONG_HDR_OFFSET_CONNECTION_ID;
+  this->_dcid   = {raw_buf + offset, dcil};
+  offset += dcil + 1;
+  this->_scid = {raw_buf + offset, scil};
+  offset += scil;
+
+  // Length Field
+  offset += QUICVariableInt::size(raw_buf + offset);
+
+  // PN Field
+  int pn_len = QUICTypeUtil::read_QUICPacketNumberLen(raw_buf);
+  QUICPacket::decode_packet_number(this->_packet_number, QUICTypeUtil::read_QUICPacketNumber(raw_buf + offset, pn_len), pn_len,
+                                   base_packet_number);
+  offset += pn_len;
+
+  this->_header_block          = concatinated_block->clone();
+  this->_header_block->_end    = this->_header_block->_start + offset;
+  this->_header_block->next    = nullptr;
+  this->_payload_block         = concatinated_block->clone();
+  this->_payload_block->_start = this->_payload_block->_start + offset;
+}
+
+QUICPacketType
+QUICHandshakePacketR::type() const
+{
+  return QUICPacketType::HANDSHAKE;
+}
+
+QUICKeyPhase
+QUICHandshakePacketR::key_phase() const
+{
+  return QUICKeyPhase::HANDSHAKE;
+}
+
+QUICPacketNumber
+QUICHandshakePacketR::packet_number() const
+{
+  return this->_packet_number;
+}
+
+Ptr<IOBufferBlock>
+QUICHandshakePacketR::header_block() const
+{
+  return this->_header_block;
+}
+
+Ptr<IOBufferBlock>
+QUICHandshakePacketR::payload_block() const
+{
+  return this->_payload_block;
+}
+
+void
+QUICHandshakePacketR::attach_payload(Ptr<IOBufferBlock> payload, bool unprotected)
+{
+  this->_payload_block = payload;
+}
+
+//
+// QUICRetryPacket
+//
+QUICRetryPacket::QUICRetryPacket(QUICVersion version, QUICConnectionId dcid, QUICConnectionId scid, QUICRetryToken &token)
+  : QUICLongHeaderPacket(version, dcid, scid, false, false, false), _token(token)
+{
+}
+
+QUICPacketType
+QUICRetryPacket::type() const
+{
+  return QUICPacketType::RETRY;
+}
+
+QUICPacketNumber
+QUICRetryPacket::packet_number() const
+{
+  ink_assert(!"You should not need packet number of Retry Packet");
+  return 0;
+}
+
+uint16_t
+QUICRetryPacket::payload_length() const
+{
+  uint16_t size = 0;
+
+  for (auto b = this->payload_block(); b; b = b->next) {
+    size += b->size();
+  }
+
+  return size;
+}
+
+Ptr<IOBufferBlock>
+QUICRetryPacket::header_block() const
+{
+  Ptr<IOBufferBlock> block;
+  size_t written_len = 0;
+
+  block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(2048, BUFFER_SIZE_INDEX_32K));
+  uint8_t *buf = reinterpret_cast<uint8_t *>(block->start());
+
+  // Common Long Header
+  written_len += this->_write_common_header(buf + written_len);
+
+  block->fill(written_len);
+
+  return block;
+}
+
+Ptr<IOBufferBlock>
+QUICRetryPacket::payload_block() const
+{
+  Ptr<IOBufferBlock> block;
+  uint8_t *buf;
+  size_t written_len = 0;
+
+  block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(QUICConnectionId::MAX_LENGTH + this->_token.length() + QUICRetryIntegrityTag::LEN,
+                                      BUFFER_SIZE_INDEX_32K));
+  buf = reinterpret_cast<uint8_t *>(block->start());
+
+  // Retry Token
+  memcpy(buf + written_len, this->_token.buf(), this->_token.length());
+  written_len += this->_token.length();
+  block->fill(written_len);
+
+  // Retry Integrity Tag
+  QUICRetryIntegrityTag::compute(buf + written_len, this->_token.original_dcid(), this->header_block(), block);
+  written_len += QUICRetryIntegrityTag::LEN;
+  block->fill(QUICRetryIntegrityTag::LEN);
+
+  return block;
+}
+
+const QUICRetryToken &
+QUICRetryPacket::token() const
+{
+  return this->_token;
+}
+
+//
+// QUICRetryPacketR
+//
+QUICRetryPacketR::QUICRetryPacketR(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to, Ptr<IOBufferBlock> blocks)
+  : QUICLongHeaderPacketR(udp_con, from, to, blocks)
+{
+  size_t len = 0;
+  for (auto b = blocks; b; b = b->next) {
+    len += b->size();
+  }
+
+  Ptr<IOBufferBlock> concatinated_block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  concatinated_block->alloc(iobuffer_size_to_index(len, BUFFER_SIZE_INDEX_32K));
+  concatinated_block->fill(len);
+
+  uint8_t *raw_buf = reinterpret_cast<uint8_t *>(concatinated_block->start());
+
+  size_t copied_len = 0;
+  for (auto b = blocks; b; b = b->next) {
+    memcpy(raw_buf + copied_len, b->start(), b->size());
+    copied_len += b->size();
+  }
+
+  uint8_t dcil = 0;
+  uint8_t scil = 0;
+  QUICInvariants::dcil(dcil, raw_buf, len);
+  QUICInvariants::scil(scil, raw_buf, len);
+
+  size_t offset = LONG_HDR_OFFSET_CONNECTION_ID;
+  this->_dcid   = {raw_buf + offset, dcil};
+  offset += dcil + 1;
+  this->_scid = {raw_buf + offset, scil};
+  offset += scil;
+
+  // Retry Token field
+  this->_token = new QUICRetryToken(raw_buf + offset, len - offset - QUICRetryIntegrityTag::LEN);
+  offset += this->_token->length();
+
+  // Retry Integrity Tag
+  memcpy(this->_integrity_tag, raw_buf + offset, QUICRetryIntegrityTag::LEN);
+
+  this->_header_block                    = concatinated_block->clone();
+  this->_header_block->_end              = this->_header_block->_start + offset;
+  this->_header_block->next              = nullptr;
+  this->_payload_block                   = concatinated_block->clone();
+  this->_payload_block->_start           = this->_payload_block->_start + offset;
+  this->_payload_block_without_tag       = this->_payload_block->clone();
+  this->_payload_block_without_tag->_end = this->_payload_block_without_tag->_end - QUICRetryIntegrityTag::LEN;
+}
+
+QUICRetryPacketR::~QUICRetryPacketR()
+{
+  delete this->_token;
+}
+
+Ptr<IOBufferBlock>
+QUICRetryPacketR::header_block() const
+{
+  return this->_header_block;
+}
+
+Ptr<IOBufferBlock>
+QUICRetryPacketR::payload_block() const
+{
+  return this->_payload_block;
+}
+
+QUICPacketType
+QUICRetryPacketR::type() const
+{
+  return QUICPacketType::RETRY;
+}
+
+QUICPacketNumber
+QUICRetryPacketR::packet_number() const
+{
+  return 0;
+}
+
+const QUICAddressValidationToken &
+QUICRetryPacketR::token() const
+{
+  return *(this->_token);
+}
+
+bool
+QUICRetryPacketR::has_valid_tag(QUICConnectionId &odcid) const
+{
+  uint8_t tag_computed[QUICRetryIntegrityTag::LEN];
+  QUICRetryIntegrityTag::compute(tag_computed, 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 5fc834f..9d20b23 100644
--- a/iocore/net/quic/QUICPacket.h
+++ b/iocore/net/quic/QUICPacket.h
@@ -27,394 +27,524 @@
 #include <atomic>
 #include <cstddef>
 
-#include "tscore/List.h"
 #include "I_IOBuffer.h"
 
 #include "QUICTypes.h"
-#include "QUICHandshakeProtocol.h"
-#include "QUICPacketHeaderProtector.h"
-#include "QUICFrame.h"
+#include "QUICRetryIntegrityTag.h"
 
 #define QUIC_FIELD_OFFSET_CONNECTION_ID 1
 #define QUIC_FIELD_OFFSET_PACKET_NUMBER 4
 #define QUIC_FIELD_OFFSET_PAYLOAD 5
 
 class UDPConnection;
-class QUICPacketHeader;
-class QUICPacket;
-class QUICPacketLongHeader;
-class QUICPacketShortHeader;
 
-extern ClassAllocator<QUICPacket> quicPacketAllocator;
-extern ClassAllocator<QUICPacketLongHeader> quicPacketLongHeaderAllocator;
-extern ClassAllocator<QUICPacketShortHeader> quicPacketShortHeaderAllocator;
-
-using QUICPacketHeaderDeleterFunc = void (*)(QUICPacketHeader *p);
-using QUICPacketHeaderUPtr        = std::unique_ptr<QUICPacketHeader, QUICPacketHeaderDeleterFunc>;
-
-class QUICPacketHeader
+class QUICPacket
 {
 public:
-  QUICPacketHeader(const IpEndpoint from, const IpEndpoint to, ats_unique_buf buf, size_t len, QUICPacketNumber base)
-    : _from(from), _to(to), _buf(std::move(buf)), _buf_len(len), _base_packet_number(base)
-  {
-  }
-  ~QUICPacketHeader() {}
-  const uint8_t *buf();
+  static constexpr int MAX_INSTANCE_SIZE = 1024;
 
-  virtual bool is_crypto_packet() const;
+  // Token field in Initial packet could be very long.
+  static constexpr size_t MAX_PACKET_HEADER_LEN = 256;
 
-  const IpEndpoint &from() const;
-  const IpEndpoint &to() const;
+  /**
+   * Creates a QUICPacket for sending packets
+   */
+  QUICPacket(bool ack_eliciting, bool probing);
 
-  virtual QUICPacketType type() const = 0;
+  virtual ~QUICPacket();
 
-  /*
-   * Returns a connection id
-   */
+  virtual QUICPacketType type() const              = 0;
   virtual QUICConnectionId destination_cid() const = 0;
-  virtual QUICConnectionId source_cid() const      = 0;
+  virtual QUICPacketNumber packet_number() const   = 0;
+  bool is_ack_eliciting() const;
+  bool is_probing_packet() const;
 
-  virtual QUICPacketNumber packet_number() const = 0;
-  virtual QUICVersion version() const            = 0;
+  // TODO These two should be pure virtual
+  virtual Ptr<IOBufferBlock>
+  header_block() const
+  {
+    return Ptr<IOBufferBlock>();
+  };
+  virtual Ptr<IOBufferBlock>
+  payload_block() const
+  {
+    return Ptr<IOBufferBlock>();
+  };
 
   /*
-   * Returns a pointer for the payload
+   * Size of whole QUIC packet (header + payload + integrity check)
    */
-  virtual const uint8_t *payload() const = 0;
+  virtual uint16_t size() const;
 
   /*
-   * Returns its payload size based on header length and buffer size that is specified to the constructo.
+   * Size of header
    */
-  uint16_t payload_size() const;
+  virtual uint16_t header_size() const;
 
   /*
-   * Returns its header size
+   * Length of payload (payload + integrity check if exists)
    */
-  virtual uint16_t size() const = 0;
+  virtual uint16_t payload_length() const;
 
-  /*
-   * Returns its packet size
+  /**
+   * Key phase
    */
-  uint16_t packet_size() const;
+  virtual QUICKeyPhase key_phase() const;
 
-  /*
-   * Returns a key phase
-   */
-  virtual QUICKeyPhase key_phase() const = 0;
+  // FIXME Remove this and use IOBufferBlock instead
+  void store(uint8_t *buf, size_t *len) const;
 
-  /*
-   * Stores serialized header
-   *
-   * The serialized data doesn't contain a payload part even if it was created with a buffer that contains payload data.
-   */
-  virtual void store(uint8_t *buf, size_t *len) const = 0;
+  /***** STATIC MEMBERS *****/
 
-  QUICPacketHeaderUPtr clone() const;
+  static uint8_t calc_packet_number_len(QUICPacketNumber num, QUICPacketNumber base);
+  static bool encode_packet_number(QUICPacketNumber &dst, QUICPacketNumber src, size_t len);
+  static bool decode_packet_number(QUICPacketNumber &dst, QUICPacketNumber src, size_t len, QUICPacketNumber largest_acked);
 
-  virtual bool has_version() const = 0;
-  virtual bool is_valid() const    = 0;
+protected:
+  QUICPacket();
 
-  /***** STATIC members *****/
+private:
+  bool _is_ack_eliciting  = false;
+  bool _is_probing_packet = false;
+};
 
-  /*
-   * Load data from a buffer and create a QUICPacketHeader
-   *
-   * This creates either a QUICPacketShortHeader or a QUICPacketLongHeader.
+class QUICPacketR : public QUICPacket
+{
+public:
+  /**
+   * Creates a QUICPacket for receiving packets
    */
-  static QUICPacketHeaderUPtr load(const IpEndpoint from, const IpEndpoint to, ats_unique_buf buf, size_t len,
-                                   QUICPacketNumber base);
+  QUICPacketR(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to);
 
-  /*
-   * Build a QUICPacketHeader
-   *
-   * This creates a QUICPacketLongHeader.
-   */
-  static QUICPacketHeaderUPtr build(QUICPacketType type, QUICKeyPhase key_phase, QUICConnectionId destination_cid,
-                                    QUICConnectionId source_cid, QUICPacketNumber packet_number,
-                                    QUICPacketNumber base_packet_number, QUICVersion version, bool crypto, ats_unique_buf payload,
-                                    size_t len);
+  virtual QUICPacketType type() const override = 0;
 
-  /*
-   * Build a QUICPacketHeader
-   *
-   * This creates a QUICPacketLongHeader for INITIAL packet
-   */
-  static QUICPacketHeaderUPtr build(QUICPacketType type, QUICKeyPhase key_phase, QUICConnectionId destination_cid,
-                                    QUICConnectionId source_cid, QUICPacketNumber packet_number,
-                                    QUICPacketNumber base_packet_number, QUICVersion version, bool crypto, ats_unique_buf payload,
-                                    size_t len, ats_unique_buf token, size_t token_len);
+  UDPConnection *udp_con() const;
+  virtual const IpEndpoint &from() const;
+  virtual const IpEndpoint &to() const;
 
-  /*
-   * Build a QUICPacketHeader
-   *
-   * This creates a QUICPacketLongHeader for RETRY packet
-   */
-  static QUICPacketHeaderUPtr build(QUICPacketType type, QUICKeyPhase key_phase, QUICVersion version,
-                                    QUICConnectionId destination_cid, QUICConnectionId source_cid, QUICConnectionId original_dcid,
-                                    ats_unique_buf retry_token, size_t retry_token_len);
+  static bool read_essential_info(Ptr<IOBufferBlock> block, QUICPacketType &type, QUICVersion &version, QUICConnectionId &dcid,
+                                  QUICConnectionId &scid, QUICPacketNumber &packet_number, QUICPacketNumber base_packet_number,
+                                  QUICKeyPhase &key_phase);
+  static bool type(QUICPacketType &type, const uint8_t *packet, size_t packet_len);
 
-  /*
-   * Build a QUICPacketHeader
-   *
-   * This creates a QUICPacketShortHeader that contains a ConnectionID.
-   */
-  static QUICPacketHeaderUPtr build(QUICPacketType type, QUICKeyPhase key_phase, QUICPacketNumber packet_number,
-                                    QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len);
+protected:
+  Ptr<IOBufferBlock> _header_block;
+  Ptr<IOBufferBlock> _payload_block;
 
-  /*
-   * Build a QUICPacketHeader
-   *
-   * This creates a QUICPacketShortHeader that doesn't contain a ConnectionID (Stateless Reset Packet).
+private:
+  UDPConnection *_udp_con = nullptr;
+  const IpEndpoint _from  = {};
+  const IpEndpoint _to    = {};
+};
+
+using QUICPacketDeleterFunc = void (*)(QUICPacket *p);
+using QUICPacketUPtr        = std::unique_ptr<QUICPacket, QUICPacketDeleterFunc>;
+
+class QUICPacketDeleter
+{
+public:
+  static void
+  delete_null_packet(QUICPacket *packet)
+  {
+    ink_assert(packet == nullptr);
+  }
+
+  static void
+  delete_dont_free(QUICPacket *packet)
+  {
+    packet->~QUICPacket();
+  }
+
+  static void
+  delete_packet_new(QUICPacket *packet)
+  {
+    delete packet;
+  }
+};
+
+class QUICLongHeaderPacket : public QUICPacket
+{
+public:
+  /**
+   * For sending packet
    */
-  static QUICPacketHeaderUPtr build(QUICPacketType type, QUICKeyPhase key_phase, QUICConnectionId connection_id,
-                                    QUICPacketNumber packet_number, QUICPacketNumber base_packet_number, ats_unique_buf payload,
-                                    size_t len);
+  QUICLongHeaderPacket(QUICVersion version, QUICConnectionId dcid, QUICConnectionId scid, bool ack_eliciting, bool probing,
+                       bool crypto);
+
+  QUICConnectionId source_cid() const;
+
+  QUICConnectionId destination_cid() const override;
+  uint16_t payload_length() const override;
+  virtual QUICVersion version() const;
+  virtual bool is_crypto_packet() const;
 
 protected:
-  QUICPacketHeader(){};
-  QUICPacketHeader(QUICPacketType type, QUICPacketNumber packet_number, QUICPacketNumber base_packet_number, bool has_version,
-                   QUICVersion version, ats_unique_buf payload, size_t payload_length, QUICKeyPhase key_phase)
-    : _payload(std::move(payload)),
-      _type(type),
-      _key_phase(key_phase),
-      _packet_number(packet_number),
-      _base_packet_number(base_packet_number),
-      _version(version),
-      _payload_length(payload_length),
-      _has_version(has_version){};
-  // Token field in Initial packet could be very long.
-  static constexpr size_t MAX_PACKET_HEADER_LEN = 256;
+  size_t _write_common_header(uint8_t *buf) const;
 
-  const IpEndpoint _from = {};
-  const IpEndpoint _to   = {};
-
-  // These two are used only if the instance was created with a buffer
-  ats_unique_buf _buf = {nullptr};
-  size_t _buf_len     = 0;
-
-  // These are used only if the instance was created without a buffer
-  uint8_t _serialized[MAX_PACKET_HEADER_LEN];
-  ats_unique_buf _payload              = ats_unique_buf(nullptr);
-  QUICPacketType _type                 = QUICPacketType::UNINITIALIZED;
-  QUICKeyPhase _key_phase              = QUICKeyPhase::INITIAL;
-  QUICConnectionId _connection_id      = QUICConnectionId::ZERO();
-  QUICPacketNumber _packet_number      = 0;
-  QUICPacketNumber _base_packet_number = 0;
-  QUICVersion _version                 = 0;
-  size_t _payload_length               = 0;
-  bool _has_version                    = false;
+  Ptr<IOBufferBlock> _payload_block;
+  size_t _payload_length = 0;
+
+private:
+  QUICVersion _version;
+  QUICConnectionId _dcid;
+  QUICConnectionId _scid;
+
+  bool _is_crypto_packet;
 };
 
-class QUICPacketLongHeader : public QUICPacketHeader
+class QUICLongHeaderPacketR : public QUICPacketR
 {
 public:
-  QUICPacketLongHeader() : QUICPacketHeader(){};
-  virtual ~QUICPacketLongHeader(){};
-  QUICPacketLongHeader(const IpEndpoint from, const IpEndpoint to, ats_unique_buf buf, size_t len, QUICPacketNumber base);
-  QUICPacketLongHeader(QUICPacketType type, QUICKeyPhase key_phase, const QUICConnectionId &destination_cid,
-                       const QUICConnectionId &source_cid, QUICPacketNumber packet_number, QUICPacketNumber base_packet_number,
-                       QUICVersion version, bool crypto, ats_unique_buf buf, size_t len,
-                       ats_unique_buf token = ats_unique_buf(nullptr), size_t token_len = 0);
-  QUICPacketLongHeader(QUICPacketType type, QUICKeyPhase key_phase, QUICVersion version, const QUICConnectionId &destination_cid,
-                       const QUICConnectionId &source_cid, const QUICConnectionId &original_dcid, ats_unique_buf retry_token,
-                       size_t retry_token_len);
+  /**
+   * For receiving packet
+   */
+  QUICLongHeaderPacketR(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to, Ptr<IOBufferBlock> blocks);
+
+  virtual ~QUICLongHeaderPacketR(){};
 
-  QUICPacketType type() const override;
   QUICConnectionId destination_cid() const override;
-  QUICConnectionId source_cid() const override;
-  QUICConnectionId original_dcid() const;
-  QUICPacketNumber packet_number() const override;
-  bool has_version() const override;
-  bool is_valid() const override;
-  bool is_crypto_packet() const override;
-  QUICVersion version() const override;
-  const uint8_t *payload() const override;
-  const uint8_t *token() const;
-  size_t token_len() const;
-  QUICKeyPhase key_phase() const override;
-  uint16_t size() const override;
-  void store(uint8_t *buf, size_t *len) const override;
+  QUICConnectionId source_cid() const;
+  virtual QUICVersion version() const;
 
   static bool type(QUICPacketType &type, const uint8_t *packet, size_t packet_len);
   static bool version(QUICVersion &version, const uint8_t *packet, size_t packet_len);
-  static bool dcil(uint8_t &dcil, const uint8_t *packet, size_t packet_len);
-  static bool scil(uint8_t &scil, const uint8_t *packet, size_t packet_len);
-  static bool token_length(size_t &token_length, uint8_t &field_len, size_t &token_length_filed_offset, const uint8_t *packet,
-                           size_t packet_len);
+  static bool key_phase(QUICKeyPhase &key_phase, const uint8_t *packet, size_t packet_len);
   static bool length(size_t &length, uint8_t &length_field_len, size_t &length_field_offset, const uint8_t *packet,
                      size_t packet_len);
-  static bool key_phase(QUICKeyPhase &key_phase, const uint8_t *packet, size_t packet_len);
+  static bool packet_length(size_t &packet_len, const uint8_t *buf, size_t buf_len);
   static bool packet_number_offset(size_t &pn_offset, const uint8_t *packet, size_t packet_len);
-  static bool packet_length(size_t &length, const uint8_t *buf, size_t buf_len);
 
-private:
-  QUICConnectionId _destination_cid = QUICConnectionId::ZERO();
-  QUICConnectionId _source_cid      = QUICConnectionId::ZERO();
-  QUICConnectionId _original_dcid   = QUICConnectionId::ZERO(); //< RETRY packet only
-  size_t _token_len                 = 0;                        //< INITIAL packet only
-  size_t _token_offset              = 0;                        //< INITIAL packet only
-  ats_unique_buf _token             = ats_unique_buf(nullptr);  //< INITIAL packet only
-  size_t _payload_offset            = 0;
-  bool _is_crypto_packet            = false;
+protected:
+  QUICVersion _version;
+  QUICConnectionId _scid;
+  QUICConnectionId _dcid;
 };
 
-class QUICPacketShortHeader : public QUICPacketHeader
+class QUICShortHeaderPacket : public QUICPacket
 {
 public:
-  QUICPacketShortHeader() : QUICPacketHeader(){};
-  virtual ~QUICPacketShortHeader(){};
-  QUICPacketShortHeader(const IpEndpoint from, const IpEndpoint to, ats_unique_buf buf, size_t len, QUICPacketNumber base);
-  QUICPacketShortHeader(QUICPacketType type, QUICKeyPhase key_phase, QUICPacketNumber packet_number,
-                        QUICPacketNumber base_packet_number, ats_unique_buf buf, size_t len);
-  QUICPacketShortHeader(QUICPacketType type, QUICKeyPhase key_phase, const QUICConnectionId &connection_id,
-                        QUICPacketNumber packet_number, QUICPacketNumber base_packet_number, ats_unique_buf buf, size_t len);
+  /**
+   * For sending packet
+   */
+  QUICShortHeaderPacket(QUICConnectionId dcid, QUICPacketNumber packet_number, QUICPacketNumber base_packet_number,
+                        QUICKeyPhase key_phase, bool ack_eliciting, bool probing);
+
   QUICPacketType type() const override;
-  QUICConnectionId destination_cid() const override;
-  QUICConnectionId
-  source_cid() const override
-  {
-    return QUICConnectionId::ZERO();
-  }
+  QUICKeyPhase key_phase() const override;
   QUICPacketNumber packet_number() const override;
-  bool has_version() const override;
-  bool is_valid() const override;
-  QUICVersion version() const override;
-  const uint8_t *payload() const override;
+  QUICConnectionId destination_cid() const override;
+
+  uint16_t payload_length() const override;
+  Ptr<IOBufferBlock> header_block() const override;
+  Ptr<IOBufferBlock> payload_block() const override;
+
+  void attach_payload(Ptr<IOBufferBlock> payload, bool unprotected);
+
+private:
+  QUICConnectionId _dcid;
+  QUICPacketNumber _packet_number;
+  QUICKeyPhase _key_phase;
+  int _packet_number_len;
+
+  Ptr<IOBufferBlock> _payload_block;
+  size_t _payload_length;
+};
+
+class QUICShortHeaderPacketR : public QUICPacketR
+{
+public:
+  /**
+   * For receiving packet
+   */
+  QUICShortHeaderPacketR(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to, Ptr<IOBufferBlock> blocks,
+                         QUICPacketNumber base_packet_number);
+
+  void attach_payload(Ptr<IOBufferBlock> payload, bool unprotected);
+
+  QUICPacketType type() const override;
   QUICKeyPhase key_phase() const override;
-  uint16_t size() const override;
-  void store(uint8_t *buf, size_t *len) const override;
+  QUICPacketNumber packet_number() const override;
+  QUICConnectionId destination_cid() const override;
+
+  Ptr<IOBufferBlock> header_block() const override;
+  Ptr<IOBufferBlock> payload_block() const override;
 
-  static bool key_phase(QUICKeyPhase &key_phase, const uint8_t *packet, size_t packet_len);
   static bool packet_number_offset(size_t &pn_offset, const uint8_t *packet, size_t packet_len, int dcil);
 
 private:
+  QUICKeyPhase _key_phase;
+  QUICPacketNumber _packet_number;
   int _packet_number_len;
+  QUICConnectionId _dcid;
 };
 
-class QUICPacketHeaderDeleter
+class QUICStatelessResetPacket : public QUICPacket
 {
 public:
-  static void
-  delete_null_header(QUICPacketHeader *header)
-  {
-    ink_assert(header == nullptr);
-  }
+  /**
+   * For sending packet
+   */
+  QUICStatelessResetPacket(QUICStatelessResetToken token, size_t maximum_size);
 
-  static void
-  delete_long_header(QUICPacketHeader *header)
-  {
-    QUICPacketLongHeader *long_header = dynamic_cast<QUICPacketLongHeader *>(header);
-    ink_assert(long_header != nullptr);
-    long_header->~QUICPacketLongHeader();
-    quicPacketLongHeaderAllocator.free(long_header);
-  }
+  QUICPacketType type() const override;
+  QUICPacketNumber packet_number() const override;
+  QUICConnectionId destination_cid() const override;
 
-  static void
-  delete_short_header(QUICPacketHeader *header)
-  {
-    QUICPacketShortHeader *short_header = dynamic_cast<QUICPacketShortHeader *>(header);
-    ink_assert(short_header != nullptr);
-    short_header->~QUICPacketShortHeader();
-    quicPacketShortHeaderAllocator.free(short_header);
-  }
+  Ptr<IOBufferBlock> header_block() const override;
+  Ptr<IOBufferBlock> payload_block() const override;
+
+  QUICStatelessResetToken token() const;
+
+private:
+  QUICStatelessResetToken _token;
+  size_t _maximum_size;
 };
 
-class QUICPacket
+class QUICStatelessResetPacketR : public QUICPacketR
 {
 public:
-  QUICPacket();
+  /**
+   * For receiving packet
+   */
+  QUICStatelessResetPacketR(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to, Ptr<IOBufferBlock> blocks);
 
-  /*
-   * Creates a QUICPacket with a QUICPacketHeader and a buffer that contains payload
-   *
-   * This will be used for receiving packets. Therefore, it is expected that payload is already decrypted.
-   * However,  QUICPacket class itself doesn't care about whether the payload is protected (encrypted) or not.
+  QUICPacketType type() const override;
+  QUICPacketNumber packet_number() const override;
+  QUICConnectionId destination_cid() const override;
+};
+
+class QUICVersionNegotiationPacket : public QUICLongHeaderPacket
+{
+public:
+  /**
+   * For sending packet
    */
-  QUICPacket(UDPConnection *udp_con, QUICPacketHeaderUPtr header, ats_unique_buf payload, size_t payload_len);
+  QUICVersionNegotiationPacket(QUICConnectionId dcid, QUICConnectionId scid, const QUICVersion versions[], int nversions);
 
-  QUICPacket(QUICPacketHeaderUPtr header, ats_unique_buf payload, size_t payload_len, std::vector<QUICFrameInfo> &frames);
+  QUICPacketType type() const override;
+  QUICVersion version() const override;
+  QUICPacketNumber packet_number() const override;
+  uint16_t payload_length() const override;
 
-  /*
-   * Creates a QUICPacket with a QUICPacketHeader, a buffer that contains payload and a flag that indicates whether the packet is
-   * ack_eliciting
-   *
-   * This will be used for sending packets. Therefore, it is expected that payload is already encrypted.
-   * However, QUICPacket class itself doesn't care about whether the payload is protected (encrypted) or not.
+  Ptr<IOBufferBlock> header_block() const override;
+  Ptr<IOBufferBlock> payload_block() const override;
+
+  const QUICVersion *versions() const;
+  int nversions() const;
+
+private:
+  const QUICVersion *_versions;
+  int _nversions;
+};
+
+class QUICVersionNegotiationPacketR : public QUICLongHeaderPacketR
+{
+public:
+  /**
+   * For receiving packet
    */
-  QUICPacket(QUICPacketHeaderUPtr header, ats_unique_buf payload, size_t payload_len, bool ack_eliciting, bool probing);
+  QUICVersionNegotiationPacketR(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to, Ptr<IOBufferBlock> blocks);
 
-  QUICPacket(QUICPacketHeaderUPtr header, ats_unique_buf payload, size_t payload_len, bool ack_eliciting, bool probing,
-             std::vector<QUICFrameInfo> &frames);
+  QUICPacketType type() const override;
+  QUICPacketNumber packet_number() const override;
+  QUICConnectionId destination_cid() const override;
 
-  virtual ~QUICPacket();
+  Ptr<IOBufferBlock> header_block() const override;
+  Ptr<IOBufferBlock> payload_block() const override;
 
-  UDPConnection *udp_con() const;
-  virtual const IpEndpoint &from() const;
-  virtual const IpEndpoint &to() const;
-  QUICPacketType type() const;
-  QUICConnectionId destination_cid() const;
-  QUICConnectionId source_cid() const;
-  QUICPacketNumber packet_number() const;
-  QUICVersion version() const;
-  const QUICPacketHeader &header() const;
-  const uint8_t *payload() const;
-  bool is_ack_eliciting() const;
-  bool is_crypto_packet() const;
-  bool is_probing_packet() const;
+  const QUICVersion supported_version(uint8_t index) const;
+  int nversions() const;
 
-  /*
-   * Size of whole QUIC packet (header + payload + integrity check)
+private:
+  QUICConnectionId _dcid;
+  uint8_t *_versions;
+  int _nversions;
+};
+
+class QUICInitialPacket : public QUICLongHeaderPacket
+{
+public:
+  /**
+   * For sending packet
    */
-  uint16_t size() const;
+  QUICInitialPacket(QUICVersion version, QUICConnectionId dcid, QUICConnectionId scid, size_t token_len, ats_unique_buf token,
+                    size_t length, QUICPacketNumber packet_number, bool ack_eliciting, bool probing, bool crypto);
 
-  /*
-   * Size of header
+  QUICPacketType type() const override;
+  QUICPacketNumber packet_number() const override;
+  QUICKeyPhase key_phase() const override;
+
+  Ptr<IOBufferBlock> header_block() const override;
+  Ptr<IOBufferBlock> payload_block() const override;
+  void attach_payload(Ptr<IOBufferBlock> payload, bool unprotected);
+
+private:
+  size_t _token_len     = 0;
+  ats_unique_buf _token = ats_unique_buf(nullptr);
+  QUICPacketNumber _packet_number;
+};
+
+class QUICInitialPacketR : public QUICLongHeaderPacketR
+{
+public:
+  /**
+   * For receiving packet
    */
-  uint16_t header_size() const;
+  QUICInitialPacketR(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to, Ptr<IOBufferBlock> blocks,
+                     QUICPacketNumber base_packet_number);
+  ~QUICInitialPacketR();
 
-  /*
-   * Length of payload
+  Ptr<IOBufferBlock> header_block() const override;
+  Ptr<IOBufferBlock> payload_block() const override;
+  void attach_payload(Ptr<IOBufferBlock> payload, bool unprotected);
+
+  QUICPacketType type() const override;
+  QUICPacketNumber packet_number() const override;
+  QUICKeyPhase key_phase() const override;
+
+  const QUICAddressValidationToken &token() const;
+
+  static bool token_length(size_t &token_length, uint8_t &field_len, size_t &token_length_filed_offset, const uint8_t *packet,
+                           size_t packet_len);
+
+protected:
+  Ptr<IOBufferBlock> _payload_block;
+
+private:
+  QUICPacketNumber _packet_number;
+  QUICAddressValidationToken *_token = nullptr;
+
+  bool _parse();
+};
+
+class QUICZeroRttPacket : public QUICLongHeaderPacket
+{
+public:
+  /**
+   * For sending packet
    */
-  uint16_t payload_length() const;
+  QUICZeroRttPacket(QUICVersion version, QUICConnectionId dcid, QUICConnectionId scid, size_t length,
+                    QUICPacketNumber packet_number, bool ack_eliciting, bool probing);
 
-  void store(uint8_t *buf, size_t *len) const;
-  QUICKeyPhase key_phase() const;
+  QUICPacketType type() const override;
+  QUICPacketNumber packet_number() const override;
+  QUICKeyPhase key_phase() const override;
 
-  /***** STATIC MEMBERS *****/
+  Ptr<IOBufferBlock> header_block() const override;
+  Ptr<IOBufferBlock> payload_block() const override;
+  void attach_payload(Ptr<IOBufferBlock> payload, bool unprotected);
 
-  static uint8_t calc_packet_number_len(QUICPacketNumber num, QUICPacketNumber base);
-  static bool encode_packet_number(QUICPacketNumber &dst, QUICPacketNumber src, size_t len);
-  static bool decode_packet_number(QUICPacketNumber &dst, QUICPacketNumber src, size_t len, QUICPacketNumber largest_acked);
+private:
+  QUICPacketNumber _packet_number;
+};
+
+class QUICZeroRttPacketR : public QUICLongHeaderPacketR
+{
+public:
+  /**
+   * For receiving packet
+   */
+  QUICZeroRttPacketR(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to, Ptr<IOBufferBlock> blocks,
+                     QUICPacketNumber base_packet_number);
 
-  LINK(QUICPacket, link);
+  Ptr<IOBufferBlock> header_block() const override;
+  Ptr<IOBufferBlock> payload_block() const override;
+  void attach_payload(Ptr<IOBufferBlock> payload, bool unprotected);
+
+  QUICPacketType type() const override;
+  QUICPacketNumber packet_number() const override;
+  QUICKeyPhase key_phase() const override;
 
 private:
-  UDPConnection *_udp_con      = nullptr;
-  QUICPacketHeaderUPtr _header = QUICPacketHeaderUPtr(nullptr, &QUICPacketHeaderDeleter::delete_null_header);
-  ats_unique_buf _payload      = ats_unique_buf(nullptr);
-  size_t _payload_size         = 0;
-  bool _is_ack_eliciting       = false;
-  bool _is_probing_packet      = false;
+  QUICPacketNumber _packet_number;
 };
 
-using QUICPacketDeleterFunc = void (*)(QUICPacket *p);
-using QUICPacketUPtr        = std::unique_ptr<QUICPacket, QUICPacketDeleterFunc>;
+class QUICHandshakePacket : public QUICLongHeaderPacket
+{
+public:
+  /**
+   * For sending packet
+   */
+  QUICHandshakePacket(QUICVersion version, QUICConnectionId dcid, QUICConnectionId scid, size_t length,
+                      QUICPacketNumber packet_number, bool ack_eliciting, bool probing, bool crypto);
 
-class QUICPacketDeleter
+  QUICPacketType type() const override;
+  QUICKeyPhase key_phase() const override;
+  QUICPacketNumber packet_number() const override;
+
+  Ptr<IOBufferBlock> header_block() const override;
+  Ptr<IOBufferBlock> payload_block() const override;
+  void attach_payload(Ptr<IOBufferBlock> payload, bool unprotected);
+
+private:
+  QUICPacketNumber _packet_number;
+};
+
+class QUICHandshakePacketR : public QUICLongHeaderPacketR
 {
 public:
-  // TODO Probably these methods should call destructor
-  static void
-  delete_null_packet(QUICPacket *packet)
-  {
-    ink_assert(packet == nullptr);
-  }
+  /**
+   * For receiving packet
+   */
+  QUICHandshakePacketR(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to, Ptr<IOBufferBlock> blocks,
+                       QUICPacketNumber base_packet_number);
 
-  static void
-  delete_packet(QUICPacket *packet)
-  {
-    packet->~QUICPacket();
-    quicPacketAllocator.free(packet);
-  }
+  Ptr<IOBufferBlock> header_block() const override;
+  Ptr<IOBufferBlock> payload_block() const override;
+  void attach_payload(Ptr<IOBufferBlock> payload, bool unprotected);
+
+  QUICPacketType type() const override;
+  QUICKeyPhase key_phase() const override;
+  QUICPacketNumber packet_number() const override;
+
+private:
+  QUICPacketNumber _packet_number;
+};
+
+class QUICRetryPacket : public QUICLongHeaderPacket
+{
+public:
+  /**
+   * For sending packet
+   */
+  QUICRetryPacket(QUICVersion version, QUICConnectionId dcid, QUICConnectionId scid, QUICRetryToken &token);
+
+  QUICPacketType type() const override;
+  QUICPacketNumber packet_number() const override;
+  uint16_t payload_length() const override;
+
+  Ptr<IOBufferBlock> header_block() const override;
+  Ptr<IOBufferBlock> payload_block() const override;
+
+  const QUICRetryToken &token() const;
+
+private:
+  QUICRetryToken _token;
+
+  bool _compute_retry_integrity_tag(uint8_t *out, QUICConnectionId odcid, Ptr<IOBufferBlock> header,
+                                    Ptr<IOBufferBlock> payload) const;
+};
+
+class QUICRetryPacketR : public QUICLongHeaderPacketR
+{
+public:
+  /**
+   * For receiving packet
+   */
+  QUICRetryPacketR(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to, Ptr<IOBufferBlock> blocks);
+  ~QUICRetryPacketR();
+
+  Ptr<IOBufferBlock> header_block() const override;
+  Ptr<IOBufferBlock> payload_block() const override;
+
+  QUICPacketType type() const override;
+  QUICPacketNumber packet_number() const override;
+
+  const QUICAddressValidationToken &token() const;
+  bool has_valid_tag(QUICConnectionId &odcid) const;
+
+private:
+  QUICAddressValidationToken *_token = nullptr;
+  uint8_t _integrity_tag[QUICRetryIntegrityTag::LEN];
+  Ptr<IOBufferBlock> _payload_block_without_tag;
 };
diff --git a/iocore/net/quic/QUICPacketFactory.cc b/iocore/net/quic/QUICPacketFactory.cc
index ea82f44..436c683 100644
--- a/iocore/net/quic/QUICPacketFactory.cc
+++ b/iocore/net/quic/QUICPacketFactory.cc
@@ -62,282 +62,255 @@ QUICPacketFactory::create_null_packet()
 }
 
 QUICPacketUPtr
-QUICPacketFactory::create(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to, ats_unique_buf buf, size_t len,
-                          QUICPacketNumber base_packet_number, QUICPacketCreationResult &result)
+QUICPacketFactory::create(uint8_t *packet_buf, UDPConnection *udp_con, IpEndpoint from, IpEndpoint to, ats_unique_buf buf,
+                          size_t len, QUICPacketNumber base_packet_number, QUICPacketCreationResult &result)
 {
-  size_t max_plain_txt_len = 2048;
-  ats_unique_buf plain_txt = ats_unique_malloc(max_plain_txt_len);
-  size_t plain_txt_len     = 0;
-
-  QUICPacketHeaderUPtr header = QUICPacketHeader::load(from, to, std::move(buf), len, base_packet_number);
-
-  QUICConnectionId dcid = header->destination_cid();
-  QUICConnectionId scid = header->source_cid();
-  QUICVDebug(scid, dcid, "Decrypting %s packet #%" PRIu64 " using %s", QUICDebugNames::packet_type(header->type()),
-             header->packet_number(), QUICDebugNames::key_phase(header->key_phase()));
-
-  if (header->has_version() && !QUICTypeUtil::is_supported_version(header->version())) {
-    if (header->type() == QUICPacketType::VERSION_NEGOTIATION) {
-      // version of VN packet is 0x00000000
-      // This packet is unprotected. Just copy the payload
-      result = QUICPacketCreationResult::SUCCESS;
-      memcpy(plain_txt.get(), header->payload(), header->payload_size());
-      plain_txt_len = header->payload_size();
-    } else {
-      // We can't decrypt packets that have unknown versions
-      // What we can use is invariant field of Long Header - version, dcid, and scid
-      result = QUICPacketCreationResult::UNSUPPORTED;
-    }
-  } else {
-    Ptr<IOBufferBlock> plain         = make_ptr<IOBufferBlock>(new_IOBufferBlock());
-    Ptr<IOBufferBlock> protected_ibb = make_ptr<IOBufferBlock>(new_IOBufferBlock());
-    protected_ibb->set_internal(reinterpret_cast<void *>(const_cast<uint8_t *>(header->payload())), header->payload_size(),
-                                BUFFER_SIZE_NOT_ALLOCATED);
-    Ptr<IOBufferBlock> header_ibb = make_ptr<IOBufferBlock>(new_IOBufferBlock());
-    header_ibb->set_internal(reinterpret_cast<void *>(const_cast<uint8_t *>(header->buf())), header->size(),
-                             BUFFER_SIZE_NOT_ALLOCATED);
-
-    switch (header->type()) {
-    case QUICPacketType::STATELESS_RESET:
-    case QUICPacketType::RETRY:
-      // These packets are unprotected. Just copy the payload
-      memcpy(plain_txt.get(), header->payload(), header->payload_size());
-      plain_txt_len = header->payload_size();
-      result        = QUICPacketCreationResult::SUCCESS;
-      break;
-    case QUICPacketType::PROTECTED:
-      if (this->_pp_key_info.is_decryption_key_available(header->key_phase())) {
-        plain = this->_pp_protector.unprotect(header_ibb, protected_ibb, header->packet_number(), header->key_phase());
-        if (plain != nullptr) {
-          memcpy(plain_txt.get(), plain->buf(), plain->size());
-          plain_txt_len = plain->size();
-          result        = QUICPacketCreationResult::SUCCESS;
-        } else {
-          result = QUICPacketCreationResult::FAILED;
-        }
+  QUICPacket *packet = nullptr;
+
+  // FIXME This is temporal. Receive IOBufferBlock from the caller.
+  Ptr<IOBufferBlock> whole_data = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  whole_data->alloc(iobuffer_size_to_index(len, BUFFER_SIZE_INDEX_32K));
+  memcpy(whole_data->start(), buf.get(), len);
+  whole_data->fill(len);
+
+  QUICPacketType type;
+  QUICVersion version;
+  QUICConnectionId dcid;
+  QUICConnectionId scid;
+  QUICPacketNumber packet_number;
+  QUICKeyPhase key_phase;
+
+  if (QUICPacketR::read_essential_info(whole_data, type, version, dcid, scid, packet_number, base_packet_number, key_phase)) {
+    QUICVDebug(scid, dcid, "Decrypting %s packet #%" PRIu64 " using %s", QUICDebugNames::packet_type(type), packet_number,
+               QUICDebugNames::key_phase(key_phase));
+
+    if (type != QUICPacketType::PROTECTED && !QUICTypeUtil::is_supported_version(version)) {
+      if (type == QUICPacketType::VERSION_NEGOTIATION) {
+        packet = new QUICVersionNegotiationPacketR(udp_con, from, to, whole_data);
+        result = QUICPacketCreationResult::SUCCESS;
       } else {
-        result = QUICPacketCreationResult::NOT_READY;
+        // We can't decrypt packets that have unknown versions
+        // What we can use is invariant field of Long Header - version, dcid, and scid
+        result = QUICPacketCreationResult::UNSUPPORTED;
       }
-      break;
-    case QUICPacketType::INITIAL:
-      if (this->_pp_key_info.is_decryption_key_available(QUICKeyPhase::INITIAL)) {
-        if (QUICTypeUtil::is_supported_version(header->version())) {
-          plain = this->_pp_protector.unprotect(header_ibb, protected_ibb, header->packet_number(), header->key_phase());
+    } else {
+      Ptr<IOBufferBlock> plain;
+      switch (type) {
+      case QUICPacketType::STATELESS_RESET:
+        packet = new (packet_buf) QUICStatelessResetPacketR(udp_con, from, to, whole_data);
+        result = QUICPacketCreationResult::SUCCESS;
+        break;
+      case QUICPacketType::RETRY:
+        packet = new (packet_buf) QUICRetryPacketR(udp_con, from, to, whole_data);
+        result = QUICPacketCreationResult::SUCCESS;
+        break;
+      case QUICPacketType::PROTECTED:
+        packet = new (packet_buf) QUICShortHeaderPacketR(udp_con, from, to, whole_data, base_packet_number);
+        if (this->_pp_key_info.is_decryption_key_available(packet->key_phase())) {
+          plain = this->_pp_protector.unprotect(packet->header_block(), packet->payload_block(), packet->packet_number(),
+                                                packet->key_phase());
           if (plain != nullptr) {
-            memcpy(plain_txt.get(), plain->buf(), plain->size());
-            plain_txt_len = plain->size();
-            result        = QUICPacketCreationResult::SUCCESS;
+            static_cast<QUICShortHeaderPacketR *>(packet)->attach_payload(plain, true);
+            result = QUICPacketCreationResult::SUCCESS;
           } else {
             result = QUICPacketCreationResult::FAILED;
           }
         } else {
-          result = QUICPacketCreationResult::SUCCESS;
+          result = QUICPacketCreationResult::NOT_READY;
         }
-      } else {
-        result = QUICPacketCreationResult::IGNORED;
-      }
-      break;
-    case QUICPacketType::HANDSHAKE:
-      if (this->_pp_key_info.is_decryption_key_available(QUICKeyPhase::HANDSHAKE)) {
-        plain = this->_pp_protector.unprotect(header_ibb, protected_ibb, header->packet_number(), header->key_phase());
-        if (plain != nullptr) {
-          memcpy(plain_txt.get(), plain->buf(), plain->size());
-          plain_txt_len = plain->size();
-          result        = QUICPacketCreationResult::SUCCESS;
+        break;
+      case QUICPacketType::INITIAL:
+        packet = new (packet_buf) QUICInitialPacketR(udp_con, from, to, whole_data, base_packet_number);
+        if (this->_pp_key_info.is_decryption_key_available(QUICKeyPhase::INITIAL)) {
+          plain = this->_pp_protector.unprotect(packet->header_block(), packet->payload_block(), packet->packet_number(),
+                                                packet->key_phase());
+          if (plain != nullptr) {
+            static_cast<QUICInitialPacketR *>(packet)->attach_payload(plain, true);
+            result = QUICPacketCreationResult::SUCCESS;
+          } else {
+            result = QUICPacketCreationResult::FAILED;
+          }
         } else {
-          result = QUICPacketCreationResult::FAILED;
+          result = QUICPacketCreationResult::IGNORED;
         }
-      } else {
-        result = QUICPacketCreationResult::IGNORED;
-      }
-      break;
-    case QUICPacketType::ZERO_RTT_PROTECTED:
-      if (this->_pp_key_info.is_decryption_key_available(QUICKeyPhase::ZERO_RTT)) {
-        plain = this->_pp_protector.unprotect(header_ibb, protected_ibb, header->packet_number(), header->key_phase());
-        if (plain != nullptr) {
-          memcpy(plain_txt.get(), plain->buf(), plain->size());
-          plain_txt_len = plain->size();
-          result        = QUICPacketCreationResult::SUCCESS;
+        break;
+      case QUICPacketType::HANDSHAKE:
+        packet = new (packet_buf) QUICHandshakePacketR(udp_con, from, to, whole_data, base_packet_number);
+        if (this->_pp_key_info.is_decryption_key_available(QUICKeyPhase::HANDSHAKE)) {
+          plain = this->_pp_protector.unprotect(packet->header_block(), packet->payload_block(), packet->packet_number(),
+                                                packet->key_phase());
+          if (plain != nullptr) {
+            static_cast<QUICHandshakePacketR *>(packet)->attach_payload(plain, true);
+            result = QUICPacketCreationResult::SUCCESS;
+          } else {
+            result = QUICPacketCreationResult::FAILED;
+          }
         } else {
           result = QUICPacketCreationResult::IGNORED;
         }
-      } else {
-        result = QUICPacketCreationResult::NOT_READY;
+        break;
+      case QUICPacketType::ZERO_RTT_PROTECTED:
+        packet = new (packet_buf) QUICZeroRttPacketR(udp_con, from, to, whole_data, base_packet_number);
+        if (this->_pp_key_info.is_decryption_key_available(QUICKeyPhase::ZERO_RTT)) {
+          plain = this->_pp_protector.unprotect(packet->header_block(), packet->payload_block(), packet->packet_number(),
+                                                packet->key_phase());
+          if (plain != nullptr) {
+            static_cast<QUICZeroRttPacketR *>(packet)->attach_payload(plain, true);
+            result = QUICPacketCreationResult::SUCCESS;
+          } else {
+            result = QUICPacketCreationResult::IGNORED;
+          }
+        } else {
+          result = QUICPacketCreationResult::NOT_READY;
+        }
+        break;
+      default:
+        result = QUICPacketCreationResult::FAILED;
+        break;
       }
-      break;
-    default:
-      result = QUICPacketCreationResult::FAILED;
-      break;
     }
+  } else {
+    Debug(tag.data(), "Failed to read essential field");
+    uint8_t *buf = reinterpret_cast<uint8_t *>(whole_data->start());
+    if (len > 16) {
+      Debug(tag_v.data(), "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", buf[0], buf[1], buf[2],
+            buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15]);
+    }
+    if (len > 32) {
+      Debug(tag_v.data(), "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", buf[16 + 0],
+            buf[16 + 1], buf[16 + 2], buf[16 + 3], buf[16 + 4], buf[16 + 5], buf[16 + 6], buf[16 + 7], buf[16 + 8], buf[16 + 9],
+            buf[16 + 10], buf[16 + 11], buf[16 + 12], buf[16 + 13], buf[16 + 14], buf[16 + 15]);
+    }
+    if (len > 48) {
+      Debug(tag_v.data(), "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", buf[32 + 0],
+            buf[32 + 1], buf[32 + 2], buf[32 + 3], buf[32 + 4], buf[32 + 5], buf[32 + 6], buf[32 + 7], buf[32 + 8], buf[32 + 9],
+            buf[32 + 10], buf[32 + 11], buf[32 + 12], buf[32 + 13], buf[32 + 14], buf[32 + 15]);
+    }
+    result = QUICPacketCreationResult::FAILED;
   }
 
-  QUICPacket *packet = nullptr;
-  if (result == QUICPacketCreationResult::SUCCESS || result == QUICPacketCreationResult::UNSUPPORTED) {
-    packet = quicPacketAllocator.alloc();
-    new (packet) QUICPacket(udp_con, std::move(header), std::move(plain_txt), plain_txt_len);
+  if (result != QUICPacketCreationResult::SUCCESS && result != QUICPacketCreationResult::UNSUPPORTED) {
+    packet = nullptr;
   }
 
-  return QUICPacketUPtr(packet, &QUICPacketDeleter::delete_packet);
+  return QUICPacketUPtr(packet, &QUICPacketDeleter::delete_dont_free);
 }
 
 QUICPacketUPtr
 QUICPacketFactory::create_version_negotiation_packet(QUICConnectionId dcid, QUICConnectionId scid)
 {
-  size_t len = sizeof(QUICVersion) * (countof(QUIC_SUPPORTED_VERSIONS) + 1);
-  ats_unique_buf versions(reinterpret_cast<uint8_t *>(ats_malloc(len)));
-  uint8_t *p = versions.get();
-
-  size_t n;
-  for (auto v : QUIC_SUPPORTED_VERSIONS) {
-    QUICTypeUtil::write_QUICVersion(v, p, &n);
-    p += n;
-  }
-
-  // [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, p, &n);
-  p += n;
-
-  ink_assert(len == static_cast<size_t>(p - versions.get()));
-  // VN packet dosen't have packet number field and version field is always 0x00000000
-  QUICPacketHeaderUPtr header = QUICPacketHeader::build(QUICPacketType::VERSION_NEGOTIATION, QUICKeyPhase::INITIAL, dcid, scid,
-                                                        0x00, 0x00, 0x00, false, std::move(versions), len);
-
-  return QUICPacketFactory::_create_unprotected_packet(std::move(header));
+  return QUICPacketUPtr(new QUICVersionNegotiationPacket(dcid, scid, QUIC_SUPPORTED_VERSIONS, countof(QUIC_SUPPORTED_VERSIONS)),
+                        &QUICPacketDeleter::delete_packet_new);
 }
 
 QUICPacketUPtr
-QUICPacketFactory::create_initial_packet(QUICConnectionId destination_cid, QUICConnectionId source_cid,
-                                         QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len,
-                                         bool retransmittable, bool probing, bool crypto, ats_unique_buf token, size_t token_len)
+QUICPacketFactory::create_initial_packet(uint8_t *packet_buf, QUICConnectionId destination_cid, QUICConnectionId source_cid,
+                                         QUICPacketNumber base_packet_number, Ptr<IOBufferBlock> payload, size_t length,
+                                         bool ack_eliciting, bool probing, bool crypto, ats_unique_buf token, size_t token_len)
 {
   QUICPacketNumberSpace index = QUICTypeUtil::pn_space(QUICEncryptionLevel::INITIAL);
   QUICPacketNumber pn         = this->_packet_number_generator[static_cast<int>(index)].next();
-  QUICPacketHeaderUPtr header =
-    QUICPacketHeader::build(QUICPacketType::INITIAL, QUICKeyPhase::INITIAL, destination_cid, source_cid, pn, base_packet_number,
-                            this->_version, crypto, std::move(payload), len, std::move(token), token_len);
-  return this->_create_encrypted_packet(std::move(header), retransmittable, probing);
-}
 
-QUICPacketUPtr
-QUICPacketFactory::create_retry_packet(QUICConnectionId destination_cid, QUICConnectionId source_cid,
-                                       QUICConnectionId original_dcid, QUICRetryToken &token)
-{
-  ats_unique_buf payload = ats_unique_malloc(token.length());
-  memcpy(payload.get(), token.buf(), token.length());
+  QUICInitialPacket *packet = new (packet_buf) QUICInitialPacket(this->_version, destination_cid, source_cid, token_len,
+                                                                 std::move(token), length, pn, ack_eliciting, probing, crypto);
 
-  QUICPacketHeaderUPtr header =
-    QUICPacketHeader::build(QUICPacketType::RETRY, QUICKeyPhase::INITIAL, QUIC_SUPPORTED_VERSIONS[0], destination_cid, source_cid,
-                            original_dcid, std::move(payload), token.length());
-  return QUICPacketFactory::_create_unprotected_packet(std::move(header));
-}
+  packet->attach_payload(payload, true); // Attach a cleartext payload with extra headers
+  Ptr<IOBufferBlock> protected_payload =
+    this->_pp_protector.protect(packet->header_block(), packet->payload_block(), packet->packet_number(), packet->key_phase());
+  if (protected_payload != nullptr) {
+    packet->attach_payload(protected_payload, false); // Replace its payload with the protected payload
+  } else {
+    QUICDebug(destination_cid, source_cid, "Failed to encrypt a packet");
+    packet = nullptr;
+  }
 
-QUICPacketUPtr
-QUICPacketFactory::create_handshake_packet(QUICConnectionId destination_cid, QUICConnectionId source_cid,
-                                           QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len,
-                                           bool retransmittable, bool probing, bool crypto)
-{
-  QUICPacketNumberSpace index = QUICTypeUtil::pn_space(QUICEncryptionLevel::HANDSHAKE);
-  QUICPacketNumber pn         = this->_packet_number_generator[static_cast<int>(index)].next();
-  QUICPacketHeaderUPtr header =
-    QUICPacketHeader::build(QUICPacketType::HANDSHAKE, QUICKeyPhase::HANDSHAKE, destination_cid, source_cid, pn, base_packet_number,
-                            this->_version, crypto, std::move(payload), len);
-  return this->_create_encrypted_packet(std::move(header), retransmittable, probing);
+  return QUICPacketUPtr(packet, &QUICPacketDeleter::delete_dont_free);
 }
 
 QUICPacketUPtr
-QUICPacketFactory::create_zero_rtt_packet(QUICConnectionId destination_cid, QUICConnectionId source_cid,
-                                          QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len,
-                                          bool retransmittable, bool probing)
+QUICPacketFactory::create_retry_packet(QUICConnectionId destination_cid, QUICConnectionId source_cid, QUICRetryToken &token)
 {
-  QUICPacketNumberSpace index = QUICTypeUtil::pn_space(QUICEncryptionLevel::ZERO_RTT);
-  QUICPacketNumber pn         = this->_packet_number_generator[static_cast<int>(index)].next();
-  QUICPacketHeaderUPtr header =
-    QUICPacketHeader::build(QUICPacketType::ZERO_RTT_PROTECTED, QUICKeyPhase::ZERO_RTT, destination_cid, source_cid, pn,
-                            base_packet_number, this->_version, false, std::move(payload), len);
-  return this->_create_encrypted_packet(std::move(header), retransmittable, probing);
+  return QUICPacketUPtr(new QUICRetryPacket(QUIC_SUPPORTED_VERSIONS[0], destination_cid, source_cid, token),
+                        &QUICPacketDeleter::delete_packet_new);
 }
 
 QUICPacketUPtr
-QUICPacketFactory::create_protected_packet(QUICConnectionId connection_id, QUICPacketNumber base_packet_number,
-                                           ats_unique_buf payload, size_t len, bool retransmittable, bool probing)
+QUICPacketFactory::create_handshake_packet(uint8_t *packet_buf, QUICConnectionId destination_cid, QUICConnectionId source_cid,
+                                           QUICPacketNumber base_packet_number, Ptr<IOBufferBlock> payload, size_t length,
+                                           bool ack_eliciting, bool probing, bool crypto)
 {
-  QUICPacketNumberSpace index = QUICTypeUtil::pn_space(QUICEncryptionLevel::ONE_RTT);
+  QUICPacketNumberSpace index = QUICTypeUtil::pn_space(QUICEncryptionLevel::HANDSHAKE);
   QUICPacketNumber pn         = this->_packet_number_generator[static_cast<int>(index)].next();
-  // TODO Key phase should be picked up from QUICHandshakeProtocol, probably
-  QUICPacketHeaderUPtr header = QUICPacketHeader::build(QUICPacketType::PROTECTED, QUICKeyPhase::PHASE_0, connection_id, pn,
-                                                        base_packet_number, std::move(payload), len);
-  return this->_create_encrypted_packet(std::move(header), retransmittable, probing);
-}
 
-QUICPacketUPtr
-QUICPacketFactory::create_stateless_reset_packet(QUICConnectionId connection_id, QUICStatelessResetToken stateless_reset_token)
-{
-  constexpr uint8_t MIN_UNPREDICTABLE_FIELD_LEN = 5;
-  std::random_device rnd;
+  QUICHandshakePacket *packet =
+    new (packet_buf) QUICHandshakePacket(this->_version, destination_cid, source_cid, length, pn, ack_eliciting, probing, crypto);
 
-  uint8_t random_packet_number = static_cast<uint8_t>(rnd() & 0xFF);
-  size_t payload_len     = static_cast<uint8_t>((rnd() & 0xFF) | (MIN_UNPREDICTABLE_FIELD_LEN + QUICStatelessResetToken::LEN));
-  ats_unique_buf payload = ats_unique_malloc(payload_len);
-  uint8_t *naked_payload = payload.get();
-
-  // Generate random octets
-  for (int i = payload_len - 1; i >= 0; --i) {
-    naked_payload[i] = static_cast<uint8_t>(rnd() & 0xFF);
+  packet->attach_payload(payload, true); // Attach a cleartext payload with extra headers
+  Ptr<IOBufferBlock> protected_payload =
+    this->_pp_protector.protect(packet->header_block(), packet->payload_block(), packet->packet_number(), packet->key_phase());
+  if (protected_payload != nullptr) {
+    packet->attach_payload(protected_payload, false); // Replace its payload with the protected payload
+  } else {
+    QUICDebug(destination_cid, source_cid, "Failed to encrypt a packet");
+    packet = nullptr;
   }
-  // Copy stateless reset token into payload
-  memcpy(naked_payload + payload_len - QUICStatelessResetToken::LEN, stateless_reset_token.buf(), QUICStatelessResetToken::LEN);
 
-  // KeyPhase won't be used
-  QUICPacketHeaderUPtr header = QUICPacketHeader::build(QUICPacketType::STATELESS_RESET, QUICKeyPhase::INITIAL, connection_id,
-                                                        random_packet_number, 0, std::move(payload), payload_len);
-  return QUICPacketFactory::_create_unprotected_packet(std::move(header));
+  return QUICPacketUPtr(packet, &QUICPacketDeleter::delete_dont_free);
 }
 
 QUICPacketUPtr
-QUICPacketFactory::_create_unprotected_packet(QUICPacketHeaderUPtr header)
+QUICPacketFactory::create_zero_rtt_packet(uint8_t *packet_buf, QUICConnectionId destination_cid, QUICConnectionId source_cid,
+                                          QUICPacketNumber base_packet_number, Ptr<IOBufferBlock> payload, size_t length,
+                                          bool ack_eliciting, bool probing)
 {
-  ats_unique_buf cleartext = ats_unique_malloc(2048);
-  size_t cleartext_len     = header->payload_size();
+  QUICPacketNumberSpace index = QUICTypeUtil::pn_space(QUICEncryptionLevel::ZERO_RTT);
+  QUICPacketNumber pn         = this->_packet_number_generator[static_cast<int>(index)].next();
+
+  QUICZeroRttPacket *packet =
+    new (packet_buf) QUICZeroRttPacket(this->_version, destination_cid, source_cid, length, pn, ack_eliciting, probing);
 
-  memcpy(cleartext.get(), header->payload(), cleartext_len);
-  QUICPacket *packet = quicPacketAllocator.alloc();
-  new (packet) QUICPacket(std::move(header), std::move(cleartext), cleartext_len, false, false);
+  packet->attach_payload(payload, true); // Attach a cleartext payload with extra headers
+  Ptr<IOBufferBlock> protected_payload =
+    this->_pp_protector.protect(packet->header_block(), packet->payload_block(), packet->packet_number(), packet->key_phase());
+  if (protected_payload != nullptr) {
+    packet->attach_payload(protected_payload, false); // Replace its payload with the protected payload
+  } else {
+    QUICDebug(destination_cid, source_cid, "Failed to encrypt a packet");
+    packet = nullptr;
+  }
 
-  return QUICPacketUPtr(packet, &QUICPacketDeleter::delete_packet);
+  return QUICPacketUPtr(packet, &QUICPacketDeleter::delete_dont_free);
 }
 
 QUICPacketUPtr
-QUICPacketFactory::_create_encrypted_packet(QUICPacketHeaderUPtr header, bool retransmittable, bool probing)
+QUICPacketFactory::create_short_header_packet(uint8_t *packet_buf, QUICConnectionId destination_cid,
+                                              QUICPacketNumber base_packet_number, Ptr<IOBufferBlock> payload, size_t length,
+                                              bool ack_eliciting, bool probing)
 {
-  QUICConnectionId dcid = header->destination_cid();
-  QUICConnectionId scid = header->source_cid();
-  QUICVDebug(dcid, scid, "Encrypting %s packet #%" PRIu64 " using %s", QUICDebugNames::packet_type(header->type()),
-             header->packet_number(), QUICDebugNames::key_phase(header->key_phase()));
-
-  QUICPacket *packet = nullptr;
-
-  Ptr<IOBufferBlock> payload_ibb = make_ptr<IOBufferBlock>(new_IOBufferBlock());
-  payload_ibb->set_internal(reinterpret_cast<void *>(const_cast<uint8_t *>(header->payload())), header->payload_size(),
-                            BUFFER_SIZE_NOT_ALLOCATED);
+  QUICPacketNumberSpace index = QUICTypeUtil::pn_space(QUICEncryptionLevel::ONE_RTT);
+  QUICPacketNumber pn         = this->_packet_number_generator[static_cast<int>(index)].next();
 
-  Ptr<IOBufferBlock> header_ibb = make_ptr<IOBufferBlock>(new_IOBufferBlock());
-  header_ibb->set_internal(reinterpret_cast<void *>(const_cast<uint8_t *>(header->buf())), header->size(),
-                           BUFFER_SIZE_NOT_ALLOCATED);
+  // TODO Key phase should be picked up from QUICHandshakeProtocol, probably
+  QUICShortHeaderPacket *packet =
+    new (packet_buf) QUICShortHeaderPacket(destination_cid, pn, base_packet_number, QUICKeyPhase::PHASE_0, ack_eliciting, probing);
 
+  packet->attach_payload(payload, true); // Attach a cleartext payload with extra headers
   Ptr<IOBufferBlock> protected_payload =
-    this->_pp_protector.protect(header_ibb, payload_ibb, header->packet_number(), header->key_phase());
+    this->_pp_protector.protect(packet->header_block(), packet->payload_block(), packet->packet_number(), packet->key_phase());
   if (protected_payload != nullptr) {
-    ats_unique_buf cipher_txt = ats_unique_malloc(protected_payload->size());
-    memcpy(cipher_txt.get(), protected_payload->buf(), protected_payload->size());
-    packet = quicPacketAllocator.alloc();
-    new (packet) QUICPacket(std::move(header), std::move(cipher_txt), protected_payload->size(), retransmittable, probing);
+    packet->attach_payload(protected_payload, false); // Replace its payload with the protected payload
   } else {
-    QUICDebug(dcid, scid, "Failed to encrypt a packet");
+    QUICDebug(destination_cid, QUICConnectionId::ZERO(), "Failed to encrypt a packet");
+    packet = nullptr;
   }
 
-  return QUICPacketUPtr(packet, &QUICPacketDeleter::delete_packet);
+  return QUICPacketUPtr(packet, &QUICPacketDeleter::delete_dont_free);
+}
+
+QUICPacketUPtr
+QUICPacketFactory::create_stateless_reset_packet(QUICStatelessResetToken stateless_reset_token, size_t maximum_size)
+{
+  return QUICPacketUPtr(new QUICStatelessResetPacket(stateless_reset_token, maximum_size), &QUICPacketDeleter::delete_packet_new);
 }
 
 void
diff --git a/iocore/net/quic/QUICPacketFactory.h b/iocore/net/quic/QUICPacketFactory.h
index 732ad35..d5316ce 100644
--- a/iocore/net/quic/QUICPacketFactory.h
+++ b/iocore/net/quic/QUICPacketFactory.h
@@ -45,27 +45,26 @@ class QUICPacketFactory
 public:
   static QUICPacketUPtr create_null_packet();
   static QUICPacketUPtr create_version_negotiation_packet(QUICConnectionId dcid, QUICConnectionId scid);
-  static QUICPacketUPtr create_stateless_reset_packet(QUICConnectionId connection_id,
-                                                      QUICStatelessResetToken stateless_reset_token);
-  static QUICPacketUPtr create_retry_packet(QUICConnectionId destination_cid, QUICConnectionId source_cid,
-                                            QUICConnectionId original_dcid, QUICRetryToken &token);
+  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);
 
   QUICPacketFactory(const QUICPacketProtectionKeyInfo &pp_key_info) : _pp_key_info(pp_key_info), _pp_protector(pp_key_info) {}
 
-  QUICPacketUPtr create(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to, ats_unique_buf buf, size_t len,
+  QUICPacketUPtr create(uint8_t *packet_buf, UDPConnection *udp_con, IpEndpoint from, IpEndpoint to, ats_unique_buf buf, size_t len,
                         QUICPacketNumber base_packet_number, QUICPacketCreationResult &result);
-  QUICPacketUPtr create_initial_packet(QUICConnectionId destination_cid, QUICConnectionId source_cid,
-                                       QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len, bool ack_eliciting,
-                                       bool probing, bool crypto, ats_unique_buf token = ats_unique_buf(nullptr),
-                                       size_t token_len = 0);
-  QUICPacketUPtr create_handshake_packet(QUICConnectionId destination_cid, QUICConnectionId source_cid,
-                                         QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len,
+  QUICPacketUPtr create_initial_packet(uint8_t *packet_buf, QUICConnectionId destination_cid, QUICConnectionId source_cid,
+                                       QUICPacketNumber base_packet_number, Ptr<IOBufferBlock> payload, size_t length,
+                                       bool ack_eliciting, bool probing, bool crypto,
+                                       ats_unique_buf token = ats_unique_buf(nullptr), size_t token_len = 0);
+  QUICPacketUPtr create_handshake_packet(uint8_t *packet_buf, QUICConnectionId destination_cid, QUICConnectionId source_cid,
+                                         QUICPacketNumber base_packet_number, Ptr<IOBufferBlock> payload, size_t length,
                                          bool ack_eliciting, bool probing, bool crypto);
-  QUICPacketUPtr create_zero_rtt_packet(QUICConnectionId destination_cid, QUICConnectionId source_cid,
-                                        QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len, bool ack_eliciting,
-                                        bool probing);
-  QUICPacketUPtr create_protected_packet(QUICConnectionId connection_id, QUICPacketNumber base_packet_number,
-                                         ats_unique_buf payload, size_t len, bool ack_eliciting, bool probing);
+  QUICPacketUPtr create_zero_rtt_packet(uint8_t *packet_buf, QUICConnectionId destination_cid, QUICConnectionId source_cid,
+                                        QUICPacketNumber base_packet_number, Ptr<IOBufferBlock> payload, size_t length,
+                                        bool ack_eliciting, bool probing);
+  QUICPacketUPtr create_short_header_packet(uint8_t *packet_buf, QUICConnectionId destination_cid,
+                                            QUICPacketNumber base_packet_number, Ptr<IOBufferBlock> payload, size_t length,
+                                            bool ack_eliciting, bool probing);
   void set_version(QUICVersion negotiated_version);
 
   bool is_ready_to_create_protected_packet();
@@ -79,7 +78,4 @@ private:
 
   // Initial, 0/1-RTT, and Handshake
   QUICPacketNumberGenerator _packet_number_generator[3];
-
-  static QUICPacketUPtr _create_unprotected_packet(QUICPacketHeaderUPtr header);
-  QUICPacketUPtr _create_encrypted_packet(QUICPacketHeaderUPtr header, bool ack_eliciting, bool probing);
 };
diff --git a/iocore/net/quic/QUICPacketHeaderProtector.cc b/iocore/net/quic/QUICPacketHeaderProtector.cc
index 0dbfe8d..f6a71b4 100644
--- a/iocore/net/quic/QUICPacketHeaderProtector.cc
+++ b/iocore/net/quic/QUICPacketHeaderProtector.cc
@@ -32,19 +32,15 @@ bool
 QUICPacketHeaderProtector::protect(uint8_t *unprotected_packet, size_t unprotected_packet_len, int dcil) const
 {
   // Do nothing if the packet is VN
-  if (QUICInvariants::is_long_header(unprotected_packet)) {
-    QUICVersion version;
-    QUICPacketLongHeader::version(version, unprotected_packet, unprotected_packet_len);
-    if (version == 0x0) {
-      return true;
-    }
+  QUICPacketType type;
+  QUICPacketR::type(type, unprotected_packet, unprotected_packet_len);
+  if (type == QUICPacketType::VERSION_NEGOTIATION) {
+    return true;
   }
 
   QUICKeyPhase phase;
-  QUICPacketType type;
   if (QUICInvariants::is_long_header(unprotected_packet)) {
-    QUICPacketLongHeader::key_phase(phase, unprotected_packet, unprotected_packet_len);
-    QUICPacketLongHeader::type(type, unprotected_packet, unprotected_packet_len);
+    QUICLongHeaderPacketR::key_phase(phase, unprotected_packet, unprotected_packet_len);
   } else {
     // This is a kind of hack. For short header we need to use the same key for header protection regardless of the key phase.
     phase = QUICKeyPhase::PHASE_0;
@@ -89,24 +85,15 @@ bool
 QUICPacketHeaderProtector::unprotect(uint8_t *protected_packet, size_t protected_packet_len) const
 {
   // Do nothing if the packet is VN or RETRY
-  if (QUICInvariants::is_long_header(protected_packet)) {
-    QUICVersion version;
-    QUICPacketLongHeader::version(version, protected_packet, protected_packet_len);
-    if (version == 0x0) {
-      return true;
-    }
-    QUICPacketType type;
-    QUICPacketLongHeader::type(type, protected_packet, protected_packet_len);
-    if (type == QUICPacketType::RETRY) {
-      return true;
-    }
+  QUICPacketType type;
+  QUICPacketR::type(type, protected_packet, protected_packet_len);
+  if (type == QUICPacketType::VERSION_NEGOTIATION || type == QUICPacketType::RETRY) {
+    return true;
   }
 
   QUICKeyPhase phase;
-  QUICPacketType type;
   if (QUICInvariants::is_long_header(protected_packet)) {
-    QUICPacketLongHeader::key_phase(phase, protected_packet, protected_packet_len);
-    QUICPacketLongHeader::type(type, protected_packet, protected_packet_len);
+    QUICLongHeaderPacketR::key_phase(phase, protected_packet, protected_packet_len);
   } else {
     // This is a kind of hack. For short header we need to use the same key for header protection regardless of the key phase.
     phase = QUICKeyPhase::PHASE_0;
@@ -155,7 +142,7 @@ QUICPacketHeaderProtector::_calc_sample_offset(uint8_t *sample_offset, const uin
     size_t dummy;
     uint8_t length_len;
     size_t length_offset;
-    if (!QUICPacketLongHeader::length(dummy, length_len, length_offset, protected_packet, protected_packet_len)) {
+    if (!QUICLongHeaderPacketR::length(dummy, length_len, length_offset, protected_packet, protected_packet_len)) {
       return false;
     }
 
@@ -175,10 +162,10 @@ QUICPacketHeaderProtector::_unprotect(uint8_t *protected_packet, size_t protecte
   // Unprotect packet number
   if (QUICInvariants::is_long_header(protected_packet)) {
     protected_packet[0] ^= mask[0] & 0x0f;
-    QUICPacketLongHeader::packet_number_offset(pn_offset, protected_packet, protected_packet_len);
+    QUICLongHeaderPacketR::packet_number_offset(pn_offset, protected_packet, protected_packet_len);
   } else {
     protected_packet[0] ^= mask[0] & 0x1f;
-    QUICPacketShortHeader::packet_number_offset(pn_offset, protected_packet, protected_packet_len, QUICConnectionId::SCID_LEN);
+    QUICShortHeaderPacketR::packet_number_offset(pn_offset, protected_packet, protected_packet_len, QUICConnectionId::SCID_LEN);
   }
   uint8_t pn_length = QUICTypeUtil::read_QUICPacketNumberLen(protected_packet);
 
@@ -199,10 +186,10 @@ QUICPacketHeaderProtector::_protect(uint8_t *protected_packet, size_t protected_
   // Protect packet number
   if (QUICInvariants::is_long_header(protected_packet)) {
     protected_packet[0] ^= mask[0] & 0x0f;
-    QUICPacketLongHeader::packet_number_offset(pn_offset, protected_packet, protected_packet_len);
+    QUICLongHeaderPacketR::packet_number_offset(pn_offset, protected_packet, protected_packet_len);
   } else {
     protected_packet[0] ^= mask[0] & 0x1f;
-    QUICPacketShortHeader::packet_number_offset(pn_offset, protected_packet, protected_packet_len, dcil);
+    QUICShortHeaderPacketR::packet_number_offset(pn_offset, protected_packet, protected_packet_len, dcil);
   }
 
   for (int i = 0; i < pn_length; ++i) {
diff --git a/iocore/net/quic/QUICPacketHeaderProtector_boringssl.cc b/iocore/net/quic/QUICPacketHeaderProtector_boringssl.cc
index 54c539e..63e71bf 100644
--- a/iocore/net/quic/QUICPacketHeaderProtector_boringssl.cc
+++ b/iocore/net/quic/QUICPacketHeaderProtector_boringssl.cc
@@ -23,9 +23,36 @@
 
 #include "QUICPacketHeaderProtector.h"
 
+#include "openssl/chacha.h"
+
 bool
 QUICPacketHeaderProtector::_generate_mask(uint8_t *mask, const uint8_t *sample, const uint8_t *key, const EVP_CIPHER *cipher) const
 {
-  ink_assert(!"not implemented");
-  return false;
+  static constexpr unsigned char FIVE_ZEROS[] = {0x00, 0x00, 0x00, 0x00, 0x00};
+
+  if (cipher == nullptr) {
+    uint32_t counter = htole32(*reinterpret_cast<const uint32_t *>(&sample[0]));
+    CRYPTO_chacha_20(mask, FIVE_ZEROS, sizeof(FIVE_ZEROS), key, &sample[4], counter);
+  } else {
+    int len             = 0;
+    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+    if (!ctx) {
+      return false;
+    }
+    if (!EVP_EncryptInit_ex(ctx, cipher, nullptr, key, sample)) {
+      EVP_CIPHER_CTX_free(ctx);
+      return false;
+    }
+    if (!EVP_EncryptUpdate(ctx, mask, &len, sample, 16)) {
+      EVP_CIPHER_CTX_free(ctx);
+      return false;
+    }
+    if (!EVP_EncryptFinal_ex(ctx, mask + len, &len)) {
+      EVP_CIPHER_CTX_free(ctx);
+      return false;
+    }
+    EVP_CIPHER_CTX_free(ctx);
+  }
+
+  return true;
 }
diff --git a/iocore/net/quic/QUICPacketHeaderProtector_openssl.cc b/iocore/net/quic/QUICPacketHeaderProtector_legacy.cc
similarity index 100%
copy from iocore/net/quic/QUICPacketHeaderProtector_openssl.cc
copy to iocore/net/quic/QUICPacketHeaderProtector_legacy.cc
diff --git a/iocore/net/quic/QUICPacketHeaderProtector_openssl.cc b/iocore/net/quic/QUICPacketHeaderProtector_openssl.cc
index 43bbba8..c7d65a1 100644
--- a/iocore/net/quic/QUICPacketHeaderProtector_openssl.cc
+++ b/iocore/net/quic/QUICPacketHeaderProtector_openssl.cc
@@ -29,21 +29,28 @@ QUICPacketHeaderProtector::_generate_mask(uint8_t *mask, const uint8_t *sample,
   static constexpr unsigned char FIVE_ZEROS[] = {0x00, 0x00, 0x00, 0x00, 0x00};
   EVP_CIPHER_CTX *ctx                         = EVP_CIPHER_CTX_new();
 
-  if (!ctx || !EVP_EncryptInit_ex(ctx, cipher, nullptr, key, sample)) {
+  if (!ctx) {
+    return false;
+  }
+  if (!EVP_EncryptInit_ex(ctx, cipher, nullptr, key, sample)) {
+    EVP_CIPHER_CTX_free(ctx);
     return false;
   }
 
   int len = 0;
   if (cipher == EVP_chacha20()) {
     if (!EVP_EncryptUpdate(ctx, mask, &len, FIVE_ZEROS, sizeof(FIVE_ZEROS))) {
+      EVP_CIPHER_CTX_free(ctx);
       return false;
     }
   } else {
     if (!EVP_EncryptUpdate(ctx, mask, &len, sample, 16)) {
+      EVP_CIPHER_CTX_free(ctx);
       return false;
     }
   }
   if (!EVP_EncryptFinal_ex(ctx, mask + len, &len)) {
+    EVP_CIPHER_CTX_free(ctx);
     return false;
   }
 
diff --git a/iocore/net/quic/QUICPacketPayloadProtector.cc b/iocore/net/quic/QUICPacketPayloadProtector.cc
index 374480b..4b8291c 100644
--- a/iocore/net/quic/QUICPacketPayloadProtector.cc
+++ b/iocore/net/quic/QUICPacketPayloadProtector.cc
@@ -46,12 +46,16 @@ QUICPacketPayloadProtector::protect(const Ptr<IOBufferBlock> unprotected_header,
 
   const EVP_CIPHER *cipher = this->_pp_key_info.get_cipher(phase);
 
-  protected_payload = make_ptr<IOBufferBlock>(new_IOBufferBlock());
-  protected_payload->alloc(iobuffer_size_to_index(unprotected_payload->size() + tag_len, BUFFER_SIZE_INDEX_32K));
+  protected_payload              = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  size_t unprotected_payload_len = 0;
+  for (Ptr<IOBufferBlock> tmp = unprotected_payload; tmp; tmp = tmp->next) {
+    unprotected_payload_len += tmp->size();
+  }
+  protected_payload->alloc(iobuffer_size_to_index(unprotected_payload_len + tag_len, BUFFER_SIZE_INDEX_32K));
 
   size_t written_len = 0;
   if (!this->_protect(reinterpret_cast<uint8_t *>(protected_payload->start()), written_len, protected_payload->write_avail(),
-                      unprotected_payload, pkt_num, reinterpret_cast<uint8_t *>(unprotected_header->buf()),
+                      unprotected_payload, pkt_num, reinterpret_cast<uint8_t *>(unprotected_header->start()),
                       unprotected_header->size(), key, iv, iv_len, cipher, tag_len)) {
     Debug(tag, "Failed to encrypt a packet #%" PRIu64 " with keys for %s", pkt_num, QUICDebugNames::key_phase(phase));
     protected_payload = nullptr;
@@ -84,9 +88,9 @@ QUICPacketPayloadProtector::unprotect(const Ptr<IOBufferBlock> unprotected_heade
 
   size_t written_len = 0;
   if (!this->_unprotect(reinterpret_cast<uint8_t *>(unprotected_payload->start()), written_len, unprotected_payload->write_avail(),
-                        reinterpret_cast<uint8_t *>(protected_payload->buf()), protected_payload->size(), pkt_num,
-                        reinterpret_cast<uint8_t *>(unprotected_header->buf()), unprotected_header->size(), key, iv, iv_len, cipher,
-                        tag_len)) {
+                        reinterpret_cast<uint8_t *>(protected_payload->start()), protected_payload->size(), pkt_num,
+                        reinterpret_cast<uint8_t *>(unprotected_header->start()), unprotected_header->size(), key, iv, iv_len,
+                        cipher, tag_len)) {
     Debug(tag, "Failed to decrypt a packet #%" PRIu64, pkt_num);
     unprotected_payload = nullptr;
   } else {
@@ -122,3 +126,111 @@ QUICPacketPayloadProtector::_gen_nonce(uint8_t *nonce, size_t &nonce_len, uint64
     nonce[iv_len - 8 + i] ^= p[i];
   }
 }
+
+bool
+QUICPacketPayloadProtector::_protect(uint8_t *cipher, size_t &cipher_len, size_t max_cipher_len, const Ptr<IOBufferBlock> plain,
+                                     uint64_t pkt_num, const uint8_t *ad, size_t ad_len, const uint8_t *key, const uint8_t *iv,
+                                     size_t iv_len, const EVP_CIPHER *aead, size_t tag_len) const
+{
+  EVP_CIPHER_CTX *aead_ctx;
+  int len;
+  uint8_t nonce[EVP_MAX_IV_LENGTH] = {0};
+  size_t nonce_len                 = 0;
+
+  this->_gen_nonce(nonce, nonce_len, pkt_num, iv, iv_len);
+
+  if (!(aead_ctx = EVP_CIPHER_CTX_new())) {
+    return false;
+  }
+  if (!EVP_EncryptInit_ex(aead_ctx, aead, nullptr, nullptr, nullptr)) {
+    return false;
+  }
+  if (!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_SET_IVLEN, nonce_len, nullptr)) {
+    return false;
+  }
+  if (!EVP_EncryptInit_ex(aead_ctx, nullptr, nullptr, key, nonce)) {
+    return false;
+  }
+  if (!EVP_EncryptUpdate(aead_ctx, nullptr, &len, ad, ad_len)) {
+    return false;
+  }
+
+  cipher_len = 0;
+  for (Ptr<IOBufferBlock> b = plain; b; b = b->next) {
+    if (!EVP_EncryptUpdate(aead_ctx, cipher + cipher_len, &len, reinterpret_cast<unsigned char *>(b->start()), b->size())) {
+      return false;
+    }
+    cipher_len += len;
+  }
+
+  if (!EVP_EncryptFinal_ex(aead_ctx, cipher + cipher_len, &len)) {
+    return false;
+  }
+  cipher_len += len;
+
+  if (max_cipher_len < cipher_len + tag_len) {
+    return false;
+  }
+  if (!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_GET_TAG, tag_len, cipher + cipher_len)) {
+    return false;
+  }
+  cipher_len += tag_len;
+
+  EVP_CIPHER_CTX_free(aead_ctx);
+
+  return true;
+}
+
+bool
+QUICPacketPayloadProtector::_unprotect(uint8_t *plain, size_t &plain_len, size_t max_plain_len, const uint8_t *cipher,
+                                       size_t cipher_len, uint64_t pkt_num, const uint8_t *ad, size_t ad_len, const uint8_t *key,
+                                       const uint8_t *iv, size_t iv_len, const EVP_CIPHER *aead, size_t tag_len) const
+{
+  EVP_CIPHER_CTX *aead_ctx;
+  int len;
+  uint8_t nonce[EVP_MAX_IV_LENGTH] = {0};
+  size_t nonce_len                 = 0;
+
+  this->_gen_nonce(nonce, nonce_len, pkt_num, iv, iv_len);
+
+  if (!(aead_ctx = EVP_CIPHER_CTX_new())) {
+    return false;
+  }
+  if (!EVP_DecryptInit_ex(aead_ctx, aead, nullptr, nullptr, nullptr)) {
+    return false;
+  }
+  if (!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_SET_IVLEN, nonce_len, nullptr)) {
+    return false;
+  }
+  if (!EVP_DecryptInit_ex(aead_ctx, nullptr, nullptr, key, nonce)) {
+    return false;
+  }
+  if (!EVP_DecryptUpdate(aead_ctx, nullptr, &len, ad, ad_len)) {
+    return false;
+  }
+
+  if (cipher_len < tag_len) {
+    return false;
+  }
+  cipher_len -= tag_len;
+  if (!EVP_DecryptUpdate(aead_ctx, plain, &len, cipher, cipher_len)) {
+    return false;
+  }
+  plain_len = len;
+
+  if (!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, const_cast<uint8_t *>(cipher + cipher_len))) {
+    return false;
+  }
+
+  int ret = EVP_DecryptFinal_ex(aead_ctx, plain + len, &len);
+
+  EVP_CIPHER_CTX_free(aead_ctx);
+
+  if (ret > 0) {
+    plain_len += len;
+    return true;
+  } else {
+    Debug(tag, "Failed to decrypt -- the first 4 bytes decrypted are %0x %0x %0x %0x", plain[0], plain[1], plain[2], plain[3]);
+    return false;
+  }
+}
diff --git a/iocore/net/quic/QUICPacketPayloadProtector_boringssl.cc b/iocore/net/quic/QUICPacketPayloadProtector_boringssl.cc
index 56e88dd..0352e51 100644
--- a/iocore/net/quic/QUICPacketPayloadProtector_boringssl.cc
+++ b/iocore/net/quic/QUICPacketPayloadProtector_boringssl.cc
@@ -25,24 +25,129 @@
 #include "QUICPacketPayloadProtector.h"
 #include "tscore/Diags.h"
 
-// static constexpr char tag[] = "quic_ppp";
+static constexpr char tag[] = "quic_ppp";
 
 bool
-QUICPacketPayloadProtector::_protect(uint8_t *protected_payload, size_t &protected_payload_len, size_t max_protecgted_payload_len,
-                                     const Ptr<IOBufferBlock> plain, uint64_t pkt_num, const uint8_t *ad, size_t ad_len,
-                                     const uint8_t *key, const uint8_t *iv, size_t iv_len, const EVP_CIPHER *cipher,
-                                     size_t tag_len) const
+QUICPacketPayloadProtector::_protect(uint8_t *cipher, size_t &cipher_len, size_t max_cipher_len, const Ptr<IOBufferBlock> plain,
+                                     uint64_t pkt_num, const uint8_t *ad, size_t ad_len, const uint8_t *key, const uint8_t *iv,
+                                     size_t iv_len, const EVP_CIPHER *aead, size_t tag_len) const
 {
-  ink_assert(!"not implemented");
-  return false;
+  EVP_CIPHER_CTX *aead_ctx;
+  int len;
+  uint8_t nonce[EVP_MAX_IV_LENGTH] = {0};
+  size_t nonce_len                 = 0;
+
+  this->_gen_nonce(nonce, nonce_len, pkt_num, iv, iv_len);
+
+  if (!(aead_ctx = EVP_CIPHER_CTX_new())) {
+    return false;
+  }
+  if (!EVP_EncryptInit_ex(aead_ctx, aead, nullptr, nullptr, nullptr)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
+    return false;
+  }
+  if (!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_SET_IVLEN, nonce_len, nullptr)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
+    return false;
+  }
+  if (!EVP_EncryptInit_ex(aead_ctx, nullptr, nullptr, key, nonce)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
+    return false;
+  }
+  if (!EVP_EncryptUpdate(aead_ctx, nullptr, &len, ad, ad_len)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
+    return false;
+  }
+
+  cipher_len           = 0;
+  Ptr<IOBufferBlock> b = plain;
+  while (b) {
+    if (!EVP_EncryptUpdate(aead_ctx, cipher + cipher_len, &len, reinterpret_cast<unsigned char *>(b->buf()), b->size())) {
+      EVP_CIPHER_CTX_free(aead_ctx);
+      return false;
+    }
+    cipher_len += len;
+    b = b->next;
+  }
+
+  if (!EVP_EncryptFinal_ex(aead_ctx, cipher + cipher_len, &len)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
+    return false;
+  }
+  cipher_len += len;
+
+  if (max_cipher_len < cipher_len + tag_len) {
+    EVP_CIPHER_CTX_free(aead_ctx);
+    return false;
+  }
+  if (!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_GET_TAG, tag_len, cipher + cipher_len)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
+    return false;
+  }
+  cipher_len += tag_len;
+
+  EVP_CIPHER_CTX_free(aead_ctx);
+
+  return true;
 }
 
 bool
-QUICPacketPayloadProtector::_unprotect(uint8_t *plain, size_t &plain_len, size_t max_plain_len, const uint8_t *protected_payload,
-                                       size_t protected_payload_len, uint64_t pkt_num, const uint8_t *ad, size_t ad_len,
-                                       const uint8_t *key, const uint8_t *iv, size_t iv_len, const EVP_CIPHER *cipher,
-                                       size_t tag_len) const
+QUICPacketPayloadProtector::_unprotect(uint8_t *plain, size_t &plain_len, size_t max_plain_len, const uint8_t *cipher,
+                                       size_t cipher_len, uint64_t pkt_num, const uint8_t *ad, size_t ad_len, const uint8_t *key,
+                                       const uint8_t *iv, size_t iv_len, const EVP_CIPHER *aead, size_t tag_len) const
 {
-  ink_assert(!"not implemented");
-  return false;
+  EVP_CIPHER_CTX *aead_ctx;
+  int len;
+  uint8_t nonce[EVP_MAX_IV_LENGTH] = {0};
+  size_t nonce_len                 = 0;
+
+  this->_gen_nonce(nonce, nonce_len, pkt_num, iv, iv_len);
+
+  if (!(aead_ctx = EVP_CIPHER_CTX_new())) {
+    return false;
+  }
+  if (!EVP_DecryptInit_ex(aead_ctx, aead, nullptr, nullptr, nullptr)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
+    return false;
+  }
+  if (!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_SET_IVLEN, nonce_len, nullptr)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
+    return false;
+  }
+  if (!EVP_DecryptInit_ex(aead_ctx, nullptr, nullptr, key, nonce)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
+    return false;
+  }
+  if (!EVP_DecryptUpdate(aead_ctx, nullptr, &len, ad, ad_len)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
+    return false;
+  }
+
+  if (cipher_len < tag_len) {
+    EVP_CIPHER_CTX_free(aead_ctx);
+    return false;
+  }
+  cipher_len -= tag_len;
+  if (!EVP_DecryptUpdate(aead_ctx, plain, &len, cipher, cipher_len)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
+    return false;
+  }
+  plain_len = len;
+
+  if (!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, const_cast<uint8_t *>(cipher + cipher_len))) {
+    EVP_CIPHER_CTX_free(aead_ctx);
+    return false;
+  }
+
+  int ret = EVP_DecryptFinal_ex(aead_ctx, plain + len, &len);
+
+  EVP_CIPHER_CTX_free(aead_ctx);
+
+  if (ret > 0) {
+    plain_len += len;
+    return true;
+  } else {
+    Debug(tag, "Failed to decrypt -- the first 4 bytes decrypted are %0x %0x %0x %0x", plain[0], plain[1], plain[2], plain[3]);
+    return false;
+  }
 }
diff --git a/iocore/net/quic/QUICPacketPayloadProtector_openssl.cc b/iocore/net/quic/QUICPacketPayloadProtector_legacy.cc
similarity index 96%
copy from iocore/net/quic/QUICPacketPayloadProtector_openssl.cc
copy to iocore/net/quic/QUICPacketPayloadProtector_legacy.cc
index b25f099..5fc9e2e 100644
--- a/iocore/net/quic/QUICPacketPayloadProtector_openssl.cc
+++ b/iocore/net/quic/QUICPacketPayloadProtector_legacy.cc
@@ -55,14 +55,12 @@ QUICPacketPayloadProtector::_protect(uint8_t *cipher, size_t &cipher_len, size_t
     return false;
   }
 
-  cipher_len           = 0;
-  Ptr<IOBufferBlock> b = plain;
-  while (b) {
-    if (!EVP_EncryptUpdate(aead_ctx, cipher + cipher_len, &len, reinterpret_cast<unsigned char *>(b->buf()), b->size())) {
+  cipher_len = 0;
+  for (Ptr<IOBufferBlock> b = plain; b; b = b->next) {
+    if (!EVP_EncryptUpdate(aead_ctx, cipher + cipher_len, &len, reinterpret_cast<unsigned char *>(b->start()), b->size())) {
       return false;
     }
     cipher_len += len;
-    b = b->next;
   }
 
   if (!EVP_EncryptFinal_ex(aead_ctx, cipher + cipher_len, &len)) {
diff --git a/iocore/net/quic/QUICPacketPayloadProtector_openssl.cc b/iocore/net/quic/QUICPacketPayloadProtector_openssl.cc
index b25f099..c900718 100644
--- a/iocore/net/quic/QUICPacketPayloadProtector_openssl.cc
+++ b/iocore/net/quic/QUICPacketPayloadProtector_openssl.cc
@@ -43,15 +43,19 @@ QUICPacketPayloadProtector::_protect(uint8_t *cipher, size_t &cipher_len, size_t
     return false;
   }
   if (!EVP_EncryptInit_ex(aead_ctx, aead, nullptr, nullptr, nullptr)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
     return false;
   }
   if (!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_SET_IVLEN, nonce_len, nullptr)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
     return false;
   }
   if (!EVP_EncryptInit_ex(aead_ctx, nullptr, nullptr, key, nonce)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
     return false;
   }
   if (!EVP_EncryptUpdate(aead_ctx, nullptr, &len, ad, ad_len)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
     return false;
   }
 
@@ -59,6 +63,7 @@ QUICPacketPayloadProtector::_protect(uint8_t *cipher, size_t &cipher_len, size_t
   Ptr<IOBufferBlock> b = plain;
   while (b) {
     if (!EVP_EncryptUpdate(aead_ctx, cipher + cipher_len, &len, reinterpret_cast<unsigned char *>(b->buf()), b->size())) {
+      EVP_CIPHER_CTX_free(aead_ctx);
       return false;
     }
     cipher_len += len;
@@ -66,14 +71,17 @@ QUICPacketPayloadProtector::_protect(uint8_t *cipher, size_t &cipher_len, size_t
   }
 
   if (!EVP_EncryptFinal_ex(aead_ctx, cipher + cipher_len, &len)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
     return false;
   }
   cipher_len += len;
 
   if (max_cipher_len < cipher_len + tag_len) {
+    EVP_CIPHER_CTX_free(aead_ctx);
     return false;
   }
   if (!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_GET_TAG, tag_len, cipher + cipher_len)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
     return false;
   }
   cipher_len += tag_len;
@@ -99,28 +107,35 @@ QUICPacketPayloadProtector::_unprotect(uint8_t *plain, size_t &plain_len, size_t
     return false;
   }
   if (!EVP_DecryptInit_ex(aead_ctx, aead, nullptr, nullptr, nullptr)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
     return false;
   }
   if (!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_SET_IVLEN, nonce_len, nullptr)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
     return false;
   }
   if (!EVP_DecryptInit_ex(aead_ctx, nullptr, nullptr, key, nonce)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
     return false;
   }
   if (!EVP_DecryptUpdate(aead_ctx, nullptr, &len, ad, ad_len)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
     return false;
   }
 
   if (cipher_len < tag_len) {
+    EVP_CIPHER_CTX_free(aead_ctx);
     return false;
   }
   cipher_len -= tag_len;
   if (!EVP_DecryptUpdate(aead_ctx, plain, &len, cipher, cipher_len)) {
+    EVP_CIPHER_CTX_free(aead_ctx);
     return false;
   }
   plain_len = len;
 
   if (!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, const_cast<uint8_t *>(cipher + cipher_len))) {
+    EVP_CIPHER_CTX_free(aead_ctx);
     return false;
   }
 
diff --git a/iocore/net/quic/QUICPacketReceiveQueue.cc b/iocore/net/quic/QUICPacketReceiveQueue.cc
index c90385b..f197790 100644
--- a/iocore/net/quic/QUICPacketReceiveQueue.cc
+++ b/iocore/net/quic/QUICPacketReceiveQueue.cc
@@ -22,6 +22,7 @@
  */
 
 #include "QUICPacketReceiveQueue.h"
+#include "QUICPacketHeaderProtector.h"
 #include "QUICPacketFactory.h"
 
 #include "QUICIntUtil.h"
@@ -44,7 +45,7 @@ QUICPacketReceiveQueue::enqueue(UDPPacket *packet)
 }
 
 QUICPacketUPtr
-QUICPacketReceiveQueue::dequeue(QUICPacketCreationResult &result)
+QUICPacketReceiveQueue::dequeue(uint8_t *packet_buf, QUICPacketCreationResult &result)
 {
   QUICPacketUPtr quic_packet = QUICPacketFactory::create_null_packet();
   UDPPacket *udp_packet      = nullptr;
@@ -67,7 +68,7 @@ QUICPacketReceiveQueue::dequeue(QUICPacketCreationResult &result)
     IOBufferBlock *b   = udp_packet->getIOBlockChain();
     size_t written     = 0;
     while (b) {
-      memcpy(this->_payload.get() + written, b->buf(), b->read_avail());
+      memcpy(this->_payload.get() + written, b->start(), b->read_avail());
       written += b->read_avail();
       b = b->next.get();
     }
@@ -83,7 +84,7 @@ QUICPacketReceiveQueue::dequeue(QUICPacketCreationResult &result)
 
     if (QUICInvariants::is_long_header(buf)) {
       QUICVersion version;
-      QUICPacketLongHeader::version(version, buf, remaining_len);
+      QUICLongHeaderPacketR::version(version, buf, remaining_len);
       if (is_vn(version)) {
         pkt_len = remaining_len;
         type    = QUICPacketType::VERSION_NEGOTIATION;
@@ -91,17 +92,17 @@ QUICPacketReceiveQueue::dequeue(QUICPacketCreationResult &result)
         result  = QUICPacketCreationResult::UNSUPPORTED;
         pkt_len = remaining_len;
       } else {
-        QUICPacketLongHeader::type(type, this->_payload.get() + this->_offset, remaining_len);
+        QUICLongHeaderPacketR::type(type, this->_payload.get() + this->_offset, remaining_len);
         if (type == QUICPacketType::RETRY) {
           pkt_len = remaining_len;
         } else {
-          if (!QUICPacketLongHeader::packet_length(pkt_len, this->_payload.get() + this->_offset, remaining_len)) {
+          if (!QUICLongHeaderPacketR::packet_length(pkt_len, this->_payload.get() + this->_offset, remaining_len)) {
+            // This should not happen basically. Ignore rest of data on current packet.
             this->_payload.release();
             this->_payload     = nullptr;
             this->_payload_len = 0;
             this->_offset      = 0;
-
-            result = QUICPacketCreationResult::IGNORED;
+            result             = QUICPacketCreationResult::IGNORED;
 
             return quic_packet;
           }
@@ -148,7 +149,7 @@ QUICPacketReceiveQueue::dequeue(QUICPacketCreationResult &result)
   }
 
   if (this->_ph_protector.unprotect(pkt.get(), pkt_len)) {
-    quic_packet = this->_packet_factory.create(this->_udp_con, this->_from, this->_to, std::move(pkt), pkt_len,
+    quic_packet = this->_packet_factory.create(packet_buf, this->_udp_con, this->_from, this->_to, std::move(pkt), pkt_len,
                                                this->_largest_received_packet_number, result);
   } else {
     // ZERO_RTT might be rejected
@@ -175,7 +176,8 @@ QUICPacketReceiveQueue::dequeue(QUICPacketCreationResult &result)
     // do nothing - if the packet is unsupported version, we don't know packet number
     break;
   default:
-    if (quic_packet && quic_packet->packet_number() > this->_largest_received_packet_number) {
+    if (quic_packet && quic_packet->type() != QUICPacketType::VERSION_NEGOTIATION &&
+        quic_packet->packet_number() > this->_largest_received_packet_number) {
       this->_largest_received_packet_number = quic_packet->packet_number();
     }
   }
diff --git a/iocore/net/quic/QUICPacketReceiveQueue.h b/iocore/net/quic/QUICPacketReceiveQueue.h
index e4e3e29..78e5b00 100644
--- a/iocore/net/quic/QUICPacketReceiveQueue.h
+++ b/iocore/net/quic/QUICPacketReceiveQueue.h
@@ -29,6 +29,7 @@
 #include "QUICPacket.h"
 
 class QUICPacketFactory;
+class QUICPacketHeaderProtector;
 
 class QUICPacketReceiveQueue
 {
@@ -36,7 +37,7 @@ public:
   QUICPacketReceiveQueue(QUICPacketFactory &packet_factory, QUICPacketHeaderProtector &ph_protector);
 
   void enqueue(UDPPacket *packet);
-  QUICPacketUPtr dequeue(QUICPacketCreationResult &result);
+  QUICPacketUPtr dequeue(uint8_t *packet_buf, QUICPacketCreationResult &result);
   uint32_t size();
   void reset();
 
diff --git a/iocore/net/quic/QUICPathManager.cc b/iocore/net/quic/QUICPathManager.cc
index 613efec..c0adbde 100644
--- a/iocore/net/quic/QUICPathManager.cc
+++ b/iocore/net/quic/QUICPathManager.cc
@@ -28,7 +28,7 @@
 #define QUICDebug(fmt, ...) Debug("quic_path", "[%s] " fmt, this->_cinfo.cids().data(), ##__VA_ARGS__)
 
 void
-QUICPathManager::open_new_path(const QUICPath &path, ink_hrtime timeout_in)
+QUICPathManagerImpl::open_new_path(const QUICPath &path, ink_hrtime timeout_in)
 {
   if (this->_verify_timeout_at == 0) {
     // Overwrite _previous_path only if _current_path is verified
@@ -41,14 +41,14 @@ QUICPathManager::open_new_path(const QUICPath &path, ink_hrtime timeout_in)
 }
 
 void
-QUICPathManager::set_trusted_path(const QUICPath &path)
+QUICPathManagerImpl::set_trusted_path(const QUICPath &path)
 {
   this->_current_path  = path;
   this->_previous_path = path;
 }
 
 void
-QUICPathManager::_check_verify_timeout()
+QUICPathManagerImpl::_check_verify_timeout()
 {
   if (this->_verify_timeout_at != 0) {
     if (this->_path_validator.is_validated(this->_current_path)) {
@@ -66,14 +66,14 @@ QUICPathManager::_check_verify_timeout()
 }
 
 const QUICPath &
-QUICPathManager::get_current_path()
+QUICPathManagerImpl::get_current_path()
 {
   this->_check_verify_timeout();
   return this->_current_path;
 }
 
 const QUICPath &
-QUICPathManager::get_verified_path()
+QUICPathManagerImpl::get_verified_path()
 {
   this->_check_verify_timeout();
   if (this->_verify_timeout_at != 0) {
diff --git a/iocore/net/quic/QUICPathManager.h b/iocore/net/quic/QUICPathManager.h
index 9f9eb37..1d4542a 100644
--- a/iocore/net/quic/QUICPathManager.h
+++ b/iocore/net/quic/QUICPathManager.h
@@ -31,15 +31,25 @@ class QUICPathValidator;
 class QUICPathManager
 {
 public:
-  QUICPathManager(const QUICConnectionInfoProvider &info, QUICPathValidator &path_validator)
+  virtual ~QUICPathManager() {}
+  virtual const QUICPath &get_current_path()                              = 0;
+  virtual const QUICPath &get_verified_path()                             = 0;
+  virtual void open_new_path(const QUICPath &path, ink_hrtime timeout_in) = 0;
+  virtual void set_trusted_path(const QUICPath &path)                     = 0;
+};
+
+class QUICPathManagerImpl : public QUICPathManager
+{
+public:
+  QUICPathManagerImpl(const QUICConnectionInfoProvider &info, QUICPathValidator &path_validator)
     : _cinfo(info), _path_validator(path_validator)
   {
   }
 
-  const QUICPath &get_current_path();
-  const QUICPath &get_verified_path();
-  void open_new_path(const QUICPath &path, ink_hrtime timeout_in);
-  void set_trusted_path(const QUICPath &path);
+  const QUICPath &get_current_path() override;
+  const QUICPath &get_verified_path() override;
+  void open_new_path(const QUICPath &path, ink_hrtime timeout_in) override;
+  void set_trusted_path(const QUICPath &path) override;
 
 private:
   const QUICConnectionInfoProvider &_cinfo;
diff --git a/iocore/net/quic/QUICPinger.cc b/iocore/net/quic/QUICPinger.cc
index 84454c8..2514c02 100644
--- a/iocore/net/quic/QUICPinger.cc
+++ b/iocore/net/quic/QUICPinger.cc
@@ -24,18 +24,20 @@
 #include "QUICPinger.h"
 
 void
-QUICPinger::request()
+QUICPinger::request(QUICEncryptionLevel level)
 {
   SCOPED_MUTEX_LOCK(lock, this->_mutex, this_ethread());
-  ++this->_need_to_fire;
+  ink_assert(level != QUICEncryptionLevel::NONE);
+  ++this->_need_to_fire[static_cast<int>(level)];
 }
 
 void
-QUICPinger::cancel()
+QUICPinger::cancel(QUICEncryptionLevel level)
 {
   SCOPED_MUTEX_LOCK(lock, this->_mutex, this_ethread());
-  if (this->_need_to_fire > 0) {
-    --this->_need_to_fire;
+  ink_assert(level != QUICEncryptionLevel::NONE);
+  if (this->_need_to_fire[static_cast<int>(level)] > 0) {
+    --this->_need_to_fire[static_cast<int>(level)];
   }
 }
 
@@ -44,27 +46,25 @@ QUICPinger::_will_generate_frame(QUICEncryptionLevel level, size_t current_packe
 {
   SCOPED_MUTEX_LOCK(lock, this->_mutex, this_ethread());
 
-  if (level != QUICEncryptionLevel::ONE_RTT) {
-    return false;
-  }
-
   // PING Frame is meaningless for ack_eliciting packet. Cancel it.
   if (ack_eliciting) {
     this->_ack_eliciting_packet_out = true;
-    this->cancel();
+    this->cancel(level);
     return false;
   }
 
-  if (this->_ack_eliciting_packet_out == false && !ack_eliciting && current_packet_size > 0 && this->_need_to_fire == 0) {
+  ink_assert(level != QUICEncryptionLevel::NONE);
+  if (this->_ack_eliciting_packet_out == false && !ack_eliciting && current_packet_size > 0 &&
+      this->_need_to_fire[static_cast<int>(level)] == 0) {
     // force to send an PING Frame
-    this->request();
+    this->request(level);
   }
 
   // only update `_ack_eliciting_packet_out` when we has something to send.
   if (current_packet_size) {
     this->_ack_eliciting_packet_out = ack_eliciting;
   }
-  return this->_need_to_fire > 0;
+  return this->_need_to_fire[static_cast<int>(level)] > 0;
 }
 
 /**
@@ -77,10 +77,11 @@ QUICPinger::_generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /*
   SCOPED_MUTEX_LOCK(lock, this->_mutex, this_ethread());
   QUICFrame *frame = nullptr;
 
-  if (level == QUICEncryptionLevel::ONE_RTT && this->_need_to_fire > 0 && maximum_frame_size > 0) {
+  ink_assert(level != QUICEncryptionLevel::NONE);
+  if (this->_need_to_fire[static_cast<int>(level)] > 0 && maximum_frame_size > 0) {
     // don't care ping frame lost or acked
     frame = QUICFrameFactory::create_ping_frame(buf, 0, nullptr);
-    --this->_need_to_fire;
+    --this->_need_to_fire[static_cast<int>(level)];
     this->_ack_eliciting_packet_out = true;
   }
 
@@ -88,8 +89,9 @@ QUICPinger::_generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /*
 }
 
 uint64_t
-QUICPinger::count()
+QUICPinger::count(QUICEncryptionLevel level)
 {
   SCOPED_MUTEX_LOCK(lock, this->_mutex, this_ethread());
-  return this->_need_to_fire;
+  ink_assert(level != QUICEncryptionLevel::NONE);
+  return this->_need_to_fire[static_cast<int>(level)];
 }
diff --git a/iocore/net/quic/QUICPinger.h b/iocore/net/quic/QUICPinger.h
index 483cfb2..0ea9b58 100644
--- a/iocore/net/quic/QUICPinger.h
+++ b/iocore/net/quic/QUICPinger.h
@@ -35,9 +35,9 @@ class QUICPinger : public QUICFrameOnceGenerator
 public:
   QUICPinger() : _mutex(new_ProxyMutex()) {}
 
-  void request();
-  void cancel();
-  uint64_t count();
+  void request(QUICEncryptionLevel level);
+  void cancel(QUICEncryptionLevel level);
+  uint64_t count(QUICEncryptionLevel level);
 
 private:
   // QUICFrameGenerator
@@ -48,5 +48,5 @@ private:
   bool _ack_eliciting_packet_out = false;
 
   Ptr<ProxyMutex> _mutex;
-  uint64_t _need_to_fire = 0;
+  uint64_t _need_to_fire[4] = {0}; // Initial, 0RTT, HANDSHAKE and 1RTT
 };
diff --git a/iocore/net/quic/QUICResetTokenTable.cc b/iocore/net/quic/QUICResetTokenTable.cc
new file mode 100644
index 0000000..0da753f
--- /dev/null
+++ b/iocore/net/quic/QUICResetTokenTable.cc
@@ -0,0 +1,53 @@
+/** @file
+
+  @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.
+ */
+
+#include "tscore/Diags.h"
+#include "QUICResetTokenTable.h"
+#include "QUICConnection.h"
+
+void
+QUICResetTokenTable::insert(const QUICStatelessResetToken token, QUICConnection *connection)
+{
+  Debug("quic_reset_token_table", "Token:%02x%02x%02x%02x... CID:%08" PRIx32 "...", token.buf()[0], token.buf()[1], token.buf()[2],
+        token.buf()[3], connection->connection_id().h32());
+  this->_map.emplace(token, connection);
+}
+
+QUICConnection *
+QUICResetTokenTable::lookup(QUICStatelessResetToken token)
+{
+  Debug("quic_reset_token_table", "Token:%02x%02x%02x%02x...", token.buf()[0], token.buf()[1], token.buf()[2], token.buf()[3]);
+  auto result = this->_map.find(token);
+  if (result != this->_map.end()) {
+    Debug("quic_reset_token_table", "CID:%08" PRIx32 "...", result->second->connection_id().h32());
+    return result->second;
+  } else {
+    Debug("quic_reset_token_table", "not fouund");
+    return nullptr;
+  }
+}
+
+void
+QUICResetTokenTable::erase(const QUICStatelessResetToken token)
+{
+  Debug("quic_reset_token_table", "Token:%02x%02x%02x%02x...", token.buf()[0], token.buf()[1], token.buf()[2], token.buf()[3]);
+  this->_map.erase(token);
+}
diff --git a/proxy/http3/Http3StreamDataVIOAdaptor.h b/iocore/net/quic/QUICResetTokenTable.h
similarity index 62%
copy from proxy/http3/Http3StreamDataVIOAdaptor.h
copy to iocore/net/quic/QUICResetTokenTable.h
index f62fd31..559b264 100644
--- a/proxy/http3/Http3StreamDataVIOAdaptor.h
+++ b/iocore/net/quic/QUICResetTokenTable.h
@@ -1,7 +1,5 @@
 /** @file
 
-  A brief file description
-
   @section license License
 
   Licensed to the Apache Software Foundation (ASF) under one
@@ -23,19 +21,26 @@
 
 #pragma once
 
-#include "Http3FrameHandler.h"
+#include "QUICTypes.h"
 
-class VIO;
+class QUICConnection;
 
-class Http3StreamDataVIOAdaptor : public Http3FrameHandler
+class QUICResetTokenTable
 {
 public:
-  Http3StreamDataVIOAdaptor(VIO *sink);
-
-  // Http3FrameHandler
-  std::vector<Http3FrameType> interests() override;
-  Http3ErrorUPtr handle_frame(std::shared_ptr<const Http3Frame> frame) override;
+  void insert(const QUICStatelessResetToken token, QUICConnection *connection);
+  QUICConnection *lookup(const QUICStatelessResetToken token);
+  void erase(const QUICStatelessResetToken token);
 
 private:
-  VIO *_sink_vio = nullptr;
+  class TokenHasher
+  {
+  public:
+    uint64_t
+    operator()(const QUICStatelessResetToken &key) const
+    {
+      return static_cast<uint64_t>(key);
+    }
+  };
+  std::unordered_map<QUICStatelessResetToken, QUICConnection *, TokenHasher> _map;
 };
diff --git a/iocore/net/quic/QUICRetryIntegrityTag.cc b/iocore/net/quic/QUICRetryIntegrityTag.cc
new file mode 100644
index 0000000..c528656
--- /dev/null
+++ b/iocore/net/quic/QUICRetryIntegrityTag.cc
@@ -0,0 +1,79 @@
+/** @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.
+ */
+
+#include "QUICRetryIntegrityTag.h"
+
+bool
+QUICRetryIntegrityTag::compute(uint8_t *out, QUICConnectionId odcid, Ptr<IOBufferBlock> header, Ptr<IOBufferBlock> payload)
+{
+  EVP_CIPHER_CTX *aead_ctx;
+
+  if (!(aead_ctx = EVP_CIPHER_CTX_new())) {
+    return false;
+  }
+  if (!EVP_EncryptInit_ex(aead_ctx, EVP_aes_128_gcm(), nullptr, nullptr, nullptr)) {
+    return false;
+  }
+  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)) {
+    return false;
+  }
+
+  // Original Destination Connection ID
+  size_t n;
+  int dummy;
+  uint8_t odcid_buf[1 + QUICConnectionId::MAX_LENGTH];
+  // Len
+  odcid_buf[0] = odcid.length();
+  // ID
+  QUICTypeUtil::write_QUICConnectionId(odcid, odcid_buf + 1, &n);
+  if (!EVP_EncryptUpdate(aead_ctx, nullptr, &dummy, odcid_buf, 1 + odcid.length())) {
+    return false;
+  }
+
+  // Retry Packet
+  for (Ptr<IOBufferBlock> b = header; b; b = b->next) {
+    if (!EVP_EncryptUpdate(aead_ctx, nullptr, &dummy, reinterpret_cast<unsigned char *>(b->start()), b->size())) {
+      return false;
+    }
+  }
+  for (Ptr<IOBufferBlock> b = payload; b; b = b->next) {
+    if (!EVP_EncryptUpdate(aead_ctx, nullptr, &dummy, reinterpret_cast<unsigned char *>(b->start()), b->size())) {
+      return false;
+    }
+  }
+
+  if (!EVP_EncryptFinal_ex(aead_ctx, nullptr, &dummy)) {
+    return false;
+  }
+
+  if (!EVP_CIPHER_CTX_ctrl(aead_ctx, EVP_CTRL_AEAD_GET_TAG, LEN, out)) {
+    return false;
+  }
+
+  EVP_CIPHER_CTX_free(aead_ctx);
+
+  return true;
+}
diff --git a/iocore/net/quic/QUICIntUtil.h b/iocore/net/quic/QUICRetryIntegrityTag.h
similarity index 59%
copy from iocore/net/quic/QUICIntUtil.h
copy to iocore/net/quic/QUICRetryIntegrityTag.h
index c259bca..3ac9498 100644
--- a/iocore/net/quic/QUICIntUtil.h
+++ b/iocore/net/quic/QUICRetryIntegrityTag.h
@@ -23,23 +23,17 @@
 
 #pragma once
 
-#include <cstddef>
-#include <cstdint>
+#include "QUICTypes.h"
 
-class QUICVariableInt
+class QUICRetryIntegrityTag
 {
 public:
-  static size_t size(const uint8_t *src);
-  static size_t size(uint64_t src);
-  static int encode(uint8_t *dst, size_t dst_len, size_t &len, uint64_t src);
-  static int decode(uint64_t &dst, size_t &len, const uint8_t *src, size_t src_len = 8);
-};
+  static constexpr int LEN = 16;
+  static bool compute(uint8_t *out, QUICConnectionId odcid, Ptr<IOBufferBlock> header, Ptr<IOBufferBlock> payload);
 
-class QUICIntUtil
-{
-public:
-  static uint64_t read_QUICVariableInt(const uint8_t *buf);
-  static void write_QUICVariableInt(uint64_t data, uint8_t *buf, size_t *len);
-  static uint64_t read_nbytes_as_uint(const uint8_t *buf, uint8_t n);
-  static void write_uint_as_nbytes(uint64_t value, uint8_t n, uint8_t *buf, size_t *len);
+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};
 };
diff --git a/iocore/net/quic/QUICStream.cc b/iocore/net/quic/QUICStream.cc
index 334b9f4..93ac8f1 100644
--- a/iocore/net/quic/QUICStream.cc
+++ b/iocore/net/quic/QUICStream.cc
@@ -245,7 +245,7 @@ QUICStreamVConnection::_signal_read_event()
   }
   MUTEX_TRY_LOCK(lock, this->_read_vio.mutex, this_ethread());
 
-  int event = this->_read_vio.ntodo() ? VC_EVENT_READ_READY : VC_EVENT_READ_COMPLETE;
+  int event = this->_read_vio.nbytes == INT64_MAX ? VC_EVENT_READ_READY : VC_EVENT_READ_COMPLETE;
 
   if (lock.is_locked()) {
     this->_read_vio.cont->handleEvent(event, &this->_read_vio);
diff --git a/iocore/net/quic/QUICStreamManager.cc b/iocore/net/quic/QUICStreamManager.cc
index de4ffb9..da66d74 100644
--- a/iocore/net/quic/QUICStreamManager.cc
+++ b/iocore/net/quic/QUICStreamManager.cc
@@ -29,10 +29,10 @@
 static constexpr char tag[]                     = "quic_stream_manager";
 static constexpr QUICStreamId QUIC_STREAM_TYPES = 4;
 
-QUICStreamManager::QUICStreamManager(QUICConnectionInfoProvider *info, QUICRTTProvider *rtt_provider, QUICApplicationMap *app_map)
-  : _stream_factory(rtt_provider, info), _info(info), _app_map(app_map)
+QUICStreamManager::QUICStreamManager(QUICContext *context, QUICApplicationMap *app_map)
+  : _stream_factory(context->rtt_provider(), context->connection_info()), _context(context), _app_map(app_map)
 {
-  if (this->_info->direction() == NET_VCONNECTION_OUT) {
+  if (this->_context->connection_info()->direction() == NET_VCONNECTION_OUT) {
     this->_next_stream_id_bidi = static_cast<uint32_t>(QUICStreamType::CLIENT_BIDI);
     this->_next_stream_id_uni  = static_cast<uint32_t>(QUICStreamType::CLIENT_UNI);
   } else {
@@ -265,12 +265,13 @@ QUICStreamManager::_find_or_create_stream_vc(QUICStreamId stream_id)
 
     uint64_t local_max_stream_data  = 0;
     uint64_t remote_max_stream_data = 0;
+    uint64_t nth_stream             = this->_stream_id_to_nth_stream(stream_id);
 
     switch (QUICTypeUtil::detect_stream_type(stream_id)) {
     case QUICStreamType::CLIENT_BIDI:
-      if (this->_info->direction() == NET_VCONNECTION_OUT) {
+      if (this->_context->connection_info()->direction() == NET_VCONNECTION_OUT) {
         // client
-        if (this->_remote_max_streams_bidi == 0 || stream_id > this->_remote_max_streams_bidi) {
+        if (this->_remote_max_streams_bidi == 0 || nth_stream > this->_remote_max_streams_bidi) {
           return nullptr;
         }
 
@@ -278,7 +279,7 @@ QUICStreamManager::_find_or_create_stream_vc(QUICStreamId stream_id)
         remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE);
       } else {
         // server
-        if (this->_local_max_streams_bidi == 0 || stream_id > this->_local_max_streams_bidi) {
+        if (this->_local_max_streams_bidi == 0 || nth_stream > this->_local_max_streams_bidi) {
           return nullptr;
         }
 
@@ -288,14 +289,14 @@ QUICStreamManager::_find_or_create_stream_vc(QUICStreamId stream_id)
 
       break;
     case QUICStreamType::CLIENT_UNI:
-      if (this->_info->direction() == NET_VCONNECTION_OUT) {
+      if (this->_context->connection_info()->direction() == NET_VCONNECTION_OUT) {
         // client
-        if (this->_remote_max_streams_uni == 0 || stream_id > this->_remote_max_streams_uni) {
+        if (this->_remote_max_streams_uni == 0 || nth_stream > this->_remote_max_streams_uni) {
           return nullptr;
         }
       } else {
         // server
-        if (this->_local_max_streams_uni == 0 || stream_id > this->_local_max_streams_uni) {
+        if (this->_local_max_streams_uni == 0 || nth_stream > this->_local_max_streams_uni) {
           return nullptr;
         }
       }
@@ -305,9 +306,9 @@ QUICStreamManager::_find_or_create_stream_vc(QUICStreamId stream_id)
 
       break;
     case QUICStreamType::SERVER_BIDI:
-      if (this->_info->direction() == NET_VCONNECTION_OUT) {
+      if (this->_context->connection_info()->direction() == NET_VCONNECTION_OUT) {
         // client
-        if (this->_local_max_streams_bidi == 0 || stream_id > this->_local_max_streams_bidi) {
+        if (this->_local_max_streams_bidi == 0 || nth_stream > this->_local_max_streams_bidi) {
           return nullptr;
         }
 
@@ -315,7 +316,7 @@ QUICStreamManager::_find_or_create_stream_vc(QUICStreamId stream_id)
         remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL);
       } else {
         // server
-        if (this->_remote_max_streams_bidi == 0 || stream_id > this->_remote_max_streams_bidi) {
+        if (this->_remote_max_streams_bidi == 0 || nth_stream > this->_remote_max_streams_bidi) {
           return nullptr;
         }
 
@@ -324,12 +325,12 @@ QUICStreamManager::_find_or_create_stream_vc(QUICStreamId stream_id)
       }
       break;
     case QUICStreamType::SERVER_UNI:
-      if (this->_info->direction() == NET_VCONNECTION_OUT) {
-        if (this->_local_max_streams_uni == 0 || stream_id > this->_local_max_streams_uni) {
+      if (this->_context->connection_info()->direction() == NET_VCONNECTION_OUT) {
+        if (this->_local_max_streams_uni == 0 || nth_stream > this->_local_max_streams_uni) {
           return nullptr;
         }
       } else {
-        if (this->_remote_max_streams_uni == 0 || stream_id > this->_remote_max_streams_uni) {
+        if (this->_remote_max_streams_uni == 0 || nth_stream > this->_remote_max_streams_uni) {
           return nullptr;
         }
       }
@@ -416,6 +417,11 @@ QUICStreamManager::will_generate_frame(QUICEncryptionLevel level, size_t current
     return false;
   }
 
+  // Don't send DATA frames if current path is not validated
+  if (!this->_context->path_manager()->get_verified_path().remote_ep().isValid()) {
+    return false;
+  }
+
   for (QUICStreamVConnection *s = this->stream_list.head; s; s = s->link.next) {
     if (s->will_generate_frame(level, current_packet_size, ack_eliciting, seq_num)) {
       return true;
@@ -466,3 +472,9 @@ QUICStreamManager::_is_level_matched(QUICEncryptionLevel level)
 
   return false;
 }
+
+uint64_t
+QUICStreamManager::_stream_id_to_nth_stream(QUICStreamId stream_id)
+{
+  return (stream_id / 4) + 1;
+}
diff --git a/iocore/net/quic/QUICStreamManager.h b/iocore/net/quic/QUICStreamManager.h
index fb38ecc..05da5fc 100644
--- a/iocore/net/quic/QUICStreamManager.h
+++ b/iocore/net/quic/QUICStreamManager.h
@@ -31,13 +31,15 @@
 #include "QUICFrame.h"
 #include "QUICStreamFactory.h"
 #include "QUICLossDetector.h"
+#include "QUICPathManager.h"
+#include "QUICContext.h"
 
 class QUICTransportParameters;
 
 class QUICStreamManager : public QUICFrameHandler, public QUICFrameGenerator
 {
 public:
-  QUICStreamManager(QUICConnectionInfoProvider *info, QUICRTTProvider *rtt_provider, QUICApplicationMap *app_map);
+  QUICStreamManager(QUICContext *context, QUICApplicationMap *app_map);
 
   void init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
                                 const std::shared_ptr<const QUICTransportParameters> &remote_tp);
@@ -82,7 +84,7 @@ private:
 
   QUICStreamFactory _stream_factory;
 
-  QUICConnectionInfoProvider *_info                           = nullptr;
+  QUICContext *_context                                       = nullptr;
   QUICApplicationMap *_app_map                                = nullptr;
   std::shared_ptr<const QUICTransportParameters> _local_tp    = nullptr;
   std::shared_ptr<const QUICTransportParameters> _remote_tp   = nullptr;
@@ -97,4 +99,6 @@ private:
     QUICEncryptionLevel::ZERO_RTT,
     QUICEncryptionLevel::ONE_RTT,
   };
+
+  uint64_t _stream_id_to_nth_stream(QUICStreamId stream_id);
 };
diff --git a/iocore/net/quic/QUICTLS.cc b/iocore/net/quic/QUICTLS.cc
index 5736ef4..b0317f2 100644
--- a/iocore/net/quic/QUICTLS.cc
+++ b/iocore/net/quic/QUICTLS.cc
@@ -32,6 +32,55 @@
 
 constexpr static char tag[] = "quic_tls";
 
+static const char *
+content_type_str(int type)
+{
+  switch (type) {
+  case SSL3_RT_CHANGE_CIPHER_SPEC:
+    return "CHANGE_CIPHER_SPEC";
+  case SSL3_RT_ALERT:
+    return "ALERT";
+  case SSL3_RT_HANDSHAKE:
+    return "HANDSHAKE";
+  case SSL3_RT_APPLICATION_DATA:
+    return "APPLICATION_DATA";
+  case SSL3_RT_HEADER:
+    // The buf contains the record header bytes only
+    return "HEADER";
+  default:
+    return "UNKNOWN";
+  }
+}
+
+static const char *
+hs_type_str(int type)
+{
+  switch (type) {
+  case SSL3_MT_CLIENT_HELLO:
+    return "CLIENT_HELLO";
+  case SSL3_MT_SERVER_HELLO:
+    return "SERVER_HELLO";
+  case SSL3_MT_NEWSESSION_TICKET:
+    return "NEWSESSION_TICKET";
+  case SSL3_MT_END_OF_EARLY_DATA:
+    return "END_OF_EARLY_DATA";
+  case SSL3_MT_ENCRYPTED_EXTENSIONS:
+    return "ENCRYPTED_EXTENSIONS";
+  case SSL3_MT_CERTIFICATE:
+    return "CERTIFICATE";
+  case SSL3_MT_CERTIFICATE_VERIFY:
+    return "CERTIFICATE_VERIFY";
+  case SSL3_MT_FINISHED:
+    return "FINISHED";
+  case SSL3_MT_KEY_UPDATE:
+    return "KEY_UPDATE";
+  case SSL3_MT_MESSAGE_HASH:
+    return "MESSAGE_HASH";
+  default:
+    return "UNKNOWN";
+  }
+}
+
 SSL *
 QUICTLS::ssl_handle()
 {
@@ -51,12 +100,6 @@ QUICTLS::remote_transport_parameters()
 }
 
 void
-QUICTLS::set_local_transport_parameters(std::shared_ptr<const QUICTransportParameters> tp)
-{
-  this->_local_transport_parameters = tp;
-}
-
-void
 QUICTLS::set_remote_transport_parameters(std::shared_ptr<const QUICTransportParameters> tp)
 {
   this->_remote_transport_parameters = tp;
@@ -79,16 +122,30 @@ QUICTLS::~QUICTLS()
   SSL_free(this->_ssl);
 }
 
+int
+QUICTLS::handshake(QUICHandshakeMsgs **out, const QUICHandshakeMsgs *in)
+{
+  if (this->is_handshake_finished()) {
+    if (in != nullptr && in->offsets[4] != 0) {
+      return this->_process_post_handshake_messages(*out, in);
+    }
+
+    return 0;
+  }
+
+  return this->_handshake(out, in);
+}
+
 void
 QUICTLS::reset()
 {
   SSL_clear(this->_ssl);
 }
 
-uint16_t
+uint64_t
 QUICTLS::convert_to_quic_trans_error_code(uint8_t alert)
 {
-  return 0x100 | alert;
+  return static_cast<uint64_t>(QUICTransErrorCode::CRYPTO_ERROR) + alert;
 }
 
 bool
@@ -114,7 +171,9 @@ QUICTLS::initialize_key_materials(QUICConnectionId cid)
   this->_pp_key_info.set_cipher_for_hp_initial(EVP_aes_128_ecb());
 
   // Generate keys
-  Debug(tag, "Generating %s keys", QUICDebugNames::key_phase(QUICKeyPhase::INITIAL));
+  if (is_debug_tag_set(tag)) {
+    Debug(tag, "Generating %s keys with cid %s", QUICDebugNames::key_phase(QUICKeyPhase::INITIAL), cid.hex().c_str());
+  }
 
   uint8_t *client_key_for_hp;
   uint8_t *client_key;
@@ -172,6 +231,127 @@ QUICTLS::initialize_key_materials(QUICConnectionId cid)
   return 1;
 }
 
+void
+QUICTLS::update_negotiated_cipher()
+{
+  this->_store_negotiated_cipher();
+  this->_store_negotiated_cipher_for_hp();
+}
+
+void
+QUICTLS::update_key_materials_for_read(QUICEncryptionLevel level, const uint8_t *secret, size_t secret_len)
+{
+  if (this->_state == HandshakeState::ABORTED) {
+    return;
+  }
+
+  QUICKeyPhase phase;
+  const EVP_CIPHER *cipher;
+
+  switch (level) {
+  case QUICEncryptionLevel::ZERO_RTT:
+    phase  = QUICKeyPhase::ZERO_RTT;
+    cipher = this->_pp_key_info.get_cipher(phase);
+    break;
+  case QUICEncryptionLevel::HANDSHAKE:
+    this->_update_encryption_level(QUICEncryptionLevel::HANDSHAKE);
+    phase = QUICKeyPhase::HANDSHAKE;
+    break;
+  case QUICEncryptionLevel::ONE_RTT:
+    this->_update_encryption_level(QUICEncryptionLevel::ONE_RTT);
+    // TODO Support Key Update
+    phase = QUICKeyPhase::PHASE_0;
+    break;
+  default:
+    phase = QUICKeyPhase::INITIAL;
+    break;
+  }
+
+  QUICHKDF hkdf(this->_get_handshake_digest());
+
+  uint8_t *key_for_hp;
+  uint8_t *key;
+  uint8_t *iv;
+  size_t key_for_hp_len;
+  size_t key_len;
+  size_t *iv_len;
+
+  cipher         = this->_pp_key_info.get_cipher(phase);
+  key_for_hp     = this->_pp_key_info.decryption_key_for_hp(phase);
+  key_for_hp_len = this->_pp_key_info.decryption_key_for_hp_len(phase);
+  key            = this->_pp_key_info.decryption_key(phase);
+  key_len        = this->_pp_key_info.decryption_key_len(phase);
+  iv             = this->_pp_key_info.decryption_iv(phase);
+  iv_len         = this->_pp_key_info.decryption_iv_len(phase);
+
+  if (this->_netvc_context == NET_VCONNECTION_IN) {
+    this->_keygen_for_client.regenerate(key_for_hp, key, iv, iv_len, secret, secret_len, cipher, hkdf);
+    this->_print_km("update - client", key_for_hp, key_for_hp_len, key, key_len, iv, *iv_len, secret, secret_len, phase);
+  } else {
+    this->_keygen_for_server.regenerate(key_for_hp, key, iv, iv_len, secret, secret_len, cipher, hkdf);
+    this->_print_km("update - server", key_for_hp, key_for_hp_len, key, key_len, iv, *iv_len, secret, secret_len, phase);
+  }
+
+  this->_pp_key_info.set_decryption_key_available(phase);
+}
+
+void
+QUICTLS::update_key_materials_for_write(QUICEncryptionLevel level, const uint8_t *secret, size_t secret_len)
+{
+  if (this->_state == HandshakeState::ABORTED) {
+    return;
+  }
+
+  QUICKeyPhase phase;
+  const EVP_CIPHER *cipher = nullptr;
+
+  switch (level) {
+  case QUICEncryptionLevel::ZERO_RTT:
+    phase  = QUICKeyPhase::ZERO_RTT;
+    cipher = this->_pp_key_info.get_cipher(phase);
+    break;
+  case QUICEncryptionLevel::HANDSHAKE:
+    this->_update_encryption_level(QUICEncryptionLevel::HANDSHAKE);
+    phase  = QUICKeyPhase::HANDSHAKE;
+    cipher = this->_pp_key_info.get_cipher(phase);
+    break;
+  case QUICEncryptionLevel::ONE_RTT:
+    this->_update_encryption_level(QUICEncryptionLevel::ONE_RTT);
+    phase  = QUICKeyPhase::PHASE_0;
+    cipher = this->_pp_key_info.get_cipher(phase);
+    break;
+  default:
+    phase = QUICKeyPhase::INITIAL;
+    break;
+  }
+
+  QUICHKDF hkdf(this->_get_handshake_digest());
+
+  uint8_t *key_for_hp;
+  uint8_t *key;
+  uint8_t *iv;
+  size_t key_for_hp_len;
+  size_t key_len;
+  size_t *iv_len;
+
+  key_for_hp     = this->_pp_key_info.encryption_key_for_hp(phase);
+  key_for_hp_len = this->_pp_key_info.encryption_key_for_hp_len(phase);
+  key            = this->_pp_key_info.encryption_key(phase);
+  key_len        = this->_pp_key_info.encryption_key_len(phase);
+  iv             = this->_pp_key_info.encryption_iv(phase);
+  iv_len         = this->_pp_key_info.encryption_iv_len(phase);
+
+  if (this->_netvc_context == NET_VCONNECTION_IN) {
+    this->_keygen_for_server.regenerate(key_for_hp, key, iv, iv_len, secret, secret_len, cipher, hkdf);
+    this->_print_km("update - server", key_for_hp, key_for_hp_len, key, key_len, iv, *iv_len, secret, secret_len, phase);
+  } else {
+    this->_keygen_for_client.regenerate(key_for_hp, key, iv, iv_len, secret, secret_len, cipher, hkdf);
+    this->_print_km("update - client", key_for_hp, key_for_hp_len, key, key_len, iv, *iv_len, secret, secret_len, phase);
+  }
+
+  this->_pp_key_info.set_encryption_key_available(phase);
+}
+
 const char *
 QUICTLS::negotiated_cipher_suite() const
 {
@@ -199,6 +379,118 @@ QUICTLS::abort_handshake()
 }
 
 void
+QUICTLS::set_ready_for_write()
+{
+  this->_should_flush = true;
+}
+
+void
+QUICTLS::on_handshake_data_generated(QUICEncryptionLevel level, const uint8_t *data, size_t len)
+{
+  int index      = static_cast<int>(level);
+  int next_index = index + 1;
+
+  size_t offset            = this->_out.offsets[next_index];
+  size_t next_level_offset = offset + len;
+
+  memcpy(this->_out.buf + offset, data, len);
+
+  for (int i = next_index; i < 5; ++i) {
+    this->_out.offsets[i] = next_level_offset;
+  }
+}
+
+void
+QUICTLS::on_tls_alert(uint8_t alert)
+{
+  this->_has_crypto_error = true;
+  this->_crypto_error     = QUICTLS::convert_to_quic_trans_error_code(alert);
+}
+
+bool
+QUICTLS::has_crypto_error() const
+{
+  return this->_has_crypto_error;
+}
+
+uint64_t
+QUICTLS::crypto_error() const
+{
+  return this->_crypto_error;
+}
+
+int
+QUICTLS::_handshake(QUICHandshakeMsgs **out, const QUICHandshakeMsgs *in)
+{
+  ink_assert(this->_ssl != nullptr);
+  if (this->_state == HandshakeState::ABORTED) {
+    return 0;
+  }
+
+  int err = SSL_ERROR_NONE;
+  ERR_clear_error();
+  int ret = 0;
+
+  SSL_set_msg_callback(this->_ssl, QUICTLS::_msg_cb);
+
+  this->_out.offsets[0] = 0;
+  this->_out.offsets[1] = 0;
+  this->_out.offsets[2] = 0;
+  this->_out.offsets[3] = 0;
+  this->_out.offsets[4] = 0;
+
+  if (in) {
+    this->_pass_quic_data_to_ssl_impl(*in);
+  }
+
+  if (this->_netvc_context == NET_VCONNECTION_IN) {
+    if (!this->_early_data_processed) {
+      if (auto ret = this->_read_early_data(); ret == 0) {
+        this->_early_data_processed = true;
+      } else if (ret < 0) {
+        return 0;
+      } else {
+        // Early data is not arrived yet -- can be multiple initial packets
+      }
+    }
+
+    ret = SSL_accept(this->_ssl);
+  } else {
+    if (!this->_early_data_processed) {
+      if (this->_write_early_data()) {
+        this->_early_data_processed = true;
+      }
+    }
+
+    ret = SSL_connect(this->_ssl);
+  }
+
+  if (ret <= 0) {
+    err = SSL_get_error(this->_ssl, ret);
+
+    switch (err) {
+    case SSL_ERROR_WANT_READ:
+    case SSL_ERROR_WANT_WRITE:
+      break;
+    default:
+      char err_buf[256] = {0};
+      ERR_error_string_n(ERR_get_error(), err_buf, sizeof(err_buf));
+      Debug(tag, "Handshake: %s", err_buf);
+      return ret;
+    }
+  }
+
+  if (this->_should_flush) {
+    this->_should_flush = false;
+    *out                = &this->_out;
+  } else {
+    *out = nullptr;
+  }
+
+  return 1;
+}
+
+void
 QUICTLS::_update_encryption_level(QUICEncryptionLevel level)
 {
   if (this->_current_level < level) {
@@ -210,10 +502,10 @@ QUICTLS::_update_encryption_level(QUICEncryptionLevel level)
 
 void
 QUICTLS::_print_km(const char *header, const uint8_t *key_for_hp, size_t key_for_hp_len, const uint8_t *key, size_t key_len,
-                   const uint8_t *iv, size_t iv_len, const uint8_t *secret, size_t secret_len)
+                   const uint8_t *iv, size_t iv_len, const uint8_t *secret, size_t secret_len, QUICKeyPhase phase)
 {
   if (is_debug_tag_set("vv_quic_crypto")) {
-    Debug("vv_quic_crypto", "%s", header);
+    Debug("vv_quic_crypto", "%s - %s", header, QUICDebugNames::key_phase(phase));
     uint8_t print_buf[128];
     if (secret) {
       QUICDebug::to_hex(print_buf, static_cast<const uint8_t *>(secret), secret_len);
@@ -227,3 +519,12 @@ QUICTLS::_print_km(const char *header, const uint8_t *key_for_hp, size_t key_for
     Debug("vv_quic_crypto", "hp=%s", print_buf);
   }
 }
... 8059 lines suppressed ...