You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by ma...@apache.org on 2022/08/15 23:24:39 UTC

[trafficserver] branch 10-Dev updated: Use quiche as QUIC implementation (#8956)

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

maskit pushed a commit to branch 10-Dev
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/10-Dev by this push:
     new 3e7348451 Use quiche as QUIC implementation (#8956)
3e7348451 is described below

commit 3e73484518d1ac34fef2554f5e3e1156333472e3
Author: Masakazu Kitajo <ma...@apache.org>
AuthorDate: Tue Aug 16 08:24:33 2022 +0900

    Use quiche as QUIC implementation (#8956)
    
    * Initial commit to add support for Quiche
    
    * Fix a bug in sent bytes count
    
    * Improve connection closing
    
    * Update inactivity timer
    
    * Use the appropriate read_vio
    
    * Fix double counting the received bytes
    
    * Change the debug tag for quiche
    
    * Add support for QLog
    
    * Delete a stream at the end of a transaction
    
    * Use SO_REUSEPORT for UDP sockets
    
    * Delay destroying QUICNetVC
    
    * Resolve some compile warnings
    
    * Resolve compile errors
    
    * Update unit tests
    
    * Update one more unit test
    
    * Remove an unused member variable
    
    * Add support for recent quiche (b841ca5)
    
    * Remove redundant comments
---
 build/quiche.m4                                    |  88 +++
 configure.ac                                       |  10 +
 include/ts/apidefs.h.in                            |   4 +-
 include/tscore/ink_inet.h                          |   4 +-
 iocore/net/I_UDPConnection.h                       |   2 +-
 iocore/net/Makefile.am                             |  16 +
 iocore/net/P_QUICNet.h                             |   4 +
 iocore/net/P_QUICNetProcessor.h                    |  62 +-
 ...CNetProcessor.h => P_QUICNetProcessor_native.h} |   0
 ...CNetProcessor.h => P_QUICNetProcessor_quiche.h} |  12 +-
 iocore/net/P_QUICNetVConnection.h                  | 382 +------------
 ...VConnection.h => P_QUICNetVConnection_native.h} |   6 +-
 iocore/net/P_QUICNetVConnection_quiche.h           | 181 ++++++
 iocore/net/P_QUICNextProtocolAccept.h              |  41 +-
 ...lAccept.h => P_QUICNextProtocolAccept_native.h} |   4 +-
 ...lAccept.h => P_QUICNextProtocolAccept_quiche.h} |   6 +-
 iocore/net/P_QUICPacketHandler.h                   | 100 +---
 ...acketHandler.h => P_QUICPacketHandler_native.h} |   0
 ...acketHandler.h => P_QUICPacketHandler_quiche.h} |  56 +-
 iocore/net/QUICNet.cc                              |  27 +-
 iocore/net/QUICNetProcessor.cc                     |   2 +-
 ...CNetProcessor.cc => QUICNetProcessor_quiche.cc} |  56 +-
 iocore/net/QUICNetVConnection.cc                   |   2 +-
 iocore/net/QUICNetVConnection_quiche.cc            | 623 +++++++++++++++++++++
 iocore/net/QUICNextProtocolAccept.cc               |   2 +-
 ...lAccept.cc => QUICNextProtocolAccept_quiche.cc} |   2 +-
 iocore/net/QUICPacketHandler_quiche.cc             | 339 +++++++++++
 iocore/net/UnixUDPConnection.cc                    |   4 +-
 iocore/net/UnixUDPNet.cc                           |  14 +-
 iocore/net/quic/Makefile.am                        |  27 +
 iocore/net/quic/Mock.h                             |   6 +-
 iocore/net/quic/QUICBidirectionalStream.cc         |   2 +-
 iocore/net/quic/QUICBidirectionalStream.h          |   8 +-
 iocore/net/quic/QUICContext.cc                     |  11 +-
 iocore/net/quic/QUICContext.h                      |  12 +-
 iocore/net/quic/QUICCryptoStream.h                 |   4 +-
 iocore/net/quic/QUICGlobals.cc                     |   3 +
 iocore/net/quic/QUICHandshake.cc                   |  50 +-
 iocore/net/quic/QUICKeyGenerator.cc                |  14 +-
 iocore/net/quic/QUICRetryIntegrityTag.cc           |   8 +-
 iocore/net/quic/QUICRetryIntegrityTag.h            |  19 +-
 iocore/net/quic/QUICStream.cc                      | 156 +-----
 iocore/net/quic/QUICStream.h                       |  56 +-
 .../net/quic/{QUICStream.cc => QUICStreamBase.cc}  |  80 +--
 iocore/net/quic/QUICStreamFactory.cc               |   4 +-
 iocore/net/quic/QUICStreamFactory.h                |   2 +-
 iocore/net/quic/QUICStreamManager.cc               | 477 +---------------
 iocore/net/quic/QUICStreamManager.h                |  86 +--
 ...treamManager.cc => QUICStreamManager_native.cc} | 326 +++++------
 ...CStreamManager.h => QUICStreamManager_native.h} |  84 ++-
 iocore/net/quic/QUICStreamManager_quiche.cc        | 126 +++++
 iocore/net/quic/QUICStreamManager_quiche.h         |  57 ++
 iocore/net/quic/QUICStreamVCAdapter.cc             |   2 +-
 iocore/net/quic/QUICStream_native.h                |  70 +++
 iocore/net/quic/QUICStream_quiche.cc               |  94 ++++
 .../{QUICStreamFactory.h => QUICStream_quiche.h}   |  31 +-
 iocore/net/quic/QUICTransportParameters.cc         |  34 +-
 iocore/net/quic/QUICTypes.h                        |   2 +-
 iocore/net/quic/QUICUnidirectionalStream.cc        |   4 +-
 iocore/net/quic/QUICUnidirectionalStream.h         |   5 +-
 iocore/net/quic/test/test_QUICKeyGenerator.cc      |  23 +-
 iocore/net/quic/test/test_QUICPacketFactory.cc     |   2 +-
 iocore/net/quic/test/test_QUICStreamManager.cc     |  11 +-
 lib/records/RecHttp.cc                             |  26 +-
 proxy/http/HttpProxyServerMain.cc                  |   8 +-
 proxy/http3/Http3App.cc                            |  32 +-
 proxy/http3/Http3App.h                             |   2 +
 proxy/http3/Http3Config.cc                         |  30 +-
 proxy/http3/Http3Config.h                          |   4 +
 proxy/http3/Http3Frame.cc                          |   2 +-
 proxy/http3/Http3Session.cc                        |   8 +
 proxy/http3/Http3Session.h                         |   3 +-
 proxy/http3/Http3SessionAccept.cc                  |   4 +-
 proxy/http3/Http3Transaction.cc                    |  13 +-
 proxy/http3/Http3Transaction.h                     |   2 +
 proxy/http3/test/main.cc                           |   2 +-
 src/traffic_quic/Makefile.inc                      |   4 +
 src/traffic_quic/quic_client.cc                    |   1 +
 src/traffic_quic/traffic_quic.cc                   |   2 +-
 src/traffic_server/Makefile.inc                    |   4 +
 src/traffic_server/traffic_server.cc               |   2 +-
 src/tscore/ink_inet.cc                             |   8 +-
 82 files changed, 2293 insertions(+), 1809 deletions(-)

diff --git a/build/quiche.m4 b/build/quiche.m4
new file mode 100644
index 000000000..3a9c7336c
--- /dev/null
+++ b/build/quiche.m4
@@ -0,0 +1,88 @@
+dnl -------------------------------------------------------- -*- autoconf -*-
+dnl Licensed to the Apache Software Foundation (ASF) under one or more
+dnl contributor license agreements.  See the NOTICE file distributed with
+dnl this work for additional information regarding copyright ownership.
+dnl The ASF licenses this file to You under the Apache License, Version 2.0
+dnl (the "License"); you may not use this file except in compliance with
+dnl the License.  You may obtain a copy of the License at
+dnl
+dnl     http://www.apache.org/licenses/LICENSE-2.0
+dnl
+dnl Unless required by applicable law or agreed to in writing, software
+dnl distributed under the License is distributed on an "AS IS" BASIS,
+dnl WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+dnl See the License for the specific language governing permissions and
+dnl limitations under the License.
+
+dnl
+dnl quiche.m4: Trafficserver's quiche autoconf macros
+dnl
+
+dnl
+dnl TS_CHECK_QUICHE: look for quiche libraries and headers
+dnl
+AC_DEFUN([TS_CHECK_QUICHE], [
+has_quiche=0
+AC_ARG_WITH(quiche, [AS_HELP_STRING([--with-quiche=DIR],[use a specific quiche library])],
+[
+  if test "x$withval" != "xyes" && test "x$withval" != "x"; then
+    quiche_base_dir="$withval"
+    if test "$withval" != "no"; then
+      has_quiche=1
+      case "$withval" in
+      *":"*)
+        quiche_include="`echo $withval | sed -e 's/:.*$//'`"
+        quiche_ldflags="`echo $withval | sed -e 's/^.*://'`"
+        AC_MSG_CHECKING(checking for quiche includes in $quiche_include libs in $quiche_ldflags )
+        ;;
+      *)
+        quiche_include="$withval/include"
+        quiche_ldflags="$withval/lib"
+        AC_MSG_CHECKING(checking for quiche includes in $withval)
+        ;;
+      esac
+    fi
+  fi
+
+  if test -d $quiche_include && test -d $quiche_ldflags && test -f $quiche_include/quiche.h; then
+    AC_MSG_RESULT([ok])
+  else
+    AC_MSG_RESULT([not found])
+  fi
+
+if test "$has_quiche" != "0"; then
+  saved_ldflags=$LDFLAGS
+  saved_cppflags=$CPPFLAGS
+  saved_libs=$LIBS
+  quiche_have_headers=0
+  quiche_have_libs=0
+  if test "$quiche_base_dir" != "/usr"; then
+    TS_ADDTO(CPPFLAGS, [-I${quiche_include}])
+    TS_ADDTO(LDFLAGS, [-L${quiche_ldflags}])
+    TS_ADDTO(LIBS, [-lquiche])
+    TS_ADDTO_RPATH(${quiche_ldflags})
+  fi
+
+  AC_CHECK_LIB([quiche], quiche_connect, [quiche_have_libs=1])
+  if test "$quiche_have_libs" != "0"; then
+    AC_CHECK_HEADERS(quiche.h, [quiche_have_headers=1])
+  fi
+  if test "$quiche_have_headers" != "0"; then
+    AC_SUBST([QUICHE_LIB], [-lquiche])
+    AC_SUBST([QUICHE_CFLAGS], [-I${quiche_include}])
+    AC_CHECK_FUNCS([quiche_config_set_active_connection_id_limit])
+  else
+    has_quiche=0
+    CPPFLAGS=$saved_cppflags
+    LDFLAGS=$saved_ldflags
+    LIBS=$saved_libs
+  fi
+fi
+],
+[
+AC_CHECK_HEADER([quiche.h], [], [has_quiche=0])
+AC_CHECK_LIB([quiche], quiche_connect, [:], [has_quiche=0])
+])
+
+AM_CONDITIONAL([USE_QUICHE], [test $has_quiche -eq 1])
+])
diff --git a/configure.ac b/configure.ac
index acecbf0eb..234cd2ae1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1436,6 +1436,16 @@ TS_CHECK_BROTLI
 # Check for optional luajit library
 TS_CHECK_LUAJIT
 
+# Check for optional quiche library
+TS_CHECK_QUICHE
+if test "${has_quiche}" = "1"; then
+enable_quic=yes
+## Doing these again for Quiche
+AM_CONDITIONAL([ENABLE_QUIC], [test "x$enable_quic" = "xyes"])
+TS_ARG_ENABLE_VAR([use], [quic])
+AC_SUBST(use_quic)
+fi
+
 #
 # Enable experimental/uri_signing plugin
 # This is here, instead of above, because it needs to know if PCRE is available.
diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in
index 66224b975..12904e066 100644
--- a/include/ts/apidefs.h.in
+++ b/include/ts/apidefs.h.in
@@ -1389,9 +1389,9 @@ extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_1_0;
 extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_1_1;
 extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_2_0;
 extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_3;
-extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_3_D27;
+extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_3_D29;
 extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_QUIC;
-extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_QUIC_D27;
+extern tsapi const char *const TS_ALPN_PROTOCOL_HTTP_QUIC_D29;
 
 extern tsapi int TS_ALPN_PROTOCOL_INDEX_HTTP_0_9;
 extern tsapi int TS_ALPN_PROTOCOL_INDEX_HTTP_1_0;
diff --git a/include/tscore/ink_inet.h b/include/tscore/ink_inet.h
index 332fa03ed..1c7010751 100644
--- a/include/tscore/ink_inet.h
+++ b/include/tscore/ink_inet.h
@@ -72,8 +72,8 @@ extern const std::string_view IP_PROTO_TAG_HTTP_1_1;
 extern const std::string_view IP_PROTO_TAG_HTTP_2_0;
 extern const std::string_view IP_PROTO_TAG_HTTP_QUIC;
 extern const std::string_view IP_PROTO_TAG_HTTP_3;
-extern const std::string_view IP_PROTO_TAG_HTTP_QUIC_D27;
-extern const std::string_view IP_PROTO_TAG_HTTP_3_D27;
+extern const std::string_view IP_PROTO_TAG_HTTP_QUIC_D29;
+extern const std::string_view IP_PROTO_TAG_HTTP_3_D29;
 
 struct IpAddr; // forward declare.
 
diff --git a/iocore/net/I_UDPConnection.h b/iocore/net/I_UDPConnection.h
index bc07a8328..a56f5cd5d 100644
--- a/iocore/net/I_UDPConnection.h
+++ b/iocore/net/I_UDPConnection.h
@@ -97,7 +97,7 @@ public:
      bindToThread() automatically so that the sockets can be passed to
      other Continuations.
   */
-  void bindToThread(Continuation *c);
+  void bindToThread(Continuation *c, EThread *t);
 
   virtual void UDPConnection_is_abstract() = 0;
 };
diff --git a/iocore/net/Makefile.am b/iocore/net/Makefile.am
index 313285c21..4ad7a3762 100644
--- a/iocore/net/Makefile.am
+++ b/iocore/net/Makefile.am
@@ -208,6 +208,21 @@ libinknet_a_SOURCES = \
 	SSLDynlock.cc
 
 if ENABLE_QUIC
+if USE_QUICHE
+libinknet_a_SOURCES += \
+  P_QUICClosedConCollector.h \
+  P_QUICPacketHandler.h \
+  P_QUICNetProcessor.h \
+  P_QUICNetVConnection.h \
+  P_QUICNextProtocolAccept.h \
+  QUICClosedConCollector.cc \
+  QUICMultiCertConfigLoader.cc \
+  QUICNet.cc \
+  QUICNetProcessor_quiche.cc \
+  QUICNetVConnection_quiche.cc \
+  QUICNextProtocolAccept_quiche.cc \
+  QUICPacketHandler_quiche.cc
+else
 libinknet_a_SOURCES += \
   P_QUICClosedConCollector.h \
   P_QUICPacketHandler.h \
@@ -223,6 +238,7 @@ libinknet_a_SOURCES += \
   QUICNetVConnection.cc \
   QUICNextProtocolAccept.cc
 endif
+endif
 
 if BUILD_TESTS
 libinknet_a_SOURCES += \
diff --git a/iocore/net/P_QUICNet.h b/iocore/net/P_QUICNet.h
index 09468bc03..4e452bb70 100644
--- a/iocore/net/P_QUICNet.h
+++ b/iocore/net/P_QUICNet.h
@@ -65,8 +65,12 @@ private:
   Que(UDPPacketInternal, link) _longInQueue;
 
 private:
+#if HAVE_QUICHE_H
+  void _process_packet(QUICPollEvent *e, NetHandler *nh);
+#else
   void _process_short_header_packet(QUICPollEvent *e, NetHandler *nh);
   void _process_long_header_packet(QUICPollEvent *e, NetHandler *nh);
+#endif
 };
 
 static inline QUICPollCont *
diff --git a/iocore/net/P_QUICNetProcessor.h b/iocore/net/P_QUICNetProcessor.h
index 68e722814..9a4d6c977 100644
--- a/iocore/net/P_QUICNetProcessor.h
+++ b/iocore/net/P_QUICNetProcessor.h
@@ -18,63 +18,15 @@
   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.
  */
 
-/****************************************************************************
-
-  P_QUICNetProcessor.h
-
-  The QUIC version of the UnixNetProcessor class.  The majority of the logic
-  is in UnixNetProcessor.  The QUICNetProcessor provides the following:
-
-  * QUIC library initialization through the start() method.
-  * Allocation of a QUICNetVConnection through the allocate_vc virtual method.
-
-  Possibly another pass through could simplify the allocate_vc logic too, but
-  I think I will stop here for now.
-
- ****************************************************************************/
 #pragma once
 
-#include "tscore/ink_platform.h"
-#include "P_Net.h"
-#include "quic/QUICConnectionTable.h"
-
-class UnixNetVConnection;
-class QUICResetTokenTable;
-struct NetAccept;
-
-//////////////////////////////////////////////////////////////////
-//
-//  class QUICNetProcessor
-//
-//////////////////////////////////////////////////////////////////
-class QUICNetProcessor : public UnixNetProcessor
-{
-public:
-  QUICNetProcessor();
-  virtual ~QUICNetProcessor();
-
-  void init() override;
-  virtual int start(int, size_t stacksize) override;
-  // TODO: refactoring NetProcessor::connect_re and UnixNetProcessor::connect_re_internal
-  // Action *connect_re(Continuation *cont, sockaddr const *addr, NetVCOptions *opts) override;
-  Action *connect_re(Continuation *cont, sockaddr const *addr, NetVCOptions *opts);
-
-  virtual NetAccept *createNetAccept(const NetProcessor::AcceptOptions &opt) override;
-  virtual NetVConnection *allocate_vc(EThread *t) override;
-
-  Action *main_accept(Continuation *cont, SOCKET fd, AcceptOptions const &opt) override;
-
-  off_t quicPollCont_offset;
-
-private:
-  QUICNetProcessor(const QUICNetProcessor &);
-  QUICNetProcessor &operator=(const QUICNetProcessor &);
-
-  QUICConnectionTable *_ctable = nullptr;
-  QUICResetTokenTable *_rtable = nullptr;
-};
+#include "tscore/ink_config.h"
 
-extern QUICNetProcessor quic_NetProcessor;
+#if HAVE_QUICHE_H
+#include "P_QUICNetProcessor_quiche.h"
+#else
+#include "P_QUICNetProcessor_native.h"
+#endif
diff --git a/iocore/net/P_QUICNetProcessor.h b/iocore/net/P_QUICNetProcessor_native.h
similarity index 100%
copy from iocore/net/P_QUICNetProcessor.h
copy to iocore/net/P_QUICNetProcessor_native.h
diff --git a/iocore/net/P_QUICNetProcessor.h b/iocore/net/P_QUICNetProcessor_quiche.h
similarity index 89%
copy from iocore/net/P_QUICNetProcessor.h
copy to iocore/net/P_QUICNetProcessor_quiche.h
index 68e722814..2da41c2ba 100644
--- a/iocore/net/P_QUICNetProcessor.h
+++ b/iocore/net/P_QUICNetProcessor_quiche.h
@@ -23,10 +23,10 @@
 
 /****************************************************************************
 
-  P_QUICNetProcessor.h
+  QUICNetProcessor.h
 
-  The QUIC version of the UnixNetProcessor class.  The majority of the logic
-  is in UnixNetProcessor.  The QUICNetProcessor provides the following:
+  The Quiche version of the UnixNetProcessor class.  The majority of the logic
+  is in UnixNetProcessor.  QUICNetProcessor provides the following:
 
   * QUIC library initialization through the start() method.
   * Allocation of a QUICNetVConnection through the allocate_vc virtual method.
@@ -40,9 +40,9 @@
 #include "tscore/ink_platform.h"
 #include "P_Net.h"
 #include "quic/QUICConnectionTable.h"
+#include <quiche.h>
 
 class UnixNetVConnection;
-class QUICResetTokenTable;
 struct NetAccept;
 
 //////////////////////////////////////////////////////////////////
@@ -73,8 +73,8 @@ private:
   QUICNetProcessor(const QUICNetProcessor &);
   QUICNetProcessor &operator=(const QUICNetProcessor &);
 
-  QUICConnectionTable *_ctable = nullptr;
-  QUICResetTokenTable *_rtable = nullptr;
+  QUICConnectionTable *_ctable  = nullptr;
+  quiche_config *_quiche_config = nullptr;
 };
 
 extern QUICNetProcessor quic_NetProcessor;
diff --git a/iocore/net/P_QUICNetVConnection.h b/iocore/net/P_QUICNetVConnection.h
index c01d775cd..53922bd3e 100644
--- a/iocore/net/P_QUICNetVConnection.h
+++ b/iocore/net/P_QUICNetVConnection.h
@@ -21,382 +21,12 @@
   limitations under the License.
  */
 
-/****************************************************************************
-
-  QUICNetVConnection.h
-
-  This file implements an I/O Processor for network I/O.
-
-
- ****************************************************************************/
 #pragma once
 
-#include "tscore/ink_platform.h"
-#include "P_Net.h"
-#include "P_EventSystem.h"
-#include "P_UnixNetVConnection.h"
-#include "P_UnixNet.h"
-#include "P_UDPNet.h"
-#include "P_ALPNSupport.h"
-#include "TLSBasicSupport.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/QUICApplication.h"
-#include "quic/QUICStream.h"
-#include "quic/QUICHandshakeProtocol.h"
-#include "quic/QUICAckFrameCreator.h"
-#include "quic/QUICPinger.h"
-#include "quic/QUICPadder.h"
-#include "quic/QUICLossDetector.h"
-#include "quic/QUICStreamManager.h"
-#include "quic/QUICAltConnectionManager.h"
-#include "quic/QUICPathValidator.h"
-#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;
-
-//////////////////////////////////////////////////////////////////
-//
-//  class NetVConnection
-//
-//  A VConnection for a network socket.
-//
-//////////////////////////////////////////////////////////////////
-
-class QUICPacketHandler;
-class QUICLossDetector;
-class QUICHandshake;
-
-class SSLNextProtocolSet;
-
-/**
- * @class QUICNetVConnection
- * @brief A NetVConnection for a QUIC network socket
- * @detail
- *
- * state_pre_handshake()
- *  | READ:
- *  |   Do nothing
- *  | WRITE:
- *  |   _state_common_send_packet()
- *  v
- * state_handshake()
- *  | READ:
- *  |   _state_handshake_process_packet()
- *  |   _state_handshake_process_initial_packet()
- *  |   _state_handshake_process_retry_packet()
- *  |   _state_handshake_process_handshake_packet()
- *  |   _state_handshake_process_zero_rtt_protected_packet()
- *  | WRITE:
- *  |   _state_common_send_packet()
- *  |   or
- *  |   _state_handshake_send_retry_packet()
- *  v
- * state_connection_established()
- *  | READ:
- *  |   _state_connection_established_receive_packet()
- *  |   _state_connection_established_process_protected_packet()
- *  | WRITE:
- *  |   _state_common_send_packet()
- *  v
- * state_connection_closing() (If closing actively)
- *  | READ:
- *  |   _state_closing_receive_packet()
- *  | WRITE:
- *  |   _state_closing_send_packet()
- *  v
- * state_connection_draining() (If closing passively)
- *  | READ:
- *  |   _state_draining_receive_packet()
- *  | WRITE:
- *  |   Do nothing
- *  v
- * state_connection_close()
- *    READ:
- *      Do nothing
- *    WRITE:
- *      Do nothing
- **/
-class QUICNetVConnection : public UnixNetVConnection,
-                           public QUICConnection,
-                           public RefCountObj,
-                           public ALPNSupport,
-                           public TLSBasicSupport,
-                           public TLSSessionResumptionSupport
-{
-  using super = UnixNetVConnection; ///< Parent type.
-
-public:
-  QUICNetVConnection();
-  ~QUICNetVConnection();
-  void init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, UDPConnection *, QUICPacketHandler *,
-            QUICResetTokenTable *rtable);
-  void init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, QUICConnectionId first_cid,
-            QUICConnectionId retry_cid, UDPConnection *, QUICPacketHandler *, QUICResetTokenTable *rtable,
-            QUICConnectionTable *ctable);
-
-  // accept new conn_id
-  int acceptEvent(int event, Event *e);
-
-  // NetVConnection
-  void set_local_addr() override;
-
-  // UnixNetVConnection
-  void reenable(VIO *vio) override;
-  VIO *do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) override;
-  VIO *do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner = false) override;
-  int connectUp(EThread *t, int fd) override;
-
-  // QUICNetVConnection
-  int startEvent(int event, Event *e);
-  int state_pre_handshake(int event, Event *data);
-  int state_handshake(int event, Event *data);
-  int state_connection_established(int event, Event *data);
-  int state_connection_closing(int event, Event *data);
-  int state_connection_draining(int event, Event *data);
-  int state_connection_closed(int event, Event *data);
-  void start();
-  void remove_connection_ids();
-  void free(EThread *t) override;
-  void free() override;
-  void destroy(EThread *t);
-
-  UDPConnection *get_udp_con();
-  virtual void net_read_io(NetHandler *nh, EThread *lthread) override;
-  virtual int64_t load_buffer_and_write(int64_t towrite, MIOBufferAccessor &buf, int64_t &total_written, int &needs) override;
-
-  int populate_protocol(std::string_view *results, int n) const override;
-  const char *protocol_contains(std::string_view tag) const override;
-
-  // QUICConnection
-  QUICStreamManager *stream_manager() override;
-  void close_quic_connection(QUICConnectionErrorUPtr error) override;
-  void reset_quic_connection() override;
-  void handle_received_packet(UDPPacket *packet) override;
-  void ping() override;
-
-  // QUICConnection (QUICConnectionInfoProvider)
-  QUICConnectionId peer_connection_id() const override;
-  QUICConnectionId original_connection_id() const override;
-  QUICConnectionId first_connection_id() const override;
-  QUICConnectionId retry_source_connection_id() const override;
-  QUICConnectionId initial_source_connection_id() const override;
-  QUICConnectionId connection_id() const override;
-  std::string_view cids() const override;
-  const QUICFiveTuple five_tuple() const override;
-  uint32_t pmtu() const override;
-  NetVConnectionContext_t direction() const override;
-  QUICVersion negotiated_version() const override;
-  std::string_view negotiated_application_name() const override;
-  bool is_closed() const override;
-  bool is_at_anti_amplification_limit() const override;
-  bool is_address_validation_completed() const override;
-  bool is_handshake_completed() const override;
-  bool has_keys_for(QUICPacketNumberSpace space) const override;
-
-  // QUICConnection (QUICFrameHandler)
-  std::vector<QUICFrameType> interests() override;
-  QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override;
-
-  int in_closed_queue = 0;
-
-  bool shouldDestroy();
-
-  LINK(QUICNetVConnection, closed_link);
-  SLINK(QUICNetVConnection, closed_alink);
-
-protected:
-  // TLSBasicSupport
-  SSL *_get_ssl_object() const override;
-  ssl_curve_id _get_tls_curve() const override;
-
-  // TLSSessionResumptionSupport
-  const IpEndpoint &_getLocalEndpoint() override;
-
-private:
-  std::random_device _rnd;
-
-  QUICConfig::scoped_config _quic_config;
-
-  QUICConnectionId _peer_quic_connection_id;      // dst cid in local
-  QUICConnectionId _peer_old_quic_connection_id;  // dst previous cid in local
-  QUICConnectionId _original_quic_connection_id;  // dst cid of initial packet from client
-  QUICConnectionId _first_quic_connection_id;     // dst cid of initial packet from client that doesn't have retry token
-  QUICConnectionId _retry_source_connection_id;   // src cid used for sending Retry packet
-  QUICConnectionId _initial_source_connection_id; // src cid used for Initial packet
-  QUICConnectionId _quic_connection_id;           // src cid in local
-  QUICFiveTuple _five_tuple;
-  bool _connection_migration_initiated = false;
-
-  char _cids_data[MAX_CIDS_SIZE] = {0};
-  std::string_view _cids;
-
-  QUICVersion _initial_version;
-  UDPConnection *_udp_con = nullptr;
-  QUICPacketProtectionKeyInfo _pp_key_info;
-  QUICPacketHandler *_packet_handler = nullptr;
-  QUICPacketFactory _packet_factory;
-  QUICFrameFactory _frame_factory;
-  QUICAckFrameManager _ack_frame_manager;
-  QUICPacketHeaderProtector _ph_protector;
-  QUICRTTMeasure _rtt_measure;
-  QUICApplicationMap *_application_map = nullptr;
-
-  uint32_t _pmtu = 1280;
-
-  // TODO: use custom allocator and make them std::unique_ptr or std::shared_ptr
-  // or make them just member variables.
-  QUICPinger *_pinger                               = nullptr;
-  QUICPadder *_padder                               = nullptr;
-  QUICHandshake *_handshake_handler                 = nullptr;
-  QUICHandshakeProtocol *_hs_protocol               = nullptr;
-  QUICLossDetector *_loss_detector                  = nullptr;
-  QUICFrameDispatcher *_frame_dispatcher            = nullptr;
-  QUICStreamManager *_stream_manager                = nullptr;
-  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;
-  QUICPathManager *_path_manager                    = nullptr;
-  QUICTokenCreator *_token_creator                  = nullptr;
-
-  QUICFrameGeneratorManager _frame_generators;
-
-  QUICPacketReceiveQueue _packet_recv_queue = {this->_packet_factory, this->_ph_protector};
-
-  QUICConnectionErrorUPtr _connection_error  = nullptr;
-  uint32_t _state_closing_recv_packet_count  = 0;
-  uint32_t _state_closing_recv_packet_window = 1;
-  uint64_t _flow_control_buffer_size         = 1024;
-
-  void _init_submodules();
-
-  void _schedule_packet_write_ready(bool delay = false);
-  void _unschedule_packet_write_ready();
-  void _close_packet_write_ready(Event *data);
-  Event *_packet_write_ready = nullptr;
-
-  void _schedule_closing_timeout(ink_hrtime interval);
-  void _unschedule_closing_timeout();
-  void _close_closing_timeout(Event *data);
-  Event *_closing_timeout = nullptr;
-
-  void _schedule_closed_event();
-  void _unschedule_closed_event();
-  void _close_closed_event(Event *data);
-  Event *_closed_event = nullptr;
-
-  void _schedule_ack_manager_periodic(ink_hrtime interval);
-  void _unschedule_ack_manager_periodic();
-  Event *_ack_manager_periodic = nullptr;
-
-  QUICEncryptionLevel _minimum_encryption_level = QUICEncryptionLevel::INITIAL;
-
-  QUICPacketNumber _largest_acked_packet_number(QUICEncryptionLevel level) const;
-  uint32_t _maximum_quic_packet_size() const;
-  uint32_t _minimum_quic_packet_size();
-  uint64_t _maximum_stream_frame_data_size();
-
-  Ptr<IOBufferBlock> _store_frame(Ptr<IOBufferBlock> parent_block, size_t &size_added, uint64_t &max_frame_size, QUICFrame &frame,
-                                  std::vector<QUICSentPacketInfo::FrameInfo> &frames);
-  QUICPacketUPtr _packetize_frames(uint8_t *packet_buf, QUICEncryptionLevel level, uint64_t max_packet_size,
-                                   std::vector<QUICSentPacketInfo::FrameInfo> &frames);
-  void _packetize_closing_frame();
-  QUICPacketUPtr _build_packet(uint8_t *packet_buf, QUICEncryptionLevel level, const Ptr<IOBufferBlock> &parent_block,
-                               bool retransmittable, bool probing, bool crypto);
-
-  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 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 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();
-  QUICConnectionErrorUPtr _state_common_send_packet();
-  QUICConnectionErrorUPtr _state_handshake_send_retry_packet();
-  QUICConnectionErrorUPtr _state_closing_send_packet();
-
-  Ptr<ProxyMutex> _packet_transmitter_mutex;
-
-  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(uint8_t *packet_buf, QUICPacketCreationResult &result);
-  void _validate_new_path(const QUICPath &path);
-
-  bool _handshake_completed = false;
-  int _complete_handshake_if_possible();
-
-  void _switch_to_handshake_state();
-  void _switch_to_established_state();
-  void _switch_to_closing_state(QUICConnectionErrorUPtr error);
-  void _switch_to_draining_state(QUICConnectionErrorUPtr error);
-  void _switch_to_close_state();
-
-  bool _application_started = false;
-  void _start_application();
-
-  void _handle_periodic_ack_event();
-  void _handle_idle_timeout();
-  void _handle_active_timeout();
-
-  QUICConnectionErrorUPtr _handle_frame(const QUICNewConnectionIdFrame &frame);
-
-  void _update_cids();
-  void _update_peer_cid(const QUICConnectionId &new_cid);
-  void _update_local_cid(const QUICConnectionId &new_cid);
-  void _rerandomize_original_cid();
-
-  QUICHandshakeProtocol *_setup_handshake_protocol(const 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};
-  size_t _av_token_len     = 0;
-
-  uint64_t _stream_frames_sent = 0;
-  uint32_t _seq_num            = 0;
-
-  // TODO: Source addresses verification through an address validation token
-  QUICAddrVerifyState _verified_state;
-
-  std::unique_ptr<QUICContext> _context;
-
-  std::shared_ptr<QLog::QLogListener> _qlog;
-};
-
-typedef int (QUICNetVConnection::*QUICNetVConnHandler)(int, void *);
+#include "tscore/ink_config.h"
 
-extern ClassAllocator<QUICNetVConnection> quicNetVCAllocator;
+#if HAVE_QUICHE_H
+#include "P_QUICNetVConnection_quiche.h"
+#else
+#include "P_QUICNetVConnection_native.h"
+#endif
diff --git a/iocore/net/P_QUICNetVConnection.h b/iocore/net/P_QUICNetVConnection_native.h
similarity index 99%
copy from iocore/net/P_QUICNetVConnection.h
copy to iocore/net/P_QUICNetVConnection_native.h
index c01d775cd..763b5b070 100644
--- a/iocore/net/P_QUICNetVConnection.h
+++ b/iocore/net/P_QUICNetVConnection_native.h
@@ -59,7 +59,7 @@
 #include "quic/QUICPinger.h"
 #include "quic/QUICPadder.h"
 #include "quic/QUICLossDetector.h"
-#include "quic/QUICStreamManager.h"
+#include "quic/QUICStreamManager_native.h"
 #include "quic/QUICAltConnectionManager.h"
 #include "quic/QUICPathValidator.h"
 #include "quic/QUICPathManager.h"
@@ -272,7 +272,7 @@ private:
   QUICHandshakeProtocol *_hs_protocol               = nullptr;
   QUICLossDetector *_loss_detector                  = nullptr;
   QUICFrameDispatcher *_frame_dispatcher            = nullptr;
-  QUICStreamManager *_stream_manager                = nullptr;
+  QUICStreamManagerImpl *_stream_manager            = nullptr;
   QUICCongestionController *_congestion_controller  = nullptr;
   QUICRemoteFlowController *_remote_flow_controller = nullptr;
   QUICLocalFlowController *_local_flow_controller   = nullptr;
@@ -346,8 +346,6 @@ private:
   QUICConnectionErrorUPtr _state_handshake_send_retry_packet();
   QUICConnectionErrorUPtr _state_closing_send_packet();
 
-  Ptr<ProxyMutex> _packet_transmitter_mutex;
-
   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);
diff --git a/iocore/net/P_QUICNetVConnection_quiche.h b/iocore/net/P_QUICNetVConnection_quiche.h
new file mode 100644
index 000000000..400d363a9
--- /dev/null
+++ b/iocore/net/P_QUICNetVConnection_quiche.h
@@ -0,0 +1,181 @@
+/** @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.
+ */
+
+/****************************************************************************
+
+  QUICNetVConnection_quiche.h
+
+  This file implements an I/O Processor for network I/O.
+
+
+ ****************************************************************************/
+#pragma once
+
+#include "tscore/ink_platform.h"
+#include "P_Net.h"
+#include "P_EventSystem.h"
+#include "P_UnixNetVConnection.h"
+#include "P_UnixNet.h"
+#include "P_UDPNet.h"
+#include "P_ALPNSupport.h"
+#include "TLSBasicSupport.h"
+#include "tscore/ink_apidefs.h"
+#include "tscore/List.h"
+
+#include "quic/QUICConnection.h"
+#include "quic/QUICConnectionTable.h"
+#include "quic/QUICContext.h"
+#include "quic/QUICStreamManager.h"
+#include "quic/QUICStreamManager_quiche.h"
+#include <quiche.h>
+
+class QUICPacketHandler;
+class QUICResetTokenTable;
+class QUICConnectionTable;
+
+class QUICNetVConnection : public UnixNetVConnection,
+                           public QUICConnection,
+                           public RefCountObj,
+                           public ALPNSupport,
+                           public TLSBasicSupport
+{
+  using super = UnixNetVConnection; ///< Parent type.
+
+public:
+  QUICNetVConnection();
+  ~QUICNetVConnection();
+  void init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, UDPConnection *, QUICPacketHandler *);
+  void init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, QUICConnectionId first_cid,
+            QUICConnectionId retry_cid, UDPConnection *, quiche_conn *, QUICPacketHandler *, QUICConnectionTable *ctable);
+
+  // Event handlers
+  int acceptEvent(int event, Event *e);
+  int state_handshake(int event, Event *e);
+  int state_established(int event, Event *e);
+
+  // RefCountObj
+  void free() override;
+
+  // NetVConnection
+  void set_local_addr() override;
+
+  // NetEvent
+  void free(EThread *t) override;
+
+  // UnixNetVConnection
+  void reenable(VIO *vio) override;
+  VIO *do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) override;
+  VIO *do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner = false) override;
+  int connectUp(EThread *t, int fd) override;
+  int64_t load_buffer_and_write(int64_t towrite, MIOBufferAccessor &buf, int64_t &total_written, int &needs) override;
+
+  // NetEvent
+  virtual void net_read_io(NetHandler *nh, EThread *lthread) override;
+
+  // NetVConnection
+  int populate_protocol(std::string_view *results, int n) const override;
+  const char *protocol_contains(std::string_view tag) const override;
+
+  // QUICConnection
+  QUICStreamManager *stream_manager() override;
+  void close_quic_connection(QUICConnectionErrorUPtr error) override;
+  void reset_quic_connection() override;
+  void handle_received_packet(UDPPacket *packet) override;
+  void ping() override;
+
+  // QUICConnection (QUICConnectionInfoProvider)
+  QUICConnectionId peer_connection_id() const override;
+  QUICConnectionId original_connection_id() const override;
+  QUICConnectionId first_connection_id() const override;
+  QUICConnectionId retry_source_connection_id() const override;
+  QUICConnectionId initial_source_connection_id() const override;
+  QUICConnectionId connection_id() const override;
+  std::string_view cids() const override;
+  const QUICFiveTuple five_tuple() const override;
+  uint32_t pmtu() const override;
+  NetVConnectionContext_t direction() const override;
+  QUICVersion negotiated_version() const override;
+  std::string_view negotiated_application_name() const override;
+  bool is_closed() const override;
+  bool is_at_anti_amplification_limit() const override;
+  bool is_address_validation_completed() const override;
+  bool is_handshake_completed() const override;
+  bool has_keys_for(QUICPacketNumberSpace space) const override;
+
+  // QUICConnection (QUICFrameHandler)
+  std::vector<QUICFrameType> interests() override;
+  QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override;
+
+  // QUICNetVConnection
+  int in_closed_queue = 0;
+
+  bool shouldDestroy();
+  void destroy(EThread *t);
+  void remove_connection_ids();
+
+  LINK(QUICNetVConnection, closed_link);
+  SLINK(QUICNetVConnection, closed_alink);
+
+protected:
+  std::unique_ptr<QUICContext> _context;
+  QUICPacketHandler *_packet_handler = nullptr;
+
+  // TLSBasicSupport
+  SSL *_get_ssl_object() const override;
+  ssl_curve_id _get_tls_curve() const override;
+
+private:
+  QUICConfig::scoped_config _quic_config;
+
+  QUICConnectionId _peer_quic_connection_id;      // dst cid in local
+  QUICConnectionId _peer_old_quic_connection_id;  // dst previous cid in local
+  QUICConnectionId _original_quic_connection_id;  // dst cid of initial packet from client
+  QUICConnectionId _first_quic_connection_id;     // dst cid of initial packet from client that doesn't have retry token
+  QUICConnectionId _retry_source_connection_id;   // src cid used for sending Retry packet
+  QUICConnectionId _initial_source_connection_id; // src cid used for Initial packet
+  QUICConnectionId _quic_connection_id;           // src cid in local
+
+  UDPConnection *_udp_con      = nullptr;
+  quiche_conn *_quiche_con     = nullptr;
+  QUICConnectionTable *_ctable = nullptr;
+
+  void _schedule_packet_write_ready(bool delay = false);
+  void _unschedule_packet_write_ready();
+  void _close_packet_write_ready(Event *data);
+  Event *_packet_write_ready = nullptr;
+
+  void _handle_read_ready();
+  void _handle_write_ready();
+  void _handle_interval();
+
+  void _switch_to_established_state();
+
+  bool _handshake_completed = false;
+  bool _application_started = false;
+  void _start_application();
+
+  QUICStreamManagerImpl *_stream_manager = nullptr;
+  QUICApplicationMap *_application_map   = nullptr;
+};
+
+extern ClassAllocator<QUICNetVConnection> quicNetVCAllocator;
diff --git a/iocore/net/P_QUICNextProtocolAccept.h b/iocore/net/P_QUICNextProtocolAccept.h
index 3ca44db55..a950fa11c 100644
--- a/iocore/net/P_QUICNextProtocolAccept.h
+++ b/iocore/net/P_QUICNextProtocolAccept.h
@@ -1,6 +1,6 @@
 /** @file
 
-  QUICNextProtocolAccept
+  A brief file description
 
   @section license License
 
@@ -23,37 +23,10 @@
 
 #pragma once
 
-#include "P_QUICNetVConnection.h"
-#include "P_SSLNextProtocolSet.h"
-#include "I_IOBuffer.h"
+#include "tscore/ink_config.h"
 
-class QUICNextProtocolAccept : public SessionAccept
-{
-public:
-  QUICNextProtocolAccept();
-  ~QUICNextProtocolAccept();
-
-  bool accept(NetVConnection *, MIOBuffer *, IOBufferReader *) override;
-
-  // Register handler as an endpoint for the specified protocol. Neither
-  // handler nor protocol are copied, so the caller must guarantee their
-  // lifetime is at least as long as that of the acceptor.
-  bool registerEndpoint(const char *protocol, Continuation *handler);
-
-  void enableProtocols(const SessionProtocolSet &protos);
-
-  SLINK(QUICNextProtocolAccept, link);
-  SSLNextProtocolSet *getProtoSet();
-
-  // noncopyable
-  QUICNextProtocolAccept(const QUICNextProtocolAccept &) = delete;            // disabled
-  QUICNextProtocolAccept &operator=(const QUICNextProtocolAccept &) = delete; // disabled
-
-private:
-  int mainEvent(int event, void *netvc) override;
-
-  SSLNextProtocolSet protoset;
-  SessionProtocolSet protoenabled;
-
-  friend struct QUICNextProtocolTrampoline;
-};
+#if HAVE_QUICHE_H
+#include "P_QUICNextProtocolAccept_quiche.h"
+#else
+#include "P_QUICNextProtocolAccept_native.h"
+#endif
diff --git a/iocore/net/P_QUICNextProtocolAccept.h b/iocore/net/P_QUICNextProtocolAccept_native.h
similarity index 97%
copy from iocore/net/P_QUICNextProtocolAccept.h
copy to iocore/net/P_QUICNextProtocolAccept_native.h
index 3ca44db55..6dee22225 100644
--- a/iocore/net/P_QUICNextProtocolAccept.h
+++ b/iocore/net/P_QUICNextProtocolAccept_native.h
@@ -46,8 +46,8 @@ public:
   SSLNextProtocolSet *getProtoSet();
 
   // noncopyable
-  QUICNextProtocolAccept(const QUICNextProtocolAccept &) = delete;            // disabled
-  QUICNextProtocolAccept &operator=(const QUICNextProtocolAccept &) = delete; // disabled
+  QUICNextProtocolAccept(const QUICNextProtocolAccept &) = delete;
+  QUICNextProtocolAccept &operator=(const QUICNextProtocolAccept &) = delete;
 
 private:
   int mainEvent(int event, void *netvc) override;
diff --git a/iocore/net/P_QUICNextProtocolAccept.h b/iocore/net/P_QUICNextProtocolAccept_quiche.h
similarity index 95%
copy from iocore/net/P_QUICNextProtocolAccept.h
copy to iocore/net/P_QUICNextProtocolAccept_quiche.h
index 3ca44db55..49654d581 100644
--- a/iocore/net/P_QUICNextProtocolAccept.h
+++ b/iocore/net/P_QUICNextProtocolAccept_quiche.h
@@ -23,7 +23,7 @@
 
 #pragma once
 
-#include "P_QUICNetVConnection.h"
+#include "P_QUICNetVConnection_quiche.h"
 #include "P_SSLNextProtocolSet.h"
 #include "I_IOBuffer.h"
 
@@ -46,8 +46,8 @@ public:
   SSLNextProtocolSet *getProtoSet();
 
   // noncopyable
-  QUICNextProtocolAccept(const QUICNextProtocolAccept &) = delete;            // disabled
-  QUICNextProtocolAccept &operator=(const QUICNextProtocolAccept &) = delete; // disabled
+  QUICNextProtocolAccept(const QUICNextProtocolAccept &) = delete;
+  QUICNextProtocolAccept &operator=(const QUICNextProtocolAccept &) = delete;
 
 private:
   int mainEvent(int event, void *netvc) override;
diff --git a/iocore/net/P_QUICPacketHandler.h b/iocore/net/P_QUICPacketHandler.h
index 9428b89a4..4ec56a5ae 100644
--- a/iocore/net/P_QUICPacketHandler.h
+++ b/iocore/net/P_QUICPacketHandler.h
@@ -23,98 +23,10 @@
 
 #pragma once
 
-#include "tscore/ink_platform.h"
-#include "P_Connection.h"
-#include "P_NetAccept.h"
-#include "quic/QUICTypes.h"
-#include "quic/QUICConnectionTable.h"
-#include "quic/QUICResetTokenTable.h"
+#include "tscore/ink_config.h"
 
-class QUICClosedConCollector;
-class QUICNetVConnection;
-class QUICPacket;
-class QUICPacketHeaderProtector;
-
-class QUICPacketHandler
-{
-public:
-  QUICPacketHandler(QUICResetTokenTable &rtable);
-  ~QUICPacketHandler();
-
-  void send_packet(const QUICPacket &packet, QUICNetVConnection *vc, const QUICPacketHeaderProtector &pn_protector);
-  void send_packet(QUICNetVConnection *vc, const Ptr<IOBufferBlock> &udp_payload);
-
-  void close_connection(QUICNetVConnection *conn);
-
-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 continuation too.
-  virtual Continuation *_get_continuation() = 0;
-
-  Event *_collector_event                       = nullptr;
-  QUICClosedConCollector *_closed_con_collector = nullptr;
-
-  virtual void _recv_packet(int event, UDPPacket *udpPacket) = 0;
-
-  QUICResetTokenTable &_rtable;
-};
-
-/*
- * @class QUICPacketHandlerIn
- * @brief QUIC Packet Handler for incoming connections
- */
-class QUICPacketHandlerIn : public NetAccept, public QUICPacketHandler
-{
-public:
-  QUICPacketHandlerIn(const NetProcessor::AcceptOptions &opt, QUICConnectionTable &ctable, QUICResetTokenTable &rtable);
-  ~QUICPacketHandlerIn();
-
-  // NetAccept
-  virtual NetProcessor *getNetProcessor() const override;
-  virtual NetAccept *clone() const override;
-  virtual int acceptEvent(int event, void *e) override;
-  void init_accept(EThread *t) override;
-
-protected:
-  // QUICPacketHandler
-  Continuation *_get_continuation() override;
-
-private:
-  void _recv_packet(int event, UDPPacket *udp_packet) override;
-  int _stateless_retry(const uint8_t *buf, uint64_t buf_len, UDPConnection *connection, IpEndpoint from, QUICConnectionId dcid,
-                       QUICConnectionId scid, QUICConnectionId *original_cid, QUICConnectionId *retry_cid, QUICVersion version);
-  bool _send_stateless_reset(QUICConnectionId dcid, uint32_t instance_id, UDPConnection *udp_con, IpEndpoint &addr,
-                             size_t maximum_size);
-  void _send_invalid_token_error(const uint8_t *initial_packet, uint64_t initial_packet_len, UDPConnection *connection,
-                                 IpEndpoint from);
-
-  QUICConnectionTable &_ctable;
-};
-
-/*
- * @class QUICPacketHandlerOut
- * @brief QUIC Packet Handler for outgoing connections
- */
-class QUICPacketHandlerOut : public Continuation, public QUICPacketHandler
-{
-public:
-  QUICPacketHandlerOut(QUICResetTokenTable &rtable);
-  ~QUICPacketHandlerOut(){};
-
-  void init(QUICNetVConnection *vc);
-  int event_handler(int event, Event *data);
-
-protected:
-  // QUICPacketHandler
-  Continuation *_get_continuation() override;
-
-private:
-  void _recv_packet(int event, UDPPacket *udp_packet) override;
-
-  QUICNetVConnection *_vc = nullptr;
-};
+#if HAVE_QUICHE_H
+#include "P_QUICPacketHandler_quiche.h"
+#else
+#include "P_QUICPacketHandler_native.h"
+#endif
diff --git a/iocore/net/P_QUICPacketHandler.h b/iocore/net/P_QUICPacketHandler_native.h
similarity index 100%
copy from iocore/net/P_QUICPacketHandler.h
copy to iocore/net/P_QUICPacketHandler_native.h
diff --git a/iocore/net/P_QUICPacketHandler.h b/iocore/net/P_QUICPacketHandler_quiche.h
similarity index 52%
copy from iocore/net/P_QUICPacketHandler.h
copy to iocore/net/P_QUICPacketHandler_quiche.h
index 9428b89a4..7bb016738 100644
--- a/iocore/net/P_QUICPacketHandler.h
+++ b/iocore/net/P_QUICPacketHandler_quiche.h
@@ -26,52 +26,34 @@
 #include "tscore/ink_platform.h"
 #include "P_Connection.h"
 #include "P_NetAccept.h"
-#include "quic/QUICTypes.h"
-#include "quic/QUICConnectionTable.h"
-#include "quic/QUICResetTokenTable.h"
+#include <quiche.h>
 
-class QUICClosedConCollector;
 class QUICNetVConnection;
-class QUICPacket;
-class QUICPacketHeaderProtector;
+class QUICConnectionTable;
+class QUICClosedConCollector;
 
 class QUICPacketHandler
 {
 public:
-  QUICPacketHandler(QUICResetTokenTable &rtable);
-  ~QUICPacketHandler();
-
-  void send_packet(const QUICPacket &packet, QUICNetVConnection *vc, const QUICPacketHeaderProtector &pn_protector);
-  void send_packet(QUICNetVConnection *vc, const Ptr<IOBufferBlock> &udp_payload);
+  QUICPacketHandler();
+  virtual ~QUICPacketHandler();
 
+  void send_packet(UDPConnection *udp_con, IpEndpoint &addr, Ptr<IOBufferBlock> udp_payload);
   void close_connection(QUICNetVConnection *conn);
 
 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 continuation too.
-  virtual Continuation *_get_continuation() = 0;
-
   Event *_collector_event                       = nullptr;
   QUICClosedConCollector *_closed_con_collector = nullptr;
 
-  virtual void _recv_packet(int event, UDPPacket *udpPacket) = 0;
+  virtual Continuation *_get_continuation() = 0;
 
-  QUICResetTokenTable &_rtable;
+  virtual void _recv_packet(int event, UDPPacket *udpPacket) = 0;
 };
 
-/*
- * @class QUICPacketHandlerIn
- * @brief QUIC Packet Handler for incoming connections
- */
 class QUICPacketHandlerIn : public NetAccept, public QUICPacketHandler
 {
 public:
-  QUICPacketHandlerIn(const NetProcessor::AcceptOptions &opt, QUICConnectionTable &ctable, QUICResetTokenTable &rtable);
+  QUICPacketHandlerIn(const NetProcessor::AcceptOptions &opt, QUICConnectionTable &ctable, quiche_config &config);
   ~QUICPacketHandlerIn();
 
   // NetAccept
@@ -85,29 +67,19 @@ protected:
   Continuation *_get_continuation() override;
 
 private:
-  void _recv_packet(int event, UDPPacket *udp_packet) override;
-  int _stateless_retry(const uint8_t *buf, uint64_t buf_len, UDPConnection *connection, IpEndpoint from, QUICConnectionId dcid,
-                       QUICConnectionId scid, QUICConnectionId *original_cid, QUICConnectionId *retry_cid, QUICVersion version);
-  bool _send_stateless_reset(QUICConnectionId dcid, uint32_t instance_id, UDPConnection *udp_con, IpEndpoint &addr,
-                             size_t maximum_size);
-  void _send_invalid_token_error(const uint8_t *initial_packet, uint64_t initial_packet_len, UDPConnection *connection,
-                                 IpEndpoint from);
-
   QUICConnectionTable &_ctable;
+  quiche_config &_quiche_config;
+
+  void _recv_packet(int event, UDPPacket *udpPacket) override;
 };
 
-/*
- * @class QUICPacketHandlerOut
- * @brief QUIC Packet Handler for outgoing connections
- */
 class QUICPacketHandlerOut : public Continuation, public QUICPacketHandler
 {
 public:
-  QUICPacketHandlerOut(QUICResetTokenTable &rtable);
+  QUICPacketHandlerOut(){};
   ~QUICPacketHandlerOut(){};
 
   void init(QUICNetVConnection *vc);
-  int event_handler(int event, Event *data);
 
 protected:
   // QUICPacketHandler
@@ -115,6 +87,4 @@ protected:
 
 private:
   void _recv_packet(int event, UDPPacket *udp_packet) override;
-
-  QUICNetVConnection *_vc = nullptr;
 };
diff --git a/iocore/net/QUICNet.cc b/iocore/net/QUICNet.cc
index 31d33d9ea..da073dd49 100644
--- a/iocore/net/QUICNet.cc
+++ b/iocore/net/QUICNet.cc
@@ -60,6 +60,26 @@ QUICPollCont::QUICPollCont(Ptr<ProxyMutex> &m, NetHandler *nh) : Continuation(m.
 
 QUICPollCont::~QUICPollCont() {}
 
+#if HAVE_QUICHE_H
+void
+QUICPollCont::_process_packet(QUICPollEvent *e, NetHandler *nh)
+{
+  UDPPacketInternal *p   = e->packet;
+  QUICNetVConnection *vc = static_cast<QUICNetVConnection *>(e->con);
+
+  vc->read.triggered = 1;
+  vc->handle_received_packet(p);
+
+  // Push QUICNetVC into nethandler's enabled list
+  int isin = ink_atomic_swap(&vc->read.in_enabled_list, 1);
+  if (!isin) {
+    nh->read_enable_list.push(vc);
+  }
+
+  // Note: We should free QUICPollEvent here since vc could be freed from other thread.
+  e->free();
+}
+#else
 void
 QUICPollCont::_process_long_header_packet(QUICPollEvent *e, NetHandler *nh)
 {
@@ -118,6 +138,7 @@ QUICPollCont::_process_short_header_packet(QUICPollEvent *e, NetHandler *nh)
   // Note: We should free QUICPollEvent here since vc could be freed from other thread.
   e->free();
 }
+#endif
 
 //
 // QUICPollCont continuation which traverse the inQueue(ASLL)
@@ -128,7 +149,6 @@ int
 QUICPollCont::pollEvent(int, Event *)
 {
   ink_assert(this->mutex->thread_holding == this_thread());
-  uint8_t *buf;
   QUICPollEvent *e;
   NetHandler *nh = get_NetHandler(this->mutex->thread_holding);
 
@@ -147,6 +167,10 @@ QUICPollCont::pollEvent(int, Event *)
   }
 
   while ((e = result.pop())) {
+#if HAVE_QUICHE_H
+    this->_process_packet(e, nh);
+#else
+    uint8_t *buf;
     buf = reinterpret_cast<uint8_t *>(e->packet->getIOBlockChain()->buf());
     if (QUICInvariants::is_long_header(buf)) {
       // Long Header Packet with Connection ID, has a valid type value.
@@ -155,6 +179,7 @@ QUICPollCont::pollEvent(int, Event *)
       // Short Header Packet with Connection ID, has a valid type value.
       this->_process_short_header_packet(e, nh);
     }
+#endif
   }
 
   return EVENT_CONT;
diff --git a/iocore/net/QUICNetProcessor.cc b/iocore/net/QUICNetProcessor.cc
index a2af493fc..93d715230 100644
--- a/iocore/net/QUICNetProcessor.cc
+++ b/iocore/net/QUICNetProcessor.cc
@@ -135,7 +135,7 @@ QUICNetProcessor::connect_re(Continuation *cont, sockaddr const *remote_addr, Ne
   if (opt->local_ip.isValid()) {
     con->setBinding(opt->local_ip, opt->local_port);
   }
-  con->bindToThread(packet_handler);
+  con->bindToThread(packet_handler, t);
 
   PollCont *pc       = get_UDPPollCont(con->ethread);
   PollDescriptor *pd = pc->pollDescriptor;
diff --git a/iocore/net/QUICNetProcessor.cc b/iocore/net/QUICNetProcessor_quiche.cc
similarity index 77%
copy from iocore/net/QUICNetProcessor.cc
copy to iocore/net/QUICNetProcessor_quiche.cc
index a2af493fc..c5eeddf42 100644
--- a/iocore/net/QUICNetProcessor.cc
+++ b/iocore/net/QUICNetProcessor_quiche.cc
@@ -23,15 +23,18 @@
 #include "tscore/I_Layout.h"
 
 #include "P_Net.h"
+#include "P_QUICNet.h"
 #include "records/I_RecHttp.h"
 
-#include "P_QUICNetProcessor.h"
-#include "P_QUICNet.h"
-#include "P_QUICPacketHandler.h"
+#include "P_QUICNetProcessor_quiche.h"
+#include "P_QUICPacketHandler_quiche.h"
+#include "P_QUICNetVConnection_quiche.h"
 #include "QUICGlobals.h"
+#include "QUICTypes.h"
 #include "QUICConfig.h"
 #include "QUICMultiCertConfigLoader.h"
-#include "QUICResetTokenTable.h"
+
+#include <quiche.h>
 
 //
 // Global Data
@@ -39,12 +42,19 @@
 
 QUICNetProcessor quic_NetProcessor;
 
+static void
+debug_log(const char *line, void *argp)
+{
+  Debug("vv_quiche", "%s\n", line);
+}
+
 QUICNetProcessor::QUICNetProcessor() {}
 
 QUICNetProcessor::~QUICNetProcessor()
 {
-  // TODO: clear all values before destroy the table.
-  delete this->_ctable;
+  if (this->_quiche_config != nullptr) {
+    quiche_config_free(this->_quiche_config);
+  }
 }
 
 void
@@ -65,6 +75,25 @@ QUICNetProcessor::start(int, size_t stacksize)
   // QUICInitializeLibrary();
   QUICConfig::startup();
   QUICCertConfig::startup();
+  QUICCertConfig::scoped_config certs;
+  SSLCertContext *context = certs->get(0);
+
+  quiche_enable_debug_logging(debug_log, NULL);
+  this->_quiche_config = quiche_config_new(QUICHE_PROTOCOL_VERSION);
+  quiche_config_set_application_protos(this->_quiche_config, (uint8_t *)"\02h3\x05h3-29\x05hq-29\x05h3-27\x05hq-27", 27);
+  quiche_config_load_cert_chain_from_pem_file(this->_quiche_config, context->userconfig->cert);
+  quiche_config_load_priv_key_from_pem_file(this->_quiche_config, context->userconfig->key);
+
+  quiche_config_set_max_idle_timeout(this->_quiche_config, 5000);
+  quiche_config_set_max_recv_udp_payload_size(this->_quiche_config, 16384);
+  quiche_config_set_max_send_udp_payload_size(this->_quiche_config, 16384);
+  quiche_config_set_initial_max_data(this->_quiche_config, 10000000);
+  quiche_config_set_initial_max_stream_data_bidi_local(this->_quiche_config, 1000000);
+  quiche_config_set_initial_max_stream_data_bidi_remote(this->_quiche_config, 1000000);
+  quiche_config_set_initial_max_stream_data_uni(this->_quiche_config, 1000000);
+  quiche_config_set_initial_max_streams_bidi(this->_quiche_config, 100);
+  quiche_config_set_initial_max_streams_uni(this->_quiche_config, 100);
+  quiche_config_set_cc_algorithm(this->_quiche_config, QUICHE_CC_RENO);
 
 #ifdef TLS1_3_VERSION_DRAFT_TXT
   // FIXME: remove this when TLS1_3_VERSION_DRAFT_TXT is removed
@@ -80,9 +109,8 @@ 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, *this->_rtable);
+  return (NetAccept *)new QUICPacketHandlerIn(opt, *this->_ctable, *this->_quiche_config);
 }
 
 NetVConnection *
@@ -99,7 +127,6 @@ QUICNetProcessor::allocate_vc(EThread *t)
       vc->from_accept_thread = true;
     }
   }
-
   vc->ep.syscall = false;
   return vc;
 }
@@ -110,6 +137,7 @@ QUICNetProcessor::connect_re(Continuation *cont, sockaddr const *remote_addr, Ne
   Debug("quic_ps", "connect to server");
   EThread *t = cont->mutex->thread_holding;
   ink_assert(t);
+
   QUICNetVConnection *vc = static_cast<QUICNetVConnection *>(this->allocate_vc(t));
 
   if (opt) {
@@ -130,12 +158,11 @@ QUICNetProcessor::connect_re(Continuation *cont, sockaddr const *remote_addr, Ne
   UnixUDPConnection *con = new UnixUDPConnection(fd);
   Debug("quic_ps", "con=%p fd=%d", con, fd);
 
-  this->_rtable                        = new QUICResetTokenTable();
-  QUICPacketHandlerOut *packet_handler = new QUICPacketHandlerOut(*this->_rtable);
+  QUICPacketHandlerOut *packet_handler = new QUICPacketHandlerOut();
   if (opt->local_ip.isValid()) {
     con->setBinding(opt->local_ip, opt->local_port);
   }
-  con->bindToThread(packet_handler);
+  con->bindToThread(packet_handler, t);
 
   PollCont *pc       = get_UDPPollCont(con->ethread);
   PollDescriptor *pd = pc->pollDescriptor;
@@ -150,7 +177,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(QUIC_SUPPORTED_VERSIONS[0], client_dst_cid, client_dst_cid, con, packet_handler, this->_rtable);
+  vc->init(QUIC_SUPPORTED_VERSIONS[0], client_dst_cid, client_dst_cid, con, packet_handler);
   packet_handler->init(vc);
 
   // Connection ID will be changed
@@ -220,8 +247,5 @@ QUICNetProcessor::main_accept(Continuation *cont, SOCKET fd, AcceptOptions const
   na->action_->server = &na->server;
   na->init_accept();
 
-  SCOPED_MUTEX_LOCK(lock, na->mutex, this_ethread());
-  udpNet.UDPBind((Continuation *)na, &na->server.accept_addr.sa, fd, 1048576, 1048576);
-
   return na->action_.get();
 }
diff --git a/iocore/net/QUICNetVConnection.cc b/iocore/net/QUICNetVConnection.cc
index 70d20ef93..18d087a93 100644
--- a/iocore/net/QUICNetVConnection.cc
+++ b/iocore/net/QUICNetVConnection.cc
@@ -453,7 +453,7 @@ QUICNetVConnection::start()
 
   this->_remote_flow_controller = new QUICRemoteConnectionFlowController(UINT64_MAX);
   this->_local_flow_controller  = new QUICLocalConnectionFlowController(&this->_rtt_measure, UINT64_MAX);
-  this->_stream_manager         = new QUICStreamManager(this->_context.get(), this->_application_map);
+  this->_stream_manager         = new QUICStreamManagerImpl(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;
diff --git a/iocore/net/QUICNetVConnection_quiche.cc b/iocore/net/QUICNetVConnection_quiche.cc
new file mode 100644
index 000000000..c92dbbc4c
--- /dev/null
+++ b/iocore/net/QUICNetVConnection_quiche.cc
@@ -0,0 +1,623 @@
+/** @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 "P_QUICNetVConnection_quiche.h"
+#include "P_QUICPacketHandler_quiche.h"
+#include "quic/QUICStream_quiche.h"
+#include <quiche.h>
+
+static constexpr ink_hrtime WRITE_READY_INTERVAL = HRTIME_MSECONDS(2);
+
+#define QUICConDebug(fmt, ...) Debug("quic_net", "[%s] " fmt, this->cids().data(), ##__VA_ARGS__)
+#define QUICConVDebug(fmt, ...) Debug("v_quic_net", "[%s] " fmt, this->cids().data(), ##__VA_ARGS__)
+
+ClassAllocator<QUICNetVConnection> quicNetVCAllocator("quicNetVCAllocator");
+
+QUICNetVConnection::QUICNetVConnection() {}
+
+QUICNetVConnection::~QUICNetVConnection() {}
+
+void
+QUICNetVConnection::init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, UDPConnection *,
+                         QUICPacketHandler *)
+{
+}
+
+void
+QUICNetVConnection::init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, QUICConnectionId first_cid,
+                         QUICConnectionId retry_cid, UDPConnection *udp_con, quiche_conn *quiche_con,
+                         QUICPacketHandler *packet_handler, QUICConnectionTable *ctable)
+{
+  SET_HANDLER((NetVConnHandler)&QUICNetVConnection::acceptEvent);
+  this->_udp_con                     = udp_con;
+  this->_quiche_con                  = quiche_con;
+  this->_packet_handler              = packet_handler;
+  this->_original_quic_connection_id = original_cid;
+  this->_quic_connection_id.randomize();
+  this->_initial_source_connection_id = this->_quic_connection_id;
+
+  if (ctable) {
+    this->_ctable = ctable;
+    this->_ctable->insert(this->_quic_connection_id, this);
+    this->_ctable->insert(this->_original_quic_connection_id, this);
+  }
+}
+
+void
+QUICNetVConnection::free()
+{
+  this->free(this_ethread());
+}
+
+// called by ET_UDP
+void
+QUICNetVConnection::remove_connection_ids()
+{
+}
+
+// called by ET_UDP
+void
+QUICNetVConnection::destroy(EThread *t)
+{
+  QUICConDebug("Destroy connection");
+  if (from_accept_thread) {
+    quicNetVCAllocator.free(this);
+  } else {
+    THREAD_FREE(this, quicNetVCAllocator, t);
+  }
+}
+
+void
+QUICNetVConnection::set_local_addr()
+{
+}
+
+void
+QUICNetVConnection::free(EThread *t)
+{
+  QUICConDebug("Free connection");
+
+  this->_udp_con = nullptr;
+
+  quiche_conn_free(this->_quiche_con);
+
+  delete this->_application_map;
+  this->_application_map = nullptr;
+  delete this->_stream_manager;
+  this->_stream_manager = nullptr;
+
+  super::clear();
+  this->_context->trigger(QUICContext::CallbackEvent::CONNECTION_CLOSE);
+  ALPNSupport::clear();
+  TLSBasicSupport::clear();
+
+  this->_packet_handler->close_connection(this);
+  this->_packet_handler = nullptr;
+}
+
+void
+QUICNetVConnection::reenable(VIO *vio)
+{
+}
+
+int
+QUICNetVConnection::state_handshake(int event, Event *data)
+{
+  if (quiche_conn_is_established(this->_quiche_con)) {
+    this->_switch_to_established_state();
+    return this->handleEvent(event, data);
+  }
+
+  switch (event) {
+  case QUIC_EVENT_PACKET_READ_READY:
+    this->_handle_read_ready();
+    break;
+  case QUIC_EVENT_PACKET_WRITE_READY:
+    this->_close_packet_write_ready(data);
+    this->_handle_write_ready();
+    // Reschedule WRITE_READY
+    this->_schedule_packet_write_ready(true);
+    break;
+  case EVENT_INTERVAL:
+    this->_handle_interval();
+    break;
+  case VC_EVENT_EOS:
+  case VC_EVENT_ERROR:
+  case VC_EVENT_ACTIVE_TIMEOUT:
+  case VC_EVENT_INACTIVITY_TIMEOUT:
+    _unschedule_packet_write_ready();
+    this->closed = 1;
+    break;
+  default:
+    QUICConDebug("Unhandleed event: %d", event);
+    break;
+  }
+
+  return EVENT_DONE;
+}
+
+int
+QUICNetVConnection::state_established(int event, Event *data)
+{
+  switch (event) {
+  case QUIC_EVENT_PACKET_READ_READY:
+    this->_handle_read_ready();
+    break;
+  case QUIC_EVENT_PACKET_WRITE_READY:
+    this->_close_packet_write_ready(data);
+    this->_handle_write_ready();
+    // Reschedule WRITE_READY
+    this->_schedule_packet_write_ready(true);
+    break;
+  case EVENT_INTERVAL:
+    this->_handle_interval();
+    break;
+  case VC_EVENT_EOS:
+  case VC_EVENT_ERROR:
+  case VC_EVENT_ACTIVE_TIMEOUT:
+  case VC_EVENT_INACTIVITY_TIMEOUT:
+    _unschedule_packet_write_ready();
+    this->closed = 1;
+    break;
+  default:
+    QUICConDebug("Unhandleed event: %d", event);
+    break;
+  }
+  return EVENT_DONE;
+}
+
+void
+QUICNetVConnection::_switch_to_established_state()
+{
+  QUICConDebug("Enter state_connection_established");
+  this->_record_tls_handshake_end_time();
+  SET_HANDLER((NetVConnHandler)&QUICNetVConnection::state_established);
+  this->_start_application();
+  this->_handshake_completed = true;
+}
+
+void
+QUICNetVConnection::_start_application()
+{
+  if (!this->_application_started) {
+    this->_application_started = true;
+
+    const uint8_t *app_name;
+    size_t app_name_len = 0;
+    quiche_conn_application_proto(this->_quiche_con, &app_name, &app_name_len);
+    if (app_name == nullptr) {
+      app_name     = reinterpret_cast<const uint8_t *>(IP_PROTO_TAG_HTTP_QUIC.data());
+      app_name_len = IP_PROTO_TAG_HTTP_QUIC.size();
+    }
+
+    this->set_negotiated_protocol_id({reinterpret_cast<const char *>(app_name), static_cast<size_t>(app_name_len)});
+
+    if (netvc_context == NET_VCONNECTION_IN) {
+      if (!this->setSelectedProtocol(app_name, app_name_len)) {
+        // this->_handle_error(std::make_unique<QUICConnectionError>(QUICTransErrorCode::PROTOCOL_VIOLATION));
+      } else {
+        this->endpoint()->handleEvent(NET_EVENT_ACCEPT, this);
+      }
+    } else {
+      this->action_.continuation->handleEvent(NET_EVENT_OPEN, this);
+    }
+  }
+}
+
+bool
+QUICNetVConnection::shouldDestroy()
+{
+  return this->refcount() == 0;
+}
+
+VIO *
+QUICNetVConnection::do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf)
+{
+  ink_assert(false);
+  return nullptr;
+}
+
+VIO *
+QUICNetVConnection::do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner)
+{
+  ink_assert(false);
+  return nullptr;
+}
+
+int
+QUICNetVConnection::acceptEvent(int event, Event *e)
+{
+  EThread *t    = (e == nullptr) ? this_ethread() : e->ethread;
+  NetHandler *h = get_NetHandler(t);
+
+  MUTEX_TRY_LOCK(lock, h->mutex, t);
+  if (!lock.is_locked()) {
+    if (event == EVENT_NONE) {
+      t->schedule_in(this, HRTIME_MSECONDS(net_retry_delay));
+      return EVENT_DONE;
+    } else {
+      e->schedule_in(HRTIME_MSECONDS(net_retry_delay));
+      return EVENT_CONT;
+    }
+  }
+
+  this->_context         = std::make_unique<QUICContext>(this);
+  this->_application_map = new QUICApplicationMap();
+  this->_stream_manager  = new QUICStreamManagerImpl(this->_context.get(), this->_application_map);
+
+  // this->thread is already assigned by QUICPacketHandlerIn::_recv_packet
+  ink_assert(this->thread == this_ethread());
+
+  // Send this NetVC to NetHandler and start to polling read & write event.
+  if (h->startIO(this) < 0) {
+    free(t);
+    return EVENT_DONE;
+  }
+
+  // FIXME: complete do_io_xxxx instead
+  this->read.enabled = 1;
+
+  // Handshake callback handler.
+  SET_HANDLER((NetVConnHandler)&QUICNetVConnection::state_handshake);
+
+  // Send this netvc to InactivityCop.
+  nh->startCop(this);
+
+  if (inactivity_timeout_in) {
+    set_inactivity_timeout(inactivity_timeout_in);
+  } else {
+    set_inactivity_timeout(0);
+  }
+
+  if (active_timeout_in) {
+    set_active_timeout(active_timeout_in);
+  }
+
+  action_.continuation->handleEvent(NET_EVENT_ACCEPT, this);
+  this->_schedule_packet_write_ready();
+
+  this->thread->schedule_in(this, HRTIME_MSECONDS(quiche_conn_timeout_as_millis(this->_quiche_con)));
+
+  return EVENT_DONE;
+}
+
+int
+QUICNetVConnection::connectUp(EThread *t, int fd)
+{
+  return 0;
+}
+
+QUICStreamManager *
+QUICNetVConnection::stream_manager()
+{
+  return this->_stream_manager;
+}
+
+void
+QUICNetVConnection::close_quic_connection(QUICConnectionErrorUPtr error)
+{
+}
+
+void
+QUICNetVConnection::reset_quic_connection()
+{
+}
+
+void
+QUICNetVConnection::handle_received_packet(UDPPacket *packet)
+{
+  IOBufferBlock *block = packet->getIOBlockChain();
+  uint8_t *buf         = reinterpret_cast<uint8_t *>(block->buf());
+  uint64_t buf_len     = block->size();
+
+  net_activity(this, this_ethread());
+  quiche_recv_info recv_info = {
+    &packet->from.sa,
+    static_cast<socklen_t>(packet->from.isIp4() ? sizeof(packet->from.sin) : sizeof(packet->from.sin6)),
+#ifdef HAVE_QUICHE_CONFIG_SET_ACTIVE_CONNECTION_ID_LIMIT
+    &packet->to.sa,
+    static_cast<socklen_t>(packet->to.isIp4() ? sizeof(packet->to.sin) : sizeof(packet->to.sin6)),
+#endif
+  };
+
+  ssize_t done = quiche_conn_recv(this->_quiche_con, buf, buf_len, &recv_info);
+  if (done < 0) {
+    QUICConVDebug("failed to process packet: %zd", done);
+    return;
+  }
+}
+
+void
+QUICNetVConnection::ping()
+{
+}
+
+QUICConnectionId
+QUICNetVConnection::peer_connection_id() const
+{
+  return {};
+}
+
+QUICConnectionId
+QUICNetVConnection::original_connection_id() const
+{
+  return {};
+}
+
+QUICConnectionId
+QUICNetVConnection::first_connection_id() const
+{
+  return {};
+}
+
+QUICConnectionId
+QUICNetVConnection::retry_source_connection_id() const
+{
+  return {};
+}
+
+QUICConnectionId
+QUICNetVConnection::initial_source_connection_id() const
+{
+  return {};
+}
+
+QUICConnectionId
+QUICNetVConnection::connection_id() const
+{
+  return {};
+}
+
+std::string_view
+QUICNetVConnection::cids() const
+{
+  return "";
+}
+
+const QUICFiveTuple
+QUICNetVConnection::five_tuple() const
+{
+  return {};
+}
+
+uint32_t
+QUICNetVConnection::pmtu() const
+{
+  return 0;
+}
+
+NetVConnectionContext_t
+QUICNetVConnection::direction() const
+{
+  return NET_VCONNECTION_IN;
+}
+
+QUICVersion
+QUICNetVConnection::negotiated_version() const
+{
+  return 0;
+}
+
+std::string_view
+QUICNetVConnection::negotiated_application_name() const
+{
+  const uint8_t *name;
+  size_t name_len = 0;
+  quiche_conn_application_proto(this->_quiche_con, &name, &name_len);
+
+  return std::string_view(reinterpret_cast<const char *>(name), name_len);
+}
+
+bool
+QUICNetVConnection::is_closed() const
+{
+  return quiche_conn_is_closed(this->_quiche_con);
+}
+
+bool
+QUICNetVConnection::is_at_anti_amplification_limit() const
+{
+  return false;
+}
+
+bool
+QUICNetVConnection::is_address_validation_completed() const
+{
+  return false;
+}
+
+bool
+QUICNetVConnection::is_handshake_completed() const
+{
+  return false;
+}
+
+bool
+QUICNetVConnection::has_keys_for(QUICPacketNumberSpace space) const
+{
+  return false;
+}
+
+std::vector<QUICFrameType>
+QUICNetVConnection::interests()
+{
+  return {};
+}
+
+QUICConnectionErrorUPtr
+QUICNetVConnection::handle_frame(QUICEncryptionLevel level, const QUICFrame &frame)
+{
+  return nullptr;
+}
+
+void
+QUICNetVConnection::net_read_io(NetHandler *nh, EThread *lthread)
+{
+  if (quiche_conn_is_readable(this->_quiche_con)) {
+    SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread());
+    this->handleEvent(QUIC_EVENT_PACKET_READ_READY, nullptr);
+  }
+}
+
+int64_t
+QUICNetVConnection::load_buffer_and_write(int64_t towrite, MIOBufferAccessor &buf, int64_t &total_written, int &needs)
+{
+  return 0;
+}
+
+void
+QUICNetVConnection::_schedule_packet_write_ready(bool delay)
+{
+  if (!this->_packet_write_ready) {
+    if (delay) {
+      this->_packet_write_ready = this->thread->schedule_in(this, WRITE_READY_INTERVAL, QUIC_EVENT_PACKET_WRITE_READY, nullptr);
+    } else {
+      this->_packet_write_ready = this->thread->schedule_imm(this, QUIC_EVENT_PACKET_WRITE_READY, nullptr);
+    }
+  }
+}
+
+void
+QUICNetVConnection::_unschedule_packet_write_ready()
+{
+  if (this->_packet_write_ready) {
+    this->_packet_write_ready->cancel();
+    this->_packet_write_ready = nullptr;
+  }
+}
+
+void
+QUICNetVConnection::_close_packet_write_ready(Event *data)
+{
+  ink_assert(this->_packet_write_ready == data);
+  this->_packet_write_ready = nullptr;
+}
+
+void
+QUICNetVConnection::_handle_read_ready()
+{
+  quiche_stream_iter *readable = quiche_conn_readable(this->_quiche_con);
+  uint64_t s                   = 0;
+  while (quiche_stream_iter_next(readable, &s)) {
+    QUICStreamImpl *stream;
+    QUICConVDebug("stream %" PRIu64 " is readable\n", s);
+    stream = static_cast<QUICStreamImpl *>(quiche_conn_stream_application_data(this->_quiche_con, s));
+    if (stream == nullptr) {
+      this->_stream_manager->create_stream(s);
+      stream = static_cast<QUICStreamImpl *>(this->_stream_manager->find_stream(s));
+      quiche_conn_stream_init_application_data(this->_quiche_con, s, stream);
+    }
+    stream->receive_data(this->_quiche_con);
+  }
+  quiche_stream_iter_free(readable);
+}
+
+void
+QUICNetVConnection::_handle_write_ready()
+{
+  if (quiche_conn_is_established(this->_quiche_con)) {
+    quiche_stream_iter *writable = quiche_conn_writable(this->_quiche_con);
+    uint64_t s                   = 0;
+    while (quiche_stream_iter_next(writable, &s)) {
+      QUICStreamImpl *stream;
+      stream = static_cast<QUICStreamImpl *>(quiche_conn_stream_application_data(this->_quiche_con, s));
+      stream->send_data(this->_quiche_con);
+    }
+    quiche_stream_iter_free(writable);
+  }
+
+  Ptr<IOBufferBlock> udp_payload;
+  quiche_send_info send_info;
+  ssize_t written;
+
+  do {
+    udp_payload = (new_IOBufferBlock());
+    udp_payload->alloc(iobuffer_size_to_index(quiche_conn_max_send_udp_payload_size(this->_quiche_con), BUFFER_SIZE_INDEX_16K));
+    written =
+      quiche_conn_send(this->_quiche_con, reinterpret_cast<uint8_t *>(udp_payload->end()), udp_payload->write_avail(), &send_info);
+    if (written > 0) {
+      udp_payload->fill(written);
+      this->_packet_handler->send_packet(this->_udp_con, this->con.addr, udp_payload);
+      net_activity(this, this_ethread());
+    }
+  } while (written > 0);
+}
+
+void
+QUICNetVConnection::_handle_interval()
+{
+  quiche_conn_on_timeout(this->_quiche_con);
+
+  if (quiche_conn_is_closed(this->_quiche_con)) {
+    this->_ctable->erase(this->_quic_connection_id, this);
+    this->_ctable->erase(this->_original_quic_connection_id, this);
+
+    if (quiche_conn_is_timed_out(this->_quiche_con)) {
+      this->thread->schedule_imm(this, VC_EVENT_INACTIVITY_TIMEOUT);
+      return;
+    }
+
+    bool is_app;
+    uint64_t error_code;
+    const uint8_t *reason;
+    size_t reason_len;
+    bool has_error = quiche_conn_peer_error(this->_quiche_con, &is_app, &error_code, &reason, &reason_len) ||
+                     quiche_conn_local_error(this->_quiche_con, &is_app, &error_code, &reason, &reason_len);
+    if (has_error && error_code != static_cast<uint64_t>(QUICTransErrorCode::NO_ERROR)) {
+      QUICConDebug("is_app=%d error_code=%" PRId64 " reason=%.*s", is_app, error_code, static_cast<int>(reason_len), reason);
+      this->thread->schedule_imm(this, VC_EVENT_ERROR);
+      return;
+    }
+
+    // If it's not timeout nor error, it's probably eos
+    this->thread->schedule_imm(this, VC_EVENT_EOS);
+
+  } else {
+    // Just schedule timeout event again if the connection is still open
+    this->thread->schedule_in(this, HRTIME_MSECONDS(quiche_conn_timeout_as_millis(this->_quiche_con)));
+  }
+}
+
+int
+QUICNetVConnection::populate_protocol(std::string_view *results, int n) const
+{
+  return 0;
+}
+
+const char *
+QUICNetVConnection::protocol_contains(std::string_view tag) const
+{
+  return "";
+}
+
+SSL *
+QUICNetVConnection::_get_ssl_object() const
+{
+  return nullptr;
+}
+
+ssl_curve_id
+QUICNetVConnection::_get_tls_curve() const
+{
+  return 0;
+}
diff --git a/iocore/net/QUICNextProtocolAccept.cc b/iocore/net/QUICNextProtocolAccept.cc
index 53faf4307..f3691e8d6 100644
--- a/iocore/net/QUICNextProtocolAccept.cc
+++ b/iocore/net/QUICNextProtocolAccept.cc
@@ -21,7 +21,7 @@
   limitations under the License.
  */
 
-#include "P_QUICNextProtocolAccept.h"
+#include "P_QUICNextProtocolAccept_native.h"
 
 static QUICNetVConnection *
 quic_netvc_cast(int event, void *edata)
diff --git a/iocore/net/QUICNextProtocolAccept.cc b/iocore/net/QUICNextProtocolAccept_quiche.cc
similarity index 98%
copy from iocore/net/QUICNextProtocolAccept.cc
copy to iocore/net/QUICNextProtocolAccept_quiche.cc
index 53faf4307..ed2256fe2 100644
--- a/iocore/net/QUICNextProtocolAccept.cc
+++ b/iocore/net/QUICNextProtocolAccept_quiche.cc
@@ -21,7 +21,7 @@
   limitations under the License.
  */
 
-#include "P_QUICNextProtocolAccept.h"
+#include "P_QUICNextProtocolAccept_quiche.h"
 
 static QUICNetVConnection *
 quic_netvc_cast(int event, void *edata)
diff --git a/iocore/net/QUICPacketHandler_quiche.cc b/iocore/net/QUICPacketHandler_quiche.cc
new file mode 100644
index 000000000..27cb45a9e
--- /dev/null
+++ b/iocore/net/QUICPacketHandler_quiche.cc
@@ -0,0 +1,339 @@
+
+/** @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/I_Layout.h"
+#include "tscore/ink_config.h"
+#include "P_Net.h"
+
+#include "P_QUICNet.h"
+#include "P_QUICPacketHandler_quiche.h"
+#include "P_QUICNetProcessor_quiche.h"
+#include "P_QUICClosedConCollector.h"
+#include "quic/QUICConnectionTable.h"
+#include <quiche.h>
+
+static constexpr char debug_tag[]   = "quic_sec";
+static constexpr char v_debug_tag[] = "v_quic_sec";
+
+#define QUICDebug(fmt, ...) Debug(debug_tag, fmt, ##__VA_ARGS__)
+#define QUICPHDebug(dcid, scid, fmt, ...) \
+  Debug(debug_tag, "[%08" PRIx32 "-%08" PRIx32 "] " fmt, dcid.h32(), scid.h32(), ##__VA_ARGS__)
+#define QUICVPHDebug(dcid, scid, fmt, ...) \
+  Debug(v_debug_tag, "[%08" PRIx32 "-%08" PRIx32 "] " fmt, dcid.h32(), scid.h32(), ##__VA_ARGS__)
+
+QUICPacketHandler::QUICPacketHandler()
+{
+  this->_closed_con_collector        = new QUICClosedConCollector;
+  this->_closed_con_collector->mutex = new_ProxyMutex();
+}
+
+QUICPacketHandler::~QUICPacketHandler()
+{
+  if (this->_collector_event != nullptr) {
+    this->_collector_event->cancel();
+    this->_collector_event = nullptr;
+  }
+
+  if (this->_closed_con_collector != nullptr) {
+    delete this->_closed_con_collector;
+    this->_closed_con_collector = nullptr;
+  }
+}
+
+void
+QUICPacketHandler::close_connection(QUICNetVConnection *conn)
+{
+  int isin = ink_atomic_swap(&conn->in_closed_queue, 1);
+  if (!isin) {
+    this->_closed_con_collector->closedQueue.push(conn);
+  }
+}
+
+void
+QUICPacketHandler::send_packet(UDPConnection *udp_con, IpEndpoint &addr, Ptr<IOBufferBlock> udp_payload)
+{
+  UDPPacket *udp_packet = new_UDPPacket(addr, 0, udp_payload);
+
+  if (is_debug_tag_set(v_debug_tag)) {
+    ip_port_text_buffer ipb;
+    QUICConnectionId dcid = QUICConnectionId::ZERO();
+    QUICConnectionId scid = QUICConnectionId::ZERO();
+
+    const uint8_t *buf = reinterpret_cast<uint8_t *>(udp_payload->buf());
+    uint64_t buf_len   = udp_payload->size();
+
+    if (!QUICInvariants::dcid(dcid, buf, buf_len)) {
+      ink_assert(false);
+    }
+
+    if (QUICInvariants::is_long_header(buf)) {
+      if (!QUICInvariants::scid(scid, buf, buf_len)) {
+        ink_assert(false);
+      }
+    }
+
+    QUICVPHDebug(dcid, scid, "send %s packet to %s from port %u size=%" PRId64, (QUICInvariants::is_long_header(buf) ? "LH" : "SH"),
+                 ats_ip_nptop(&addr, ipb, sizeof(ipb)), udp_con->getPortNum(), buf_len);
+  }
+
+  udp_con->send(this->_get_continuation(), udp_packet);
+  get_UDPNetHandler(static_cast<UnixUDPConnection *>(udp_con)->ethread)->signalActivity();
+}
+
+QUICPacketHandlerIn::QUICPacketHandlerIn(const NetProcessor::AcceptOptions &opt, QUICConnectionTable &ctable, quiche_config &config)
+  : NetAccept(opt), QUICPacketHandler(), _ctable(ctable), _quiche_config(config)
+{
+  this->mutex = new_ProxyMutex();
+}
+
+QUICPacketHandlerIn::~QUICPacketHandlerIn() {}
+
+NetProcessor *
+QUICPacketHandlerIn::getNetProcessor() const
+{
+  return &quic_NetProcessor;
+}
+
+NetAccept *
+QUICPacketHandlerIn::clone() const
+{
+  NetAccept *na;
+  na  = new QUICPacketHandlerIn(opt, this->_ctable, this->_quiche_config);
+  *na = *this;
+  return na;
+}
+
+int
+QUICPacketHandlerIn::acceptEvent(int event, void *data)
+{
+  // NetVConnection *netvc;
+  ink_release_assert(event == EVENT_IMMEDIATE || event == NET_EVENT_DATAGRAM_OPEN || event == NET_EVENT_DATAGRAM_READ_READY ||
+                     event == NET_EVENT_DATAGRAM_ERROR);
+  ink_release_assert((event == NET_EVENT_DATAGRAM_OPEN) ? (data != nullptr) : (1));
+  ink_release_assert((event == NET_EVENT_DATAGRAM_READ_READY) ? (data != nullptr) : (1));
+
+  if (event == NET_EVENT_DATAGRAM_OPEN) {
+    // Nothing to do.
+    return EVENT_CONT;
+  } else if (event == NET_EVENT_DATAGRAM_READ_READY) {
+    if (this->_collector_event == nullptr) {
+      this->_collector_event = this_ethread()->schedule_every(this->_closed_con_collector, HRTIME_MSECONDS(100));
+    }
+
+    Queue<UDPPacket> *queue = static_cast<Queue<UDPPacket> *>(data);
+    UDPPacket *packet_r;
+    while ((packet_r = queue->dequeue())) {
+      this->_recv_packet(event, packet_r);
+    }
+    return EVENT_CONT;
+  } else if (event == EVENT_IMMEDIATE) {
+    this->setThreadAffinity(this_ethread());
+    SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread());
+    udpNet.UDPBind((Continuation *)this, &this->server.accept_addr.sa, -1, 1048576, 1048576);
+    return EVENT_CONT;
+  }
+
+  /////////////////
+  // EVENT_ERROR //
+  /////////////////
+  if (((long)data) == -ECONNABORTED) {
+  }
+
+  ink_abort("QUIC accept received fatal error: errno = %d", -(static_cast<int>((intptr_t)data)));
+  return EVENT_CONT;
+}
+
+void
+QUICPacketHandlerIn::init_accept(EThread *t = nullptr)
+{
+  int i, n;
+
+  SET_HANDLER(&QUICPacketHandlerIn::acceptEvent);
+
+  n = eventProcessor.thread_group[ET_UDP]._count;
+  for (i = 0; i < n; i++) {
+    NetAccept *a = (i < n - 1) ? clone() : this;
+    EThread *t   = eventProcessor.thread_group[ET_UDP]._thread[i];
+    a->mutex     = get_NetHandler(t)->mutex;
+    t->schedule_imm(a);
+  }
+}
+
+Continuation *
+QUICPacketHandlerIn::_get_continuation()
+{
+  return static_cast<NetAccept *>(this);
+}
+
+void
+QUICPacketHandlerIn::_recv_packet(int event, UDPPacket *udp_packet)
+{
+  // Assumption: udp_packet has only one IOBufferBlock
+  IOBufferBlock *block = udp_packet->getIOBlockChain();
+  const uint8_t *buf   = reinterpret_cast<uint8_t *>(block->buf());
+  uint64_t buf_len     = block->size();
+
+  constexpr int MAX_TOKEN_LEN             = 1200;
+  constexpr int DEFAULT_MAX_DATAGRAM_SIZE = 1350;
+  uint8_t type;
+  uint32_t version;
+  uint8_t scid[QUICHE_MAX_CONN_ID_LEN];
+  size_t scid_len = sizeof(scid);
+  uint8_t dcid[QUICHE_MAX_CONN_ID_LEN];
+  size_t dcid_len = sizeof(dcid);
+  uint8_t token[MAX_TOKEN_LEN];
+  size_t token_len = sizeof(token);
+
+  int rc = quiche_header_info(buf, buf_len, QUICConnectionId::SCID_LEN, &version, &type, scid, &scid_len, dcid, &dcid_len, token,
+                              &token_len);
+  if (rc < 0) {
+    QUICDebug("Ignore packet - failed to parse header");
+    udp_packet->free();
+    return;
+  }
+
+  if (dcid_len > 255 || scid_len > 255) {
+    QUICDebug("Ignore packet - too long connection id");
+    udp_packet->free();
+    return;
+  }
+  QUICConnection *qc     = this->_ctable.lookup({dcid, static_cast<uint8_t>(dcid_len)});
+  QUICNetVConnection *vc = static_cast<QUICNetVConnection *>(qc);
+
+  EThread *eth = nullptr;
+  if (vc == nullptr) {
+    if (!quiche_version_is_supported(version)) {
+      Ptr<IOBufferBlock> udp_payload(new_IOBufferBlock());
+      udp_payload->alloc(iobuffer_size_to_index(DEFAULT_MAX_DATAGRAM_SIZE, BUFFER_SIZE_INDEX_2K));
+      QUICPHDebug(QUICConnectionId(scid, scid_len), QUICConnectionId(dcid, dcid_len), "Unsupported version: 0x%x", version);
+      ssize_t written = quiche_negotiate_version(scid, scid_len, dcid, dcid_len, reinterpret_cast<uint8_t *>(udp_payload->end()),
+                                                 udp_payload->write_avail());
+      udp_payload->fill(written);
+      this->send_packet(udp_packet->getConnection(), udp_packet->from, udp_payload);
+      udp_packet->free();
+      return;
+    }
+
+    QUICConfig::scoped_config params;
+    if (params->stateless_retry() && token_len == 0) {
+      QUICConnectionId new_cid;
+      new_cid.randomize();
+      QUICRetryToken retry_token = {udp_packet->from, {dcid, static_cast<uint8_t>(dcid_len)}, new_cid};
+      Ptr<IOBufferBlock> udp_payload(new_IOBufferBlock());
+      udp_payload->alloc(iobuffer_size_to_index(DEFAULT_MAX_DATAGRAM_SIZE, BUFFER_SIZE_INDEX_2K));
+      ssize_t written =
+        quiche_retry(scid, scid_len, dcid, dcid_len, new_cid, new_cid.length(), retry_token.buf(), retry_token.length(), version,
+                     reinterpret_cast<uint8_t *>(udp_payload->end()), udp_payload->write_avail());
+      udp_payload->fill(written);
+      this->send_packet(udp_packet->getConnection(), udp_packet->from, udp_payload);
+
+      udp_packet->free();
+      return;
+    }
+
+    // Create a new connection
+    Connection con;
+    con.setRemote(&udp_packet->from.sa);
+
+    eth                           = eventProcessor.assign_thread(ET_NET);
+    QUICConnectionId original_cid = {dcid, static_cast<uint8_t>(dcid_len)};
+    QUICConnectionId peer_cid     = {scid, static_cast<uint8_t>(scid_len)};
+
+    if (is_debug_tag_set("quic_sec")) {
+      QUICPHDebug(peer_cid, original_cid, "client initial dcid=%s", original_cid.hex().c_str());
+    }
+
+    QUICRetryToken retry_token = {token, token_len};
+    if (params->stateless_retry() && !retry_token.is_valid(udp_packet->from)) {
+      fprintf(stderr, "invalid address validation token\n");
+      udp_packet->free();
+      return;
+    }
+
+    QUICConnectionId new_cid;
+    quiche_conn *quiche_con =
+      quiche_accept(new_cid, new_cid.length(), retry_token.original_dcid(), retry_token.original_dcid().length(),
+#ifdef HAVE_QUICHE_CONFIG_SET_ACTIVE_CONNECTION_ID_LIMIT
+                    &udp_packet->to.sa, udp_packet->to.isIp4() ? sizeof(udp_packet->to.sin) : sizeof(udp_packet->to.sin6),
+#endif
+                    &udp_packet->from.sa, udp_packet->from.isIp4() ? sizeof(udp_packet->from.sin) : sizeof(udp_packet->from.sin6),
+                    &this->_quiche_config);
+
+    if (params->qlog_dir() != nullptr) {
+      char qlog_filepath[PATH_MAX];
+      const uint8_t *quic_trace_id;
+      size_t quic_trace_id_len = 0;
+      quiche_conn_trace_id(quiche_con, &quic_trace_id, &quic_trace_id_len);
+      sprintf(qlog_filepath, "%s/%.*s.sqlog", Layout::get()->relative(params->qlog_dir()).c_str(),
+              static_cast<int>(quic_trace_id_len), quic_trace_id);
+      quiche_conn_set_qlog_path(quiche_con, qlog_filepath, "ats", "");
+    }
+
+    vc = static_cast<QUICNetVConnection *>(getNetProcessor()->allocate_vc(nullptr));
+    // TODO Extract OCID and RCID from the token
+    // vc->init(version, peer_cid, original_cid, ocid_in_retry_token, rcid_in_retry_token, udp_packet->getConnection(), this,
+    // &this->_ctable);
+    vc->init(version, peer_cid, new_cid, QUICConnectionId::ZERO(), QUICConnectionId::ZERO(), udp_packet->getConnection(),
+             quiche_con, this, &this->_ctable);
+    vc->id = net_next_connection_number();
+    vc->con.move(con);
+    vc->submit_time = Thread::get_hrtime();
+    vc->thread      = eth;
+    vc->mutex       = new_ProxyMutex();
+    vc->action_     = *this->action_;
+    vc->set_is_transparent(this->opt.f_inbound_transparent);
+    vc->set_context(NET_VCONNECTION_IN);
+    vc->options.ip_proto  = NetVCOptions::USE_UDP;
+    vc->options.ip_family = udp_packet->from.sa.sa_family;
+    eth->schedule_imm(vc, EVENT_NONE, nullptr);
+    qc = vc;
+  } else if (vc && vc->in_closed_queue) {
+    // TODO Send stateless reset
+    udp_packet->free();
+    return;
+  }
+  eth = vc->thread;
+
+  QUICPollEvent *qe = quicPollEventAllocator.alloc();
+  qe->init(qc, static_cast<UDPPacketInternal *>(udp_packet));
+  // Push the packet into QUICPollCont
+  get_QUICPollCont(eth)->inQueue.push(qe);
+  get_NetHandler(eth)->signalActivity();
+
+  return;
+}
+
+void
+QUICPacketHandlerOut::init(QUICNetVConnection *vc)
+{
+}
+
+Continuation *
+QUICPacketHandlerOut::_get_continuation()
+{
+  return this;
+}
+
+void
+QUICPacketHandlerOut::_recv_packet(int event, UDPPacket *udp_packet)
+{
+}
diff --git a/iocore/net/UnixUDPConnection.cc b/iocore/net/UnixUDPConnection.cc
index 57c8814f2..53fb1cdc2 100644
--- a/iocore/net/UnixUDPConnection.cc
+++ b/iocore/net/UnixUDPConnection.cc
@@ -98,11 +98,9 @@ UnixUDPConnection::callbackHandler(int event, void *data)
 }
 
 void
-UDPConnection::bindToThread(Continuation *c)
+UDPConnection::bindToThread(Continuation *c, EThread *t)
 {
   UnixUDPConnection *uc = (UnixUDPConnection *)this;
-  // add to new connections queue for EThread.
-  EThread *t = eventProcessor.assign_thread(ET_UDP);
   ink_assert(t);
   ink_assert(get_UDPNetHandler(t));
   uc->ethread = t;
diff --git a/iocore/net/UnixUDPNet.cc b/iocore/net/UnixUDPNet.cc
index b31417289..dd30f279e 100644
--- a/iocore/net/UnixUDPNet.cc
+++ b/iocore/net/UnixUDPNet.cc
@@ -806,6 +806,18 @@ UDPNetProcessor::UDPBind(Continuation *cont, sockaddr const *addr, int fd, int s
     goto Lerror;
   }
 
+  if (safe_setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, SOCKOPT_ON, sizeof(int)) < 0) {
+    Debug("udpnet", "setsockopt for SO_REUSEPORT failed");
+    goto Lerror;
+  }
+
+#ifdef SO_REUSEPORT_LB
+  if (safe_setsockopt(fd, SOL_SOCKET, SO_REUSEPORT_LB, SOCKOPT_ON, sizeof(int)) < 0) {
+    Debug("udpnet", "setsockopt for SO_REUSEPORT_LB failed");
+    goto Lerror;
+  }
+#endif
+
   if (need_bind && (socketManager.ink_bind(fd, addr, ats_ip_size(addr)) < 0)) {
     Debug("udpnet", "ink_bind failed");
     goto Lerror;
@@ -828,7 +840,7 @@ UDPNetProcessor::UDPBind(Continuation *cont, sockaddr const *addr, int fd, int s
 
   Debug("udpnet", "UDPNetProcessor::UDPBind: %p fd=%d", n, fd);
   n->setBinding(&myaddr.sa);
-  n->bindToThread(cont);
+  n->bindToThread(cont, cont->getThreadAffinity());
 
   pc = get_UDPPollCont(n->ethread);
   pd = pc->pollDescriptor;
diff --git a/iocore/net/quic/Makefile.am b/iocore/net/quic/Makefile.am
index ab32d2540..69da6df2f 100644
--- a/iocore/net/quic/Makefile.am
+++ b/iocore/net/quic/Makefile.am
@@ -34,6 +34,8 @@ AM_CPPFLAGS += \
 
 noinst_LIBRARIES = libquic.a
 
+if USE_QUICHE
+else
 if OPENSSL_IS_BORINGSSL
 QUICPHProtector_impl = QUICPacketHeaderProtector_boringssl.cc
 QUICPPProtector_impl = QUICPacketPayloadProtector_boringssl.cc
@@ -47,7 +49,26 @@ QUICKeyGenerator_impl = QUICKeyGenerator_openssl.cc
 endif
 
 QLog_impl = qlog/QLogEvent.cc qlog/QLogFrame.cc qlog/QLog.cc
+endif
+
 
+if USE_QUICHE
+libquic_a_SOURCES = \
+  QUICApplication.cc \
+  QUICApplicationMap.cc \
+  QUICConfig.cc \
+  QUICContext.cc \
+  QUICConnectionTable.cc \
+  QUICGlobals.cc \
+  QUICTypes.cc \
+  QUICIntUtil.cc \
+  QUICStream.cc \
+  QUICStream_quiche.cc \
+  QUICStreamManager.cc \
+  QUICStreamManager_quiche.cc \
+  QUICStreamAdapter.cc \
+  QUICStreamVCAdapter.cc
+else
 libquic_a_SOURCES = \
   QUICGlobals.cc \
   QUICTypes.cc \
@@ -59,12 +80,14 @@ libquic_a_SOURCES = \
   QUICVersionNegotiator.cc \
   QUICLossDetector.cc \
   QUICStreamManager.cc \
+  QUICStreamManager_native.cc \
   QUICNewRenoCongestionController.cc \
   QUICFlowController.cc \
   QUICStreamState.cc \
   QUICStreamAdapter.cc \
   QUICStreamVCAdapter.cc \
   QUICStream.cc \
+  QUICStreamBase.cc \
   QUICHandshake.cc \
   QUICPacketHeaderProtector.cc \
   $(QUICPHProtector_impl) \
@@ -103,10 +126,13 @@ libquic_a_SOURCES = \
   QUICContext.cc \
   QUICTokenCreator.cc \
   $(QLog_impl)
+endif
 
 #
 # Check Programs
 #
+if USE_QUICHE
+else
 check_PROGRAMS = \
   test_QUICAckFrameCreator \
   test_QUICAltConnectionManager \
@@ -132,6 +158,7 @@ check_PROGRAMS = \
   test_QUICFrameRetransmitter \
   test_QUICAddrVerifyState \
   test_QUICPinger
+endif
 
 TESTS = $(check_PROGRAMS)
 
diff --git a/iocore/net/quic/Mock.h b/iocore/net/quic/Mock.h
index 17cbf40c6..442c8eab0 100644
--- a/iocore/net/quic/Mock.h
+++ b/iocore/net/quic/Mock.h
@@ -27,12 +27,14 @@
 
 #include "QUICApplication.h"
 #include "QUICStreamManager.h"
+#include "QUICStreamManager_native.h"
 #include "QUICLossDetector.h"
 #include "QUICPacketProtectionKeyInfo.h"
 #include "QUICPinger.h"
 #include "QUICPadder.h"
 #include "QUICEvents.h"
 #include "QUICPacketProtectionKeyInfo.h"
+#include "QUICPathManager.h"
 #include "QUICPinger.h"
 #include "QUICPadder.h"
 #include "QUICHandshakeProtocol.h"
@@ -238,10 +240,10 @@ class MockQUICConnectionInfoProvider : public QUICConnectionInfoProvider
   }
 };
 
-class MockQUICStreamManager : public QUICStreamManager
+class MockQUICStreamManager : public QUICStreamManagerImpl
 {
 public:
-  MockQUICStreamManager(QUICContext *context) : QUICStreamManager(context, nullptr) {}
+  MockQUICStreamManager(QUICContext *context) : QUICStreamManagerImpl(context, nullptr) {}
 
   // Override
   virtual QUICConnectionErrorUPtr
diff --git a/iocore/net/quic/QUICBidirectionalStream.cc b/iocore/net/quic/QUICBidirectionalStream.cc
index e5653e7d3..a8dccbf20 100644
--- a/iocore/net/quic/QUICBidirectionalStream.cc
+++ b/iocore/net/quic/QUICBidirectionalStream.cc
@@ -29,7 +29,7 @@
 //
 QUICBidirectionalStream::QUICBidirectionalStream(QUICRTTProvider *rtt_provider, QUICConnectionInfoProvider *cinfo, QUICStreamId sid,
                                                  uint64_t recv_max_stream_data, uint64_t send_max_stream_data)
-  : QUICStream(cinfo, sid),
+  : QUICStreamBase(cinfo, sid),
     _remote_flow_controller(send_max_stream_data, _id),
     _local_flow_controller(rtt_provider, recv_max_stream_data, _id),
     _flow_control_buffer_size(recv_max_stream_data),
diff --git a/iocore/net/quic/QUICBidirectionalStream.h b/iocore/net/quic/QUICBidirectionalStream.h
index 6f0118bcd..49b22c7ae 100644
--- a/iocore/net/quic/QUICBidirectionalStream.h
+++ b/iocore/net/quic/QUICBidirectionalStream.h
@@ -24,14 +24,18 @@
 #pragma once
 
 #include "QUICStream.h"
+#include "QUICStream_native.h"
 
-class QUICBidirectionalStream : public QUICStream, public QUICTransferProgressProvider
+class QUICBidirectionalStream : public QUICStreamBase, public QUICTransferProgressProvider
 {
 public:
   QUICBidirectionalStream(QUICRTTProvider *rtt_provider, QUICConnectionInfoProvider *cinfo, QUICStreamId sid,
                           uint64_t recv_max_stream_data, uint64_t send_max_stream_data);
   QUICBidirectionalStream()
-    : QUICStream(), _remote_flow_controller(0, 0), _local_flow_controller(nullptr, 0, 0), _state(nullptr, nullptr, nullptr, nullptr)
+    : QUICStreamBase(),
+      _remote_flow_controller(0, 0),
+      _local_flow_controller(nullptr, 0, 0),
+      _state(nullptr, nullptr, nullptr, nullptr)
   {
   }
 
diff --git a/iocore/net/quic/QUICContext.cc b/iocore/net/quic/QUICContext.cc
index b065f5610..7de91eab9 100644
--- a/iocore/net/quic/QUICContext.cc
+++ b/iocore/net/quic/QUICContext.cc
@@ -94,16 +94,20 @@ private:
   const QUICConfigParams *_params;
 };
 
+#if HAVE_QUICHE_H
+QUICContext::QUICContext(QUICConnectionInfoProvider *info) : _connection_info(info) {}
+#else
 QUICContext::QUICContext(QUICRTTProvider *rtt, QUICConnectionInfoProvider *info, QUICPacketProtectionKeyInfoProvider *key_info,
                          QUICPathManager *path_manager)
-  : _key_info(key_info),
-    _connection_info(info),
+  : _connection_info(info),
+    _key_info(key_info),
     _rtt_provider(rtt),
     _path_manager(path_manager),
     _ld_config(std::make_unique<QUICLDConfigQCP>(_config)),
     _cc_config(std::make_unique<QUICCCConfigQCP>(_config))
 {
 }
+#endif
 
 QUICConnectionInfoProvider *
 QUICContext::connection_info() const
@@ -117,6 +121,8 @@ QUICContext::config() const
   return _config;
 }
 
+#if HAVE_QUICHE_H
+#else
 QUICPacketProtectionKeyInfoProvider *
 QUICContext::key_info() const
 {
@@ -146,3 +152,4 @@ QUICContext::path_manager() const
 {
   return _path_manager;
 }
+#endif
diff --git a/iocore/net/quic/QUICContext.h b/iocore/net/quic/QUICContext.h
index 0469b4cda..81eb761da 100644
--- a/iocore/net/quic/QUICContext.h
+++ b/iocore/net/quic/QUICContext.h
@@ -72,17 +72,24 @@ public:
 class QUICContext
 {
 public:
+#if HAVE_QUICHE_H
+  QUICContext(QUICConnectionInfoProvider *info);
+#else
   QUICContext(QUICRTTProvider *rtt, QUICConnectionInfoProvider *info, QUICPacketProtectionKeyInfoProvider *key_info,
               QUICPathManager *path_manager);
+#endif
 
   virtual ~QUICContext(){};
   virtual QUICConnectionInfoProvider *connection_info() const;
   virtual QUICConfig::scoped_config config() const;
+#if HAVE_QUICHE_H
+#else
   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;
+#endif
 
   // register a callback which will be called when specified event happen.
   void
@@ -183,13 +190,16 @@ protected:
 
 private:
   QUICConfig::scoped_config _config;
+  QUICConnectionInfoProvider *_connection_info = nullptr;
+#if HAVE_QUICHE_H
+#else
   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;
+#endif
 
   std::vector<std::shared_ptr<QUICCallback>> _callbacks;
 };
diff --git a/iocore/net/quic/QUICCryptoStream.h b/iocore/net/quic/QUICCryptoStream.h
index 42f99db74..781d38f38 100644
--- a/iocore/net/quic/QUICCryptoStream.h
+++ b/iocore/net/quic/QUICCryptoStream.h
@@ -24,6 +24,7 @@
 #pragma once
 
 #include "QUICStream.h"
+#include "QUICStream_native.h"
 
 /**
  * @brief QUIC Crypto stream
@@ -33,7 +34,7 @@
  * - no flow control
  * - no state (never closed)
  */
-class QUICCryptoStream : public QUICStream
+class QUICCryptoStream : public QUICStreamBase
 {
 public:
   QUICCryptoStream();
@@ -42,7 +43,6 @@ public:
   int state_stream_open(int event, void *data);
 
   const QUICConnectionInfoProvider *info() const;
-  QUICOffset final_offset() const;
   void reset_send_offset();
   void reset_recv_offset();
 
diff --git a/iocore/net/quic/QUICGlobals.cc b/iocore/net/quic/QUICGlobals.cc
index 6dcd8f09e..3cb4ea594 100644
--- a/iocore/net/quic/QUICGlobals.cc
+++ b/iocore/net/quic/QUICGlobals.cc
@@ -53,6 +53,8 @@ QUIC::init()
 int
 QUIC::ssl_client_new_session(SSL *ssl, SSL_SESSION *session)
 {
+#if HAVE_QUICHE_H
+#else
   QUICTLS *qtls            = static_cast<QUICTLS *>(SSL_get_ex_data(ssl, QUIC::ssl_quic_tls_index));
   const char *session_file = qtls->session_file();
   auto file                = BIO_new_file(session_file, "w");
@@ -64,6 +66,7 @@ QUIC::ssl_client_new_session(SSL *ssl, SSL_SESSION *session)
 
   PEM_write_bio_SSL_SESSION(file, session);
   BIO_free(file);
+#endif
   return 0;
 }
 
diff --git a/iocore/net/quic/QUICHandshake.cc b/iocore/net/quic/QUICHandshake.cc
index f64f22213..cff8086ee 100644
--- a/iocore/net/quic/QUICHandshake.cc
+++ b/iocore/net/quic/QUICHandshake.cc
@@ -259,14 +259,12 @@ QUICHandshake::_check_remote_transport_parameters(std::shared_ptr<const QUICTran
   }
 
   // Check if CIDs in TP match with the ones in packets
-  if (this->negotiated_version() == QUIC_SUPPORTED_VERSIONS[0]) { // draft-28
-    uint16_t cid_buf_len;
-    const uint8_t *cid_buf = tp->getAsBytes(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID, cid_buf_len);
-    QUICConnectionId cid_in_tp(cid_buf, cid_buf_len);
-    if (cid_in_tp != this->_initial_source_cid_received) {
-      this->_abort_handshake(QUICTransErrorCode::PROTOCOL_VIOLATION);
-      return false;
-    }
+  uint16_t cid_buf_len;
+  const uint8_t *cid_buf = tp->getAsBytes(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID, cid_buf_len);
+  QUICConnectionId cid_in_tp(cid_buf, cid_buf_len);
+  if (cid_in_tp != this->_initial_source_cid_received) {
+    this->_abort_handshake(QUICTransErrorCode::PROTOCOL_VIOLATION);
+    return false;
   }
 
   this->_remote_transport_parameters = tp;
@@ -285,23 +283,21 @@ QUICHandshake::_check_remote_transport_parameters(std::shared_ptr<const QUICTran
   }
 
   // Check if CIDs in TP match with the ones in packets
-  if (this->negotiated_version() == QUIC_SUPPORTED_VERSIONS[0]) { // draft-28
-    uint16_t cid_buf_len;
-    const uint8_t *cid_buf = tp->getAsBytes(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID, cid_buf_len);
+  uint16_t cid_buf_len;
+  const uint8_t *cid_buf = tp->getAsBytes(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID, cid_buf_len);
+  QUICConnectionId cid_in_tp(cid_buf, cid_buf_len);
+  if (cid_in_tp != this->_initial_source_cid_received) {
+    this->_abort_handshake(QUICTransErrorCode::PROTOCOL_VIOLATION);
+    return false;
+  }
+
+  if (!this->_retry_source_cid_received.is_zero()) {
+    cid_buf = tp->getAsBytes(QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID, cid_buf_len);
     QUICConnectionId cid_in_tp(cid_buf, cid_buf_len);
-    if (cid_in_tp != this->_initial_source_cid_received) {
+    if (cid_in_tp != this->_retry_source_cid_received) {
       this->_abort_handshake(QUICTransErrorCode::PROTOCOL_VIOLATION);
       return false;
     }
-
-    if (!this->_retry_source_cid_received.is_zero()) {
-      cid_buf = tp->getAsBytes(QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID, cid_buf_len);
-      QUICConnectionId cid_in_tp(cid_buf, cid_buf_len);
-      if (cid_in_tp != this->_retry_source_cid_received) {
-        this->_abort_handshake(QUICTransErrorCode::PROTOCOL_VIOLATION);
-        return false;
-      }
-    }
   }
 
   this->_remote_transport_parameters = tp;
@@ -439,15 +435,11 @@ QUICHandshake::_load_local_server_transport_parameters(const QUICTPConfig &tp_co
     tp->set(QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID, this->_qc->retry_source_connection_id(),
             this->_qc->retry_source_connection_id().length());
   } else {
-    if (this->negotiated_version() == QUIC_SUPPORTED_VERSIONS[0]) { // draft-28
-      tp->set(QUICTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID, this->_qc->original_connection_id(),
-              this->_qc->original_connection_id().length());
-    }
-  }
-  if (this->negotiated_version() == QUIC_SUPPORTED_VERSIONS[0]) { // draft-28
-    tp->set(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID, this->_qc->initial_source_connection_id(),
-            this->_qc->initial_source_connection_id().length());
+    tp->set(QUICTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID, this->_qc->original_connection_id(),
+            this->_qc->original_connection_id().length());
   }
+  tp->set(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID, this->_qc->initial_source_connection_id(),
+          this->_qc->initial_source_connection_id().length());
 
   // MAYs
   if (tp_config.initial_max_data() != 0) {
diff --git a/iocore/net/quic/QUICKeyGenerator.cc b/iocore/net/quic/QUICKeyGenerator.cc
index 09ed9eda3..50c2fa5f3 100644
--- a/iocore/net/quic/QUICKeyGenerator.cc
+++ b/iocore/net/quic/QUICKeyGenerator.cc
@@ -34,10 +34,10 @@
 using namespace std::literals;
 
 constexpr static uint8_t QUIC_VERSION_1_SALT[] = {
-  0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99,
+  0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a,
 };
-constexpr static uint8_t QUIC_VERSION_1_D27_SALT[] = {
-  0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, 0xd2, 0x43, 0x2b, 0xb4, 0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02,
+constexpr static uint8_t QUIC_VERSION_1_D29_SALT[] = {
+  0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99,
 };
 constexpr static std::string_view LABEL_FOR_CLIENT_INITIAL_SECRET("client in"sv);
 constexpr static std::string_view LABEL_FOR_SERVER_INITIAL_SECRET("server in"sv);
@@ -117,13 +117,13 @@ QUICKeyGenerator::_generate_initial_secret(QUICVersion version, uint8_t *out, si
   // TODO: do not extract initial secret twice
   QUICTypeUtil::write_QUICConnectionId(cid, client_connection_id, &cid_len);
   switch (version) {
-  case 0xff00001d: // Draft-29
+  case 0x00000001: // Version 1
     salt     = QUIC_VERSION_1_SALT;
     salt_len = sizeof(QUIC_VERSION_1_SALT);
     break;
-  case 0xff00001b: // Draft-27
-    salt     = QUIC_VERSION_1_D27_SALT;
-    salt_len = sizeof(QUIC_VERSION_1_D27_SALT);
+  case 0xff00001d: // Draft-29
+    salt     = QUIC_VERSION_1_D29_SALT;
+    salt_len = sizeof(QUIC_VERSION_1_D29_SALT);
     break;
   default:
     salt     = QUIC_VERSION_1_SALT;
diff --git a/iocore/net/quic/QUICRetryIntegrityTag.cc b/iocore/net/quic/QUICRetryIntegrityTag.cc
index 117dff696..e8988bac0 100644
--- a/iocore/net/quic/QUICRetryIntegrityTag.cc
+++ b/iocore/net/quic/QUICRetryIntegrityTag.cc
@@ -41,13 +41,13 @@ QUICRetryIntegrityTag::compute(uint8_t *out, QUICVersion version, QUICConnection
   const uint8_t *key;
   const uint8_t *nonce;
   switch (version) {
-  case 0xff00001d: // Draft-29
+  case 0x00000001: // Version 1
     key   = KEY_FOR_RETRY_INTEGRITY_TAG;
     nonce = NONCE_FOR_RETRY_INTEGRITY_TAG;
     break;
-  case 0xff00001b: // Draft-27
-    key   = KEY_FOR_RETRY_INTEGRITY_TAG_D27;
-    nonce = NONCE_FOR_RETRY_INTEGRITY_TAG_D27;
+  case 0xff00001d: // Draft-29
+    key   = KEY_FOR_RETRY_INTEGRITY_TAG_D29;
+    nonce = NONCE_FOR_RETRY_INTEGRITY_TAG_D29;
     break;
   default:
     key   = KEY_FOR_RETRY_INTEGRITY_TAG;
diff --git a/iocore/net/quic/QUICRetryIntegrityTag.h b/iocore/net/quic/QUICRetryIntegrityTag.h
index 081bd59da..3649fff1d 100644
--- a/iocore/net/quic/QUICRetryIntegrityTag.h
+++ b/iocore/net/quic/QUICRetryIntegrityTag.h
@@ -33,13 +33,14 @@ public:
                       Ptr<IOBufferBlock> payload);
 
 private:
-  static constexpr uint8_t KEY_FOR_RETRY_INTEGRITY_TAG[]   = {0xcc, 0xce, 0x18, 0x7e, 0xd0, 0x9a, 0x09, 0xd0,
-                                                            0x57, 0x28, 0x15, 0x5a, 0x6c, 0xb9, 0x6b, 0xe1};
-  static constexpr uint8_t NONCE_FOR_RETRY_INTEGRITY_TAG[] = {0xe5, 0x49, 0x30, 0xf9, 0x7f, 0x21,
-                                                              0x36, 0xf0, 0x53, 0x0a, 0x8c, 0x1c};
-  // For draft 27
-  static constexpr uint8_t KEY_FOR_RETRY_INTEGRITY_TAG_D27[]   = {0x4d, 0x32, 0xec, 0xdb, 0x2a, 0x21, 0x33, 0xc8,
-                                                                0x41, 0xe4, 0x04, 0x3d, 0xf2, 0x7d, 0x44, 0x30};
-  static constexpr uint8_t NONCE_FOR_RETRY_INTEGRITY_TAG_D27[] = {0x4d, 0x16, 0x11, 0xd0, 0x55, 0x13,
-                                                                  0xa5, 0x52, 0xc5, 0x87, 0xd5, 0x75};
+  // For version 1
+  static constexpr uint8_t KEY_FOR_RETRY_INTEGRITY_TAG[]   = {0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a,
+                                                            0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68, 0xc8, 0x4e};
+  static constexpr uint8_t NONCE_FOR_RETRY_INTEGRITY_TAG[] = {0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63,
+                                                              0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb};
+  // For draft 29
+  static constexpr uint8_t KEY_FOR_RETRY_INTEGRITY_TAG_D29[]   = {0xcc, 0xce, 0x18, 0x7e, 0xd0, 0x9a, 0x09, 0xd0,
+                                                                0x57, 0x28, 0x15, 0x5a, 0x6c, 0xb9, 0x6b, 0xe1};
+  static constexpr uint8_t NONCE_FOR_RETRY_INTEGRITY_TAG_D29[] = {0xe5, 0x49, 0x30, 0xf9, 0x7f, 0x21,
+                                                                  0x36, 0xf0, 0x53, 0x0a, 0x8c, 0x1c};
 };
diff --git a/iocore/net/quic/QUICStream.cc b/iocore/net/quic/QUICStream.cc
index 932d1c140..8753f9dfd 100644
--- a/iocore/net/quic/QUICStream.cc
+++ b/iocore/net/quic/QUICStream.cc
@@ -37,16 +37,16 @@ QUICStream::id() const
   return this->_id;
 }
 
-QUICStreamDirection
-QUICStream::direction() const
+const QUICConnectionInfoProvider *
+QUICStream::connection_info()
 {
-  return QUICTypeUtil::detect_stream_direction(this->_id, this->_connection_info->direction());
+  return this->_connection_info;
 }
 
-const QUICConnectionInfoProvider *
-QUICStream::connection_info() const
+QUICStreamDirection
+QUICStream::direction() const
 {
-  return this->_connection_info;
+  return QUICTypeUtil::detect_stream_direction(this->_id, this->_connection_info->direction());
 }
 
 bool
@@ -55,153 +55,9 @@ QUICStream::is_bidirectional() const
   return ((this->_id & 0x03) < 0x02);
 }
 
-QUICOffset
-QUICStream::final_offset() const
-{
-  // TODO Return final offset
-  return 0;
-}
-
 void
 QUICStream::set_io_adapter(QUICStreamAdapter *adapter)
 {
   this->_adapter = adapter;
   this->_on_adapter_updated();
 }
-
-QUICOffset
-QUICStream::reordered_bytes() const
-{
-  return this->_reordered_bytes;
-}
-
-QUICConnectionErrorUPtr
-QUICStream::recv(const QUICStreamFrame &frame)
-{
-  return nullptr;
-}
-
-QUICConnectionErrorUPtr
-QUICStream::recv(const QUICMaxStreamDataFrame &frame)
-{
-  return nullptr;
-}
-
-QUICConnectionErrorUPtr
-QUICStream::recv(const QUICStreamDataBlockedFrame &frame)
-{
-  return nullptr;
-}
-
-QUICConnectionErrorUPtr
-QUICStream::recv(const QUICStopSendingFrame &frame)
-{
-  return nullptr;
-}
-
-QUICConnectionErrorUPtr
-QUICStream::recv(const QUICRstStreamFrame &frame)
-{
-  return nullptr;
-}
-
-QUICConnectionErrorUPtr
-QUICStream::recv(const QUICCryptoFrame &frame)
-{
-  return nullptr;
-}
-
-void
-QUICStream::_records_stream_frame(QUICEncryptionLevel level, const QUICStreamFrame &frame)
-{
-  QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
-  info->type                    = frame.type();
-  info->level                   = level;
-  StreamFrameInfo *frame_info   = reinterpret_cast<StreamFrameInfo *>(info->data);
-  frame_info->stream_id         = frame.stream_id();
-  frame_info->offset            = frame.offset();
-  frame_info->has_fin           = frame.has_fin_flag();
-  frame_info->block             = frame.data();
-  this->_records_frame(frame.id(), std::move(info));
-}
-
-void
-QUICStream::_records_rst_stream_frame(QUICEncryptionLevel level, const QUICRstStreamFrame &frame)
-{
-  QUICFrameInformationUPtr info  = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
-  info->type                     = frame.type();
-  info->level                    = level;
-  RstStreamFrameInfo *frame_info = reinterpret_cast<RstStreamFrameInfo *>(info->data);
-  frame_info->error_code         = frame.error_code();
-  frame_info->final_offset       = frame.final_offset();
-  this->_records_frame(frame.id(), std::move(info));
-}
-
-void
-QUICStream::_records_stop_sending_frame(QUICEncryptionLevel level, const QUICStopSendingFrame &frame)
-{
-  QUICFrameInformationUPtr info    = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
-  info->type                       = frame.type();
-  info->level                      = level;
-  StopSendingFrameInfo *frame_info = reinterpret_cast<StopSendingFrameInfo *>(info->data);
-  frame_info->error_code           = frame.error_code();
-  this->_records_frame(frame.id(), std::move(info));
-}
-
-void
-QUICStream::_records_crypto_frame(QUICEncryptionLevel level, const QUICCryptoFrame &frame)
-{
-  QUICFrameInformationUPtr info      = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
-  info->type                         = QUICFrameType::CRYPTO;
-  info->level                        = level;
-  CryptoFrameInfo *crypto_frame_info = reinterpret_cast<CryptoFrameInfo *>(info->data);
-  crypto_frame_info->offset          = frame.offset();
-  crypto_frame_info->block           = frame.data();
-  this->_records_frame(frame.id(), std::move(info));
-}
-
-void
-QUICStream::set_state_listener(QUICStreamStateListener *listener)
-{
-  this->_state_listener = listener;
-}
-
-void
-QUICStream::_notify_state_change()
-{
-  if (this->_state_listener) {
-    // TODO Check own state and call an appropriate callback function
-  }
-}
-
-void
-QUICStream::reset(QUICStreamErrorUPtr error)
-{
-}
-
-void
-QUICStream::stop_sending(QUICStreamErrorUPtr error)
-{
-}
-
-QUICOffset
-QUICStream::largest_offset_received() const
-{
-  return 0;
-}
-
-QUICOffset
-QUICStream::largest_offset_sent() const
-{
-  return 0;
-}
-
-void
-QUICStream::on_eos()
-{
-}
-
-void
-QUICStream::on_read()
-{
-}
diff --git a/iocore/net/quic/QUICStream.h b/iocore/net/quic/QUICStream.h
index d6cdf06ab..81aea2a1b 100644
--- a/iocore/net/quic/QUICStream.h
+++ b/iocore/net/quic/QUICStream.h
@@ -44,7 +44,7 @@ class QUICStreamStateListener;
  * @brief QUIC Stream
  * TODO: This is similar to Http2Stream. Need to think some integration.
  */
-class QUICStream : public QUICFrameGenerator, public QUICFrameRetransmitter
+class QUICStream
 {
 public:
   QUICStream() {}
@@ -52,10 +52,20 @@ public:
   virtual ~QUICStream();
 
   QUICStreamId id() const;
+  const QUICConnectionInfoProvider *connection_info();
   QUICStreamDirection direction() const;
-  const QUICConnectionInfoProvider *connection_info() const;
   bool is_bidirectional() const;
-  QUICOffset final_offset() const;
+
+  virtual QUICOffset final_offset() const = 0;
+
+  virtual void stop_sending(QUICStreamErrorUPtr error) = 0;
+  virtual void reset(QUICStreamErrorUPtr error)        = 0;
+
+  /*
+   * QUICApplication need to call one of these functions when it process VC_EVENT_*
+   */
+  virtual void on_read() = 0;
+  virtual void on_eos()  = 0;
 
   /**
    * Set an adapter to read/write data from/to this stream
@@ -64,48 +74,12 @@ public:
    * to access data in the  way the applications wants.
    */
   void set_io_adapter(QUICStreamAdapter *adapter);
-
-  /*
-   * QUICApplication need to call one of these functions when it process VC_EVENT_*
-   */
-  virtual void on_read();
-  virtual void on_eos();
-
-  virtual QUICConnectionErrorUPtr recv(const QUICStreamFrame &frame);
-  virtual QUICConnectionErrorUPtr recv(const QUICMaxStreamDataFrame &frame);
-  virtual QUICConnectionErrorUPtr recv(const QUICStreamDataBlockedFrame &frame);
-  virtual QUICConnectionErrorUPtr recv(const QUICStopSendingFrame &frame);
-  virtual QUICConnectionErrorUPtr recv(const QUICRstStreamFrame &frame);
-  virtual QUICConnectionErrorUPtr recv(const QUICCryptoFrame &frame);
-
-  QUICOffset reordered_bytes() const;
-  virtual QUICOffset largest_offset_received() const;
-  virtual QUICOffset largest_offset_sent() const;
-
-  virtual void stop_sending(QUICStreamErrorUPtr error);
-  virtual void reset(QUICStreamErrorUPtr error);
-
-  void set_state_listener(QUICStreamStateListener *listener);
-
-  LINK(QUICStream, link);
+  virtual void _on_adapter_updated(){};
 
 protected:
   QUICConnectionInfoProvider *_connection_info = nullptr;
   QUICStreamId _id                             = 0;
-  QUICOffset _send_offset                      = 0;
-  QUICOffset _reordered_bytes                  = 0;
-
-  QUICStreamAdapter *_adapter              = nullptr;
-  QUICStreamStateListener *_state_listener = nullptr;
-
-  virtual void _on_adapter_updated(){};
-
-  void _notify_state_change();
-
-  void _records_rst_stream_frame(QUICEncryptionLevel level, const QUICRstStreamFrame &frame);
-  void _records_stream_frame(QUICEncryptionLevel level, const QUICStreamFrame &frame);
-  void _records_stop_sending_frame(QUICEncryptionLevel level, const QUICStopSendingFrame &frame);
-  void _records_crypto_frame(QUICEncryptionLevel level, const QUICCryptoFrame &frame);
+  QUICStreamAdapter *_adapter                  = nullptr;
 };
 
 class QUICStreamStateListener
diff --git a/iocore/net/quic/QUICStream.cc b/iocore/net/quic/QUICStreamBase.cc
similarity index 65%
copy from iocore/net/quic/QUICStream.cc
copy to iocore/net/quic/QUICStreamBase.cc
index 932d1c140..1fc3dc093 100644
--- a/iocore/net/quic/QUICStream.cc
+++ b/iocore/net/quic/QUICStreamBase.cc
@@ -22,97 +22,59 @@
  */
 
 #include "QUICStream.h"
-
-#include "QUICStreamManager.h"
-
-constexpr uint32_t MAX_STREAM_FRAME_OVERHEAD = 24;
-
-QUICStream::QUICStream(QUICConnectionInfoProvider *cinfo, QUICStreamId sid) : _connection_info(cinfo), _id(sid) {}
-
-QUICStream::~QUICStream() {}
-
-QUICStreamId
-QUICStream::id() const
-{
-  return this->_id;
-}
-
-QUICStreamDirection
-QUICStream::direction() const
-{
-  return QUICTypeUtil::detect_stream_direction(this->_id, this->_connection_info->direction());
-}
-
-const QUICConnectionInfoProvider *
-QUICStream::connection_info() const
-{
-  return this->_connection_info;
-}
-
-bool
-QUICStream::is_bidirectional() const
-{
-  return ((this->_id & 0x03) < 0x02);
-}
+#include "QUICStream_native.h"
 
 QUICOffset
-QUICStream::final_offset() const
+QUICStreamBase::final_offset() const
 {
   // TODO Return final offset
   return 0;
 }
 
-void
-QUICStream::set_io_adapter(QUICStreamAdapter *adapter)
-{
-  this->_adapter = adapter;
-  this->_on_adapter_updated();
-}
-
 QUICOffset
-QUICStream::reordered_bytes() const
+QUICStreamBase::reordered_bytes() const
 {
   return this->_reordered_bytes;
 }
 
 QUICConnectionErrorUPtr
-QUICStream::recv(const QUICStreamFrame &frame)
+QUICStreamBase::recv(const QUICStreamFrame &frame)
 {
   return nullptr;
 }
 
 QUICConnectionErrorUPtr
-QUICStream::recv(const QUICMaxStreamDataFrame &frame)
+QUICStreamBase::recv(const QUICMaxStreamDataFrame &frame)
 {
   return nullptr;
 }
 
 QUICConnectionErrorUPtr
-QUICStream::recv(const QUICStreamDataBlockedFrame &frame)
+QUICStreamBase::recv(const QUICStreamDataBlockedFrame &frame)
 {
   return nullptr;
 }
 
 QUICConnectionErrorUPtr
-QUICStream::recv(const QUICStopSendingFrame &frame)
+QUICStreamBase::recv(const QUICStopSendingFrame &frame)
 {
   return nullptr;
 }
 
 QUICConnectionErrorUPtr
-QUICStream::recv(const QUICRstStreamFrame &frame)
+QUICStreamBase::recv(const QUICRstStreamFrame &frame)
 {
   return nullptr;
 }
 
 QUICConnectionErrorUPtr
-QUICStream::recv(const QUICCryptoFrame &frame)
+QUICStreamBase::recv(const QUICCryptoFrame &frame)
 {
   return nullptr;
 }
 
 void
-QUICStream::_records_stream_frame(QUICEncryptionLevel level, const QUICStreamFrame &frame)
+QUICStreamBase::_records_stream_frame(QUICEncryptionLevel level, const QUICStreamFrame &frame)
 {
   QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
   info->type                    = frame.type();
@@ -126,7 +88,7 @@ QUICStream::_records_stream_frame(QUICEncryptionLevel level, const QUICStreamFra
 }
 
 void
-QUICStream::_records_rst_stream_frame(QUICEncryptionLevel level, const QUICRstStreamFrame &frame)
+QUICStreamBase::_records_rst_stream_frame(QUICEncryptionLevel level, const QUICRstStreamFrame &frame)
 {
   QUICFrameInformationUPtr info  = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
   info->type                     = frame.type();
@@ -138,7 +100,7 @@ QUICStream::_records_rst_stream_frame(QUICEncryptionLevel level, const QUICRstSt
 }
 
 void
-QUICStream::_records_stop_sending_frame(QUICEncryptionLevel level, const QUICStopSendingFrame &frame)
+QUICStreamBase::_records_stop_sending_frame(QUICEncryptionLevel level, const QUICStopSendingFrame &frame)
 {
   QUICFrameInformationUPtr info    = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
   info->type                       = frame.type();
@@ -149,7 +111,7 @@ QUICStream::_records_stop_sending_frame(QUICEncryptionLevel level, const QUICSto
 }
 
 void
-QUICStream::_records_crypto_frame(QUICEncryptionLevel level, const QUICCryptoFrame &frame)
+QUICStreamBase::_records_crypto_frame(QUICEncryptionLevel level, const QUICCryptoFrame &frame)
 {
   QUICFrameInformationUPtr info      = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
   info->type                         = QUICFrameType::CRYPTO;
@@ -161,13 +123,13 @@ QUICStream::_records_crypto_frame(QUICEncryptionLevel level, const QUICCryptoFra
 }
 
 void
-QUICStream::set_state_listener(QUICStreamStateListener *listener)
+QUICStreamBase::set_state_listener(QUICStreamStateListener *listener)
 {
   this->_state_listener = listener;
 }
 
 void
-QUICStream::_notify_state_change()
+QUICStreamBase::_notify_state_change()
 {
   if (this->_state_listener) {
     // TODO Check own state and call an appropriate callback function
@@ -175,33 +137,33 @@ QUICStream::_notify_state_change()
 }
 
 void
-QUICStream::reset(QUICStreamErrorUPtr error)
+QUICStreamBase::reset(QUICStreamErrorUPtr error)
 {
 }
 
 void
-QUICStream::stop_sending(QUICStreamErrorUPtr error)
+QUICStreamBase::stop_sending(QUICStreamErrorUPtr error)
 {
 }
 
 QUICOffset
-QUICStream::largest_offset_received() const
+QUICStreamBase::largest_offset_received() const
 {
   return 0;
 }
 
 QUICOffset
-QUICStream::largest_offset_sent() const
+QUICStreamBase::largest_offset_sent() const
 {
   return 0;
 }
 
 void
-QUICStream::on_eos()
+QUICStreamBase::on_eos()
 {
 }
 
 void
-QUICStream::on_read()
+QUICStreamBase::on_read()
 {
 }
diff --git a/iocore/net/quic/QUICStreamFactory.cc b/iocore/net/quic/QUICStreamFactory.cc
index 1f09c7521..398c63772 100644
--- a/iocore/net/quic/QUICStreamFactory.cc
+++ b/iocore/net/quic/QUICStreamFactory.cc
@@ -26,10 +26,10 @@
 #include "QUICUnidirectionalStream.h"
 #include "QUICStreamFactory.h"
 
-QUICStream *
+QUICStreamBase *
 QUICStreamFactory::create(QUICStreamId sid, uint64_t local_max_stream_data, uint64_t remote_max_stream_data)
 {
-  QUICStream *stream = nullptr;
+  QUICStreamBase *stream = nullptr;
   switch (QUICTypeUtil::detect_stream_direction(sid, this->_info->direction())) {
   case QUICStreamDirection::BIDIRECTIONAL:
     stream = new QUICBidirectionalStream(this->_rtt_provider, this->_info, sid, local_max_stream_data, remote_max_stream_data);
diff --git a/iocore/net/quic/QUICStreamFactory.h b/iocore/net/quic/QUICStreamFactory.h
index 43690edbd..9898f5a5d 100644
--- a/iocore/net/quic/QUICStreamFactory.h
+++ b/iocore/net/quic/QUICStreamFactory.h
@@ -35,7 +35,7 @@ public:
   ~QUICStreamFactory() {}
 
   // create a bidistream, send only stream or receive only stream
-  QUICStream *create(QUICStreamId sid, uint64_t recv_max_stream_data, uint64_t send_max_stream_data);
+  QUICStreamBase *create(QUICStreamId sid, uint64_t recv_max_stream_data, uint64_t send_max_stream_data);
 
   // delete stream by stream type
   void delete_stream(QUICStream *stream);
diff --git a/iocore/net/quic/QUICStreamManager.cc b/iocore/net/quic/QUICStreamManager.cc
index 233832162..35d9630aa 100644
--- a/iocore/net/quic/QUICStreamManager.cc
+++ b/iocore/net/quic/QUICStreamManager.cc
@@ -26,485 +26,12 @@
 #include "QUICApplication.h"
 #include "QUICTransportParameters.h"
 
-static constexpr char tag[]                     = "quic_stream_manager";
-static constexpr QUICStreamId QUIC_STREAM_TYPES = 4;
+QUICStreamManager::QUICStreamManager(QUICContext *context, QUICApplicationMap *app_map) : _context(context), _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->_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 {
-    this->_next_stream_id_bidi = static_cast<uint32_t>(QUICStreamType::SERVER_BIDI);
-    this->_next_stream_id_uni  = static_cast<uint32_t>(QUICStreamType::SERVER_UNI);
-  }
-}
-
-QUICStreamManager::~QUICStreamManager()
-{
-  for (auto stream = stream_list.pop(); stream != nullptr; stream = stream_list.pop()) {
-    _stream_factory.delete_stream(stream);
-  }
-}
-
-std::vector<QUICFrameType>
-QUICStreamManager::interests()
-{
-  return {
-    QUICFrameType::STREAM,          QUICFrameType::RESET_STREAM, QUICFrameType::STOP_SENDING,
-    QUICFrameType::MAX_STREAM_DATA, QUICFrameType::MAX_STREAMS,
-  };
-}
-
-void
-QUICStreamManager::init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
-                                            const std::shared_ptr<const QUICTransportParameters> &remote_tp)
-{
-  this->_local_tp  = local_tp;
-  this->_remote_tp = remote_tp;
-
-  if (this->_local_tp) {
-    this->_local_max_streams_bidi = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAMS_BIDI);
-    this->_local_max_streams_uni  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAMS_UNI);
-  }
-  if (this->_remote_tp) {
-    this->_remote_max_streams_bidi = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAMS_BIDI);
-    this->_remote_max_streams_uni  = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAMS_UNI);
-  }
-}
-
-void
-QUICStreamManager::set_max_streams_bidi(uint64_t max_streams)
-{
-  if (this->_local_max_streams_bidi <= max_streams) {
-    this->_local_max_streams_bidi = max_streams;
-  }
-}
-
-void
-QUICStreamManager::set_max_streams_uni(uint64_t max_streams)
-{
-  if (this->_local_max_streams_uni <= max_streams) {
-    this->_local_max_streams_uni = max_streams;
-  }
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::create_stream(QUICStreamId stream_id)
-{
-  // TODO: check stream_id
-  QUICConnectionErrorUPtr error = nullptr;
-  QUICStream *stream            = this->_find_or_create_stream(stream_id);
-  if (!stream) {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
-  }
-
-  QUICApplication *application = this->_app_map->get(stream_id);
-  application->on_new_stream(*stream);
-
-  return error;
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::create_uni_stream(QUICStreamId &new_stream_id)
-{
-  QUICConnectionErrorUPtr error = this->create_stream(this->_next_stream_id_uni);
-  if (error == nullptr) {
-    new_stream_id = this->_next_stream_id_uni;
-    this->_next_stream_id_uni += QUIC_STREAM_TYPES;
-  }
-
-  return error;
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::create_bidi_stream(QUICStreamId &new_stream_id)
-{
-  QUICConnectionErrorUPtr error = this->create_stream(this->_next_stream_id_bidi);
-  if (error == nullptr) {
-    new_stream_id = this->_next_stream_id_bidi;
-    this->_next_stream_id_bidi += QUIC_STREAM_TYPES;
-  }
-
-  return error;
-}
-
-void
-QUICStreamManager::reset_stream(QUICStreamId stream_id, QUICStreamErrorUPtr error)
-{
-  auto stream = this->_find_stream(stream_id);
-  stream->reset(std::move(error));
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::handle_frame(QUICEncryptionLevel level, const QUICFrame &frame)
-{
-  QUICConnectionErrorUPtr error = nullptr;
-
-  switch (frame.type()) {
-  case QUICFrameType::MAX_STREAM_DATA:
-    error = this->_handle_frame(static_cast<const QUICMaxStreamDataFrame &>(frame));
-    break;
-  case QUICFrameType::STREAM_DATA_BLOCKED:
-    // STREAM_DATA_BLOCKED frame is for debugging. Just propagate to streams
-    error = this->_handle_frame(static_cast<const QUICStreamDataBlockedFrame &>(frame));
-    break;
-  case QUICFrameType::STREAM:
-    error = this->_handle_frame(static_cast<const QUICStreamFrame &>(frame));
-    break;
-  case QUICFrameType::STOP_SENDING:
-    error = this->_handle_frame(static_cast<const QUICStopSendingFrame &>(frame));
-    break;
-  case QUICFrameType::RESET_STREAM:
-    error = this->_handle_frame(static_cast<const QUICRstStreamFrame &>(frame));
-    break;
-  case QUICFrameType::MAX_STREAMS:
-    error = this->_handle_frame(static_cast<const QUICMaxStreamsFrame &>(frame));
-    break;
-  default:
-    Debug(tag, "Unexpected frame type: %02x", static_cast<unsigned int>(frame.type()));
-    ink_assert(false);
-    break;
-  }
-
-  return error;
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::_handle_frame(const QUICMaxStreamDataFrame &frame)
-{
-  QUICStream *stream = this->_find_or_create_stream(frame.stream_id());
-  if (stream) {
-    return stream->recv(frame);
-  } else {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
-  }
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::_handle_frame(const QUICStreamDataBlockedFrame &frame)
-{
-  QUICStream *stream = this->_find_or_create_stream(frame.stream_id());
-  if (stream) {
-    return stream->recv(frame);
-  } else {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
-  }
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::_handle_frame(const QUICStreamFrame &frame)
-{
-  QUICStream *stream = this->_find_or_create_stream(frame.stream_id());
-  if (stream) {
-    return stream->recv(frame);
-  } else {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
-  }
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::_handle_frame(const QUICRstStreamFrame &frame)
-{
-  QUICStream *stream = this->_find_or_create_stream(frame.stream_id());
-  if (stream) {
-    return stream->recv(frame);
-  } else {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
-  }
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::_handle_frame(const QUICStopSendingFrame &frame)
-{
-  QUICStream *stream = this->_find_or_create_stream(frame.stream_id());
-  if (stream) {
-    return stream->recv(frame);
-  } else {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
-  }
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::_handle_frame(const QUICMaxStreamsFrame &frame)
-{
-  QUICStreamType type = QUICTypeUtil::detect_stream_type(frame.maximum_streams());
-  if (type == QUICStreamType::SERVER_BIDI || type == QUICStreamType::CLIENT_BIDI) {
-    this->_remote_max_streams_bidi = frame.maximum_streams();
-  } else {
-    this->_remote_max_streams_uni = frame.maximum_streams();
-  }
-  return nullptr;
-}
-
-QUICStream *
-QUICStreamManager::_find_stream(QUICStreamId id)
-{
-  for (QUICStream *s = this->stream_list.head; s; s = s->link.next) {
-    if (s->id() == id) {
-      return s;
-    }
-  }
-  return nullptr;
-}
-
-QUICStream *
-QUICStreamManager::_find_or_create_stream(QUICStreamId stream_id)
-{
-  QUICStream *stream = this->_find_stream(stream_id);
-  if (!stream) {
-    if (!this->_local_tp) {
-      return nullptr;
-    }
-
-    ink_assert(this->_local_tp);
-    ink_assert(this->_remote_tp);
-
-    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->_context->connection_info()->direction() == NET_VCONNECTION_OUT) {
-        // client
-        if (this->_remote_max_streams_bidi == 0 || nth_stream > this->_remote_max_streams_bidi) {
-          return nullptr;
-        }
-
-        local_max_stream_data  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL);
-        remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE);
-      } else {
-        // server
-        if (this->_local_max_streams_bidi == 0 || nth_stream > this->_local_max_streams_bidi) {
-          return nullptr;
-        }
-
-        local_max_stream_data  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE);
-        remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL);
-      }
-
-      break;
-    case QUICStreamType::CLIENT_UNI:
-      if (this->_context->connection_info()->direction() == NET_VCONNECTION_OUT) {
-        // client
-        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 || nth_stream > this->_local_max_streams_uni) {
-          return nullptr;
-        }
-      }
-
-      local_max_stream_data  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI);
-      remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI);
-
-      break;
-    case QUICStreamType::SERVER_BIDI:
-      if (this->_context->connection_info()->direction() == NET_VCONNECTION_OUT) {
-        // client
-        if (this->_local_max_streams_bidi == 0 || nth_stream > this->_local_max_streams_bidi) {
-          return nullptr;
-        }
-
-        local_max_stream_data  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE);
-        remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL);
-      } else {
-        // server
-        if (this->_remote_max_streams_bidi == 0 || nth_stream > this->_remote_max_streams_bidi) {
-          return nullptr;
-        }
-
-        local_max_stream_data  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_LOCAL);
-        remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE);
-      }
-      break;
-    case QUICStreamType::SERVER_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 || nth_stream > this->_remote_max_streams_uni) {
-          return nullptr;
-        }
-      }
-
-      local_max_stream_data  = this->_local_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI);
-      remote_max_stream_data = this->_remote_tp->getAsUInt(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI);
-
-      break;
-    default:
-      ink_release_assert(false);
-      break;
-    }
-
-    stream = this->_stream_factory.create(stream_id, local_max_stream_data, remote_max_stream_data);
-    ink_assert(stream != nullptr);
-    stream->set_state_listener(this);
-    this->stream_list.push(stream);
-
-    QUICApplication *application = this->_app_map->get(stream_id);
-    application->on_new_stream(*stream);
-  }
-
-  return stream;
-}
-
-uint64_t
-QUICStreamManager::total_reordered_bytes() const
-{
-  uint64_t total_bytes = 0;
-
-  // FIXME Iterating all (open + closed) streams is expensive
-  for (QUICStream *s = this->stream_list.head; s; s = s->link.next) {
-    total_bytes += s->reordered_bytes();
-  }
-  return total_bytes;
-}
-
-uint64_t
-QUICStreamManager::total_offset_received() const
-{
-  uint64_t total_offset_received = 0;
-
-  // FIXME Iterating all (open + closed) streams is expensive
-  for (QUICStream *s = this->stream_list.head; s; s = s->link.next) {
-    total_offset_received += s->largest_offset_received();
-  }
-  return total_offset_received;
-}
-
-uint64_t
-QUICStreamManager::total_offset_sent() const
-{
-  return this->_total_offset_sent;
-}
-
-void
-QUICStreamManager::_add_total_offset_sent(uint32_t sent_byte)
-{
-  // FIXME: use atomic increment
-  this->_total_offset_sent += sent_byte;
-}
-
-uint32_t
-QUICStreamManager::stream_count() const
-{
-  uint32_t count = 0;
-  for (QUICStream *s = this->stream_list.head; s; s = s->link.next) {
-    ++count;
-  }
-  return count;
-}
+QUICStreamManager::~QUICStreamManager() {}
 
 void
 QUICStreamManager::set_default_application(QUICApplication *app)
 {
   this->_app_map->set_default(app);
 }
-
-bool
-QUICStreamManager::will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num)
-{
-  if (!this->_is_level_matched(level)) {
-    return false;
-  }
-
-  // workaround fix until support 0-RTT on client
-  if (level == QUICEncryptionLevel::ZERO_RTT) {
-    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 (QUICStream *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;
-    }
-  }
-
-  return false;
-}
-
-QUICFrame *
-QUICStreamManager::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                                  size_t current_packet_size, uint32_t seq_num)
-{
-  QUICFrame *frame = nullptr;
-
-  if (!this->_is_level_matched(level)) {
-    return frame;
-  }
-
-  // workaround fix until support 0-RTT on client
-  if (level == QUICEncryptionLevel::ZERO_RTT) {
-    return frame;
-  }
-
-  // FIXME We should pick a stream based on priority
-  for (QUICStream *s = this->stream_list.head; s; s = s->link.next) {
-    frame = s->generate_frame(buf, level, connection_credit, maximum_frame_size, current_packet_size, seq_num);
-    if (frame) {
-      break;
-    }
-  }
-
-  if (frame != nullptr && frame->type() == QUICFrameType::STREAM) {
-    this->_add_total_offset_sent(static_cast<QUICStreamFrame *>(frame)->data_length());
-  }
-
-  return frame;
-}
-
-void
-QUICStreamManager::on_stream_state_close(const QUICStream *stream)
-{
-  auto direction = this->_context->connection_info()->direction();
-  switch (QUICTypeUtil::detect_stream_type(stream->id())) {
-  case QUICStreamType::SERVER_BIDI:
-    if (direction == NET_VCONNECTION_OUT) {
-      this->_local_max_streams_bidi += 1;
-    }
-    break;
-  case QUICStreamType::SERVER_UNI:
-    if (direction == NET_VCONNECTION_OUT) {
-      this->_local_max_streams_uni += 1;
-    }
-    break;
-  case QUICStreamType::CLIENT_BIDI:
-    if (direction == NET_VCONNECTION_IN) {
-      this->_local_max_streams_bidi += 1;
-    }
-    break;
-  case QUICStreamType::CLIENT_UNI:
-    if (direction == NET_VCONNECTION_IN) {
-      this->_local_max_streams_uni += 1;
-    }
-    break;
-  }
-}
-
-bool
-QUICStreamManager::_is_level_matched(QUICEncryptionLevel level)
-{
-  for (auto l : this->_encryption_level_filter) {
-    if (l == level) {
-      return true;
-    }
-  }
-
-  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 a52a375d6..e7a3bfd2a 100644
--- a/iocore/net/quic/QUICStreamManager.h
+++ b/iocore/net/quic/QUICStreamManager.h
@@ -24,85 +24,37 @@
 #pragma once
 
 #include "QUICTypes.h"
-#include "QUICBidirectionalStream.h"
-#include "QUICUnidirectionalStream.h"
 #include "QUICApplicationMap.h"
-#include "QUICFrameHandler.h"
-#include "QUICFrame.h"
-#include "QUICStreamFactory.h"
-#include "QUICLossDetector.h"
-#include "QUICPathManager.h"
 #include "QUICContext.h"
 
 class QUICTransportParameters;
 
-class QUICStreamManager : public QUICFrameHandler, public QUICFrameGenerator, public QUICStreamStateListener
+class QUICStreamManager : public QUICStreamStateListener
 {
 public:
   QUICStreamManager(QUICContext *context, QUICApplicationMap *app_map);
-  ~QUICStreamManager();
+  virtual ~QUICStreamManager();
 
-  void init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
-                                const std::shared_ptr<const QUICTransportParameters> &remote_tp);
-  void set_max_streams_bidi(uint64_t max_streams);
-  void set_max_streams_uni(uint64_t max_streams);
-  uint64_t total_reordered_bytes() const;
-  uint64_t total_offset_received() const;
-  uint64_t total_offset_sent() const;
+  virtual void init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
+                                        const std::shared_ptr<const QUICTransportParameters> &remote_tp) = 0;
+  virtual void set_max_streams_bidi(uint64_t max_streams)                                                = 0;
+  virtual void set_max_streams_uni(uint64_t max_streams)                                                 = 0;
+  virtual uint64_t total_reordered_bytes() const                                                         = 0;
+  virtual uint64_t total_offset_received() const                                                         = 0;
+  virtual uint64_t total_offset_sent() const                                                             = 0;
 
-  uint32_t stream_count() const;
-  QUICConnectionErrorUPtr create_stream(QUICStreamId stream_id);
-  QUICConnectionErrorUPtr create_uni_stream(QUICStreamId &new_stream_id);
-  QUICConnectionErrorUPtr create_bidi_stream(QUICStreamId &new_stream_id);
-  void reset_stream(QUICStreamId stream_id, QUICStreamErrorUPtr error);
+  virtual uint32_t stream_count() const                   = 0;
+  virtual QUICStream *find_stream(QUICStreamId stream_id) = 0;
 
-  void set_default_application(QUICApplication *app);
-
-  DLL<QUICStream> stream_list;
-
-  // QUICFrameHandler
-  virtual std::vector<QUICFrameType> interests() override;
-  virtual QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override;
+  virtual QUICConnectionErrorUPtr create_stream(QUICStreamId stream_id)           = 0;
+  virtual QUICConnectionErrorUPtr create_uni_stream(QUICStreamId &new_stream_id)  = 0;
+  virtual QUICConnectionErrorUPtr create_bidi_stream(QUICStreamId &new_stream_id) = 0;
+  virtual QUICConnectionErrorUPtr delete_stream(QUICStreamId &new_stream_id)      = 0;
+  virtual void reset_stream(QUICStreamId stream_id, QUICStreamErrorUPtr error)    = 0;
 
-  // QUICFrameGenerator
-  bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t timestamp) override;
-  QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            size_t current_packet_size, uint32_t timestamp) override;
-
-  // QUICStreamStateListener
-  void on_stream_state_close(const QUICStream *stream) override;
+  void set_default_application(QUICApplication *app);
 
 protected:
-  virtual bool _is_level_matched(QUICEncryptionLevel level) override;
-
-private:
-  QUICStream *_find_stream(QUICStreamId id);
-  QUICStream *_find_or_create_stream(QUICStreamId stream_id);
-  void _add_total_offset_sent(uint32_t sent_byte);
-  QUICConnectionErrorUPtr _handle_frame(const QUICStreamFrame &frame);
-  QUICConnectionErrorUPtr _handle_frame(const QUICRstStreamFrame &frame);
-  QUICConnectionErrorUPtr _handle_frame(const QUICStopSendingFrame &frame);
-  QUICConnectionErrorUPtr _handle_frame(const QUICMaxStreamDataFrame &frame);
-  QUICConnectionErrorUPtr _handle_frame(const QUICStreamDataBlockedFrame &frame);
-  QUICConnectionErrorUPtr _handle_frame(const QUICMaxStreamsFrame &frame);
-
-  QUICStreamFactory _stream_factory;
-
-  QUICContext *_context                                       = nullptr;
-  QUICApplicationMap *_app_map                                = nullptr;
-  std::shared_ptr<const QUICTransportParameters> _local_tp    = nullptr;
-  std::shared_ptr<const QUICTransportParameters> _remote_tp   = nullptr;
-  QUICStreamId _local_max_streams_bidi                        = 0;
-  QUICStreamId _local_max_streams_uni                         = 0;
-  QUICStreamId _remote_max_streams_bidi                       = 0;
-  QUICStreamId _remote_max_streams_uni                        = 0;
-  QUICStreamId _next_stream_id_uni                            = 0;
-  QUICStreamId _next_stream_id_bidi                           = 0;
-  uint64_t _total_offset_sent                                 = 0;
-  std::array<QUICEncryptionLevel, 2> _encryption_level_filter = {
-    QUICEncryptionLevel::ZERO_RTT,
-    QUICEncryptionLevel::ONE_RTT,
-  };
-
-  uint64_t _stream_id_to_nth_stream(QUICStreamId stream_id);
+  QUICContext *_context        = nullptr;
+  QUICApplicationMap *_app_map = nullptr;
 };
diff --git a/iocore/net/quic/QUICStreamManager.cc b/iocore/net/quic/QUICStreamManager_native.cc
similarity index 77%
copy from iocore/net/quic/QUICStreamManager.cc
copy to iocore/net/quic/QUICStreamManager_native.cc
index 233832162..77cbba860 100644
--- a/iocore/net/quic/QUICStreamManager.cc
+++ b/iocore/net/quic/QUICStreamManager_native.cc
@@ -21,16 +21,13 @@
  *  limitations under the License.
  */
 
-#include "QUICStreamManager.h"
-
-#include "QUICApplication.h"
-#include "QUICTransportParameters.h"
+#include "QUICStreamManager_native.h"
 
 static constexpr char tag[]                     = "quic_stream_manager";
 static constexpr QUICStreamId QUIC_STREAM_TYPES = 4;
 
-QUICStreamManager::QUICStreamManager(QUICContext *context, QUICApplicationMap *app_map)
-  : _stream_factory(context->rtt_provider(), context->connection_info()), _context(context), _app_map(app_map)
+QUICStreamManagerImpl::QUICStreamManagerImpl(QUICContext *context, QUICApplicationMap *app_map)
+  : QUICStreamManager(context, app_map), _stream_factory(context->rtt_provider(), context->connection_info())
 {
   if (this->_context->connection_info()->direction() == NET_VCONNECTION_OUT) {
     this->_next_stream_id_bidi = static_cast<uint32_t>(QUICStreamType::CLIENT_BIDI);
@@ -41,25 +38,16 @@ QUICStreamManager::QUICStreamManager(QUICContext *context, QUICApplicationMap *a
   }
 }
 
-QUICStreamManager::~QUICStreamManager()
+QUICStreamManagerImpl::~QUICStreamManagerImpl()
 {
   for (auto stream = stream_list.pop(); stream != nullptr; stream = stream_list.pop()) {
     _stream_factory.delete_stream(stream);
   }
 }
 
-std::vector<QUICFrameType>
-QUICStreamManager::interests()
-{
-  return {
-    QUICFrameType::STREAM,          QUICFrameType::RESET_STREAM, QUICFrameType::STOP_SENDING,
-    QUICFrameType::MAX_STREAM_DATA, QUICFrameType::MAX_STREAMS,
-  };
-}
-
 void
-QUICStreamManager::init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
-                                            const std::shared_ptr<const QUICTransportParameters> &remote_tp)
+QUICStreamManagerImpl::init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
+                                                const std::shared_ptr<const QUICTransportParameters> &remote_tp)
 {
   this->_local_tp  = local_tp;
   this->_remote_tp = remote_tp;
@@ -75,7 +63,7 @@ QUICStreamManager::init_flow_control_params(const std::shared_ptr<const QUICTran
 }
 
 void
-QUICStreamManager::set_max_streams_bidi(uint64_t max_streams)
+QUICStreamManagerImpl::set_max_streams_bidi(uint64_t max_streams)
 {
   if (this->_local_max_streams_bidi <= max_streams) {
     this->_local_max_streams_bidi = max_streams;
@@ -83,7 +71,7 @@ QUICStreamManager::set_max_streams_bidi(uint64_t max_streams)
 }
 
 void
-QUICStreamManager::set_max_streams_uni(uint64_t max_streams)
+QUICStreamManagerImpl::set_max_streams_uni(uint64_t max_streams)
 {
   if (this->_local_max_streams_uni <= max_streams) {
     this->_local_max_streams_uni = max_streams;
@@ -91,7 +79,7 @@ QUICStreamManager::set_max_streams_uni(uint64_t max_streams)
 }
 
 QUICConnectionErrorUPtr
-QUICStreamManager::create_stream(QUICStreamId stream_id)
+QUICStreamManagerImpl::create_stream(QUICStreamId stream_id)
 {
   // TODO: check stream_id
   QUICConnectionErrorUPtr error = nullptr;
@@ -107,7 +95,7 @@ QUICStreamManager::create_stream(QUICStreamId stream_id)
 }
 
 QUICConnectionErrorUPtr
-QUICStreamManager::create_uni_stream(QUICStreamId &new_stream_id)
+QUICStreamManagerImpl::create_uni_stream(QUICStreamId &new_stream_id)
 {
   QUICConnectionErrorUPtr error = this->create_stream(this->_next_stream_id_uni);
   if (error == nullptr) {
@@ -118,130 +106,39 @@ QUICStreamManager::create_uni_stream(QUICStreamId &new_stream_id)
   return error;
 }
 
-QUICConnectionErrorUPtr
-QUICStreamManager::create_bidi_stream(QUICStreamId &new_stream_id)
-{
-  QUICConnectionErrorUPtr error = this->create_stream(this->_next_stream_id_bidi);
-  if (error == nullptr) {
-    new_stream_id = this->_next_stream_id_bidi;
-    this->_next_stream_id_bidi += QUIC_STREAM_TYPES;
-  }
-
-  return error;
-}
-
 void
-QUICStreamManager::reset_stream(QUICStreamId stream_id, QUICStreamErrorUPtr error)
+QUICStreamManagerImpl::reset_stream(QUICStreamId stream_id, QUICStreamErrorUPtr error)
 {
   auto stream = this->_find_stream(stream_id);
   stream->reset(std::move(error));
 }
 
 QUICConnectionErrorUPtr
-QUICStreamManager::handle_frame(QUICEncryptionLevel level, const QUICFrame &frame)
+QUICStreamManagerImpl::create_bidi_stream(QUICStreamId &new_stream_id)
 {
-  QUICConnectionErrorUPtr error = nullptr;
-
-  switch (frame.type()) {
-  case QUICFrameType::MAX_STREAM_DATA:
-    error = this->_handle_frame(static_cast<const QUICMaxStreamDataFrame &>(frame));
-    break;
-  case QUICFrameType::STREAM_DATA_BLOCKED:
-    // STREAM_DATA_BLOCKED frame is for debugging. Just propagate to streams
-    error = this->_handle_frame(static_cast<const QUICStreamDataBlockedFrame &>(frame));
-    break;
-  case QUICFrameType::STREAM:
-    error = this->_handle_frame(static_cast<const QUICStreamFrame &>(frame));
-    break;
-  case QUICFrameType::STOP_SENDING:
-    error = this->_handle_frame(static_cast<const QUICStopSendingFrame &>(frame));
-    break;
-  case QUICFrameType::RESET_STREAM:
-    error = this->_handle_frame(static_cast<const QUICRstStreamFrame &>(frame));
-    break;
-  case QUICFrameType::MAX_STREAMS:
-    error = this->_handle_frame(static_cast<const QUICMaxStreamsFrame &>(frame));
-    break;
-  default:
-    Debug(tag, "Unexpected frame type: %02x", static_cast<unsigned int>(frame.type()));
-    ink_assert(false);
-    break;
+  QUICConnectionErrorUPtr error = this->create_stream(this->_next_stream_id_bidi);
+  if (error == nullptr) {
+    new_stream_id = this->_next_stream_id_bidi;
+    this->_next_stream_id_bidi += QUIC_STREAM_TYPES;
   }
 
   return error;
 }
 
 QUICConnectionErrorUPtr
-QUICStreamManager::_handle_frame(const QUICMaxStreamDataFrame &frame)
-{
-  QUICStream *stream = this->_find_or_create_stream(frame.stream_id());
-  if (stream) {
-    return stream->recv(frame);
-  } else {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
-  }
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::_handle_frame(const QUICStreamDataBlockedFrame &frame)
-{
-  QUICStream *stream = this->_find_or_create_stream(frame.stream_id());
-  if (stream) {
-    return stream->recv(frame);
-  } else {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
-  }
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::_handle_frame(const QUICStreamFrame &frame)
-{
-  QUICStream *stream = this->_find_or_create_stream(frame.stream_id());
-  if (stream) {
-    return stream->recv(frame);
-  } else {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
-  }
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::_handle_frame(const QUICRstStreamFrame &frame)
-{
-  QUICStream *stream = this->_find_or_create_stream(frame.stream_id());
-  if (stream) {
-    return stream->recv(frame);
-  } else {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
-  }
-}
-
-QUICConnectionErrorUPtr
-QUICStreamManager::_handle_frame(const QUICStopSendingFrame &frame)
+QUICStreamManagerImpl::delete_stream(QUICStreamId &stream_id)
 {
-  QUICStream *stream = this->_find_or_create_stream(frame.stream_id());
-  if (stream) {
-    return stream->recv(frame);
-  } else {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
-  }
-}
+  QUICStreamBase *stream = static_cast<QUICStreamBase *>(this->find_stream(stream_id));
+  stream_list.remove(stream);
+  delete stream;
 
-QUICConnectionErrorUPtr
-QUICStreamManager::_handle_frame(const QUICMaxStreamsFrame &frame)
-{
-  QUICStreamType type = QUICTypeUtil::detect_stream_type(frame.maximum_streams());
-  if (type == QUICStreamType::SERVER_BIDI || type == QUICStreamType::CLIENT_BIDI) {
-    this->_remote_max_streams_bidi = frame.maximum_streams();
-  } else {
-    this->_remote_max_streams_uni = frame.maximum_streams();
-  }
   return nullptr;
 }
 
-QUICStream *
-QUICStreamManager::_find_stream(QUICStreamId id)
+QUICStreamBase *
+QUICStreamManagerImpl::_find_stream(QUICStreamId id)
 {
-  for (QUICStream *s = this->stream_list.head; s; s = s->link.next) {
+  for (QUICStreamBase *s = this->stream_list.head; s; s = s->link.next) {
     if (s->id() == id) {
       return s;
     }
@@ -249,10 +146,10 @@ QUICStreamManager::_find_stream(QUICStreamId id)
   return nullptr;
 }
 
-QUICStream *
-QUICStreamManager::_find_or_create_stream(QUICStreamId stream_id)
+QUICStreamBase *
+QUICStreamManagerImpl::_find_or_create_stream(QUICStreamId stream_id)
 {
-  QUICStream *stream = this->_find_stream(stream_id);
+  QUICStreamBase *stream = this->_find_stream(stream_id);
   if (!stream) {
     if (!this->_local_tp) {
       return nullptr;
@@ -355,60 +252,171 @@ QUICStreamManager::_find_or_create_stream(QUICStreamId stream_id)
 }
 
 uint64_t
-QUICStreamManager::total_reordered_bytes() const
+QUICStreamManagerImpl::total_reordered_bytes() const
 {
   uint64_t total_bytes = 0;
 
   // FIXME Iterating all (open + closed) streams is expensive
-  for (QUICStream *s = this->stream_list.head; s; s = s->link.next) {
+  for (QUICStreamBase *s = this->stream_list.head; s; s = s->link.next) {
     total_bytes += s->reordered_bytes();
   }
   return total_bytes;
 }
 
 uint64_t
-QUICStreamManager::total_offset_received() const
+QUICStreamManagerImpl::total_offset_received() const
 {
   uint64_t total_offset_received = 0;
 
   // FIXME Iterating all (open + closed) streams is expensive
-  for (QUICStream *s = this->stream_list.head; s; s = s->link.next) {
+  for (QUICStreamBase *s = this->stream_list.head; s; s = s->link.next) {
     total_offset_received += s->largest_offset_received();
   }
   return total_offset_received;
 }
 
 uint64_t
-QUICStreamManager::total_offset_sent() const
+QUICStreamManagerImpl::total_offset_sent() const
 {
   return this->_total_offset_sent;
 }
 
 void
-QUICStreamManager::_add_total_offset_sent(uint32_t sent_byte)
+QUICStreamManagerImpl::_add_total_offset_sent(uint32_t sent_byte)
 {
   // FIXME: use atomic increment
   this->_total_offset_sent += sent_byte;
 }
 
 uint32_t
-QUICStreamManager::stream_count() const
+QUICStreamManagerImpl::stream_count() const
 {
   uint32_t count = 0;
-  for (QUICStream *s = this->stream_list.head; s; s = s->link.next) {
+  for (QUICStreamBase *s = this->stream_list.head; s; s = s->link.next) {
     ++count;
   }
   return count;
 }
 
-void
-QUICStreamManager::set_default_application(QUICApplication *app)
+QUICStream *
+QUICStreamManagerImpl::find_stream(QUICStreamId id)
+{
+  return this->_find_stream(id);
+}
+
+std::vector<QUICFrameType>
+QUICStreamManagerImpl::interests()
 {
-  this->_app_map->set_default(app);
+  return {
+    QUICFrameType::STREAM,          QUICFrameType::RESET_STREAM, QUICFrameType::STOP_SENDING,
+    QUICFrameType::MAX_STREAM_DATA, QUICFrameType::MAX_STREAMS,
+  };
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::handle_frame(QUICEncryptionLevel level, const QUICFrame &frame)
+{
+  QUICConnectionErrorUPtr error = nullptr;
+
+  switch (frame.type()) {
+  case QUICFrameType::MAX_STREAM_DATA:
+    error = this->_handle_frame(static_cast<const QUICMaxStreamDataFrame &>(frame));
+    break;
+  case QUICFrameType::STREAM_DATA_BLOCKED:
+    // STREAM_DATA_BLOCKED frame is for debugging. Just propagate to streams
+    error = this->_handle_frame(static_cast<const QUICStreamDataBlockedFrame &>(frame));
+    break;
+  case QUICFrameType::STREAM:
+    error = this->_handle_frame(static_cast<const QUICStreamFrame &>(frame));
+    break;
+  case QUICFrameType::STOP_SENDING:
+    error = this->_handle_frame(static_cast<const QUICStopSendingFrame &>(frame));
+    break;
+  case QUICFrameType::RESET_STREAM:
+    error = this->_handle_frame(static_cast<const QUICRstStreamFrame &>(frame));
+    break;
+  case QUICFrameType::MAX_STREAMS:
+    error = this->_handle_frame(static_cast<const QUICMaxStreamsFrame &>(frame));
+    break;
+  default:
+    Debug(tag, "Unexpected frame type: %02x", static_cast<unsigned int>(frame.type()));
+    ink_assert(false);
+    break;
+  }
+
+  return error;
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::_handle_frame(const QUICMaxStreamDataFrame &frame)
+{
+  QUICStreamBase *stream = this->_find_or_create_stream(frame.stream_id());
+  if (stream) {
+    return stream->recv(frame);
+  } else {
+    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
+  }
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::_handle_frame(const QUICStreamDataBlockedFrame &frame)
+{
+  QUICStreamBase *stream = this->_find_or_create_stream(frame.stream_id());
+  if (stream) {
+    return stream->recv(frame);
+  } else {
+    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
+  }
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::_handle_frame(const QUICStreamFrame &frame)
+{
+  QUICStreamBase *stream = this->_find_or_create_stream(frame.stream_id());
+  if (stream) {
+    return stream->recv(frame);
+  } else {
+    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
+  }
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::_handle_frame(const QUICRstStreamFrame &frame)
+{
+  QUICStreamBase *stream = this->_find_or_create_stream(frame.stream_id());
+  if (stream) {
+    return stream->recv(frame);
+  } else {
+    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
+  }
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::_handle_frame(const QUICStopSendingFrame &frame)
+{
+  QUICStreamBase *stream = this->_find_or_create_stream(frame.stream_id());
+  if (stream) {
+    return stream->recv(frame);
+  } else {
+    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
+  }
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::_handle_frame(const QUICMaxStreamsFrame &frame)
+{
+  QUICStreamType type = QUICTypeUtil::detect_stream_type(frame.maximum_streams());
+  if (type == QUICStreamType::SERVER_BIDI || type == QUICStreamType::CLIENT_BIDI) {
+    this->_remote_max_streams_bidi = frame.maximum_streams();
+  } else {
+    this->_remote_max_streams_uni = frame.maximum_streams();
+  }
+  return nullptr;
 }
 
 bool
-QUICStreamManager::will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num)
+QUICStreamManagerImpl::will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting,
+                                           uint32_t seq_num)
 {
   if (!this->_is_level_matched(level)) {
     return false;
@@ -424,7 +432,7 @@ QUICStreamManager::will_generate_frame(QUICEncryptionLevel level, size_t current
     return false;
   }
 
-  for (QUICStream *s = this->stream_list.head; s; s = s->link.next) {
+  for (QUICStreamBase *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;
     }
@@ -434,8 +442,8 @@ QUICStreamManager::will_generate_frame(QUICEncryptionLevel level, size_t current
 }
 
 QUICFrame *
-QUICStreamManager::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                                  size_t current_packet_size, uint32_t seq_num)
+QUICStreamManagerImpl::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit,
+                                      uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num)
 {
   QUICFrame *frame = nullptr;
 
@@ -449,7 +457,7 @@ QUICStreamManager::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint6
   }
 
   // FIXME We should pick a stream based on priority
-  for (QUICStream *s = this->stream_list.head; s; s = s->link.next) {
+  for (QUICStreamBase *s = this->stream_list.head; s; s = s->link.next) {
     frame = s->generate_frame(buf, level, connection_credit, maximum_frame_size, current_packet_size, seq_num);
     if (frame) {
       break;
@@ -463,8 +471,20 @@ QUICStreamManager::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint6
   return frame;
 }
 
+bool
+QUICStreamManagerImpl::_is_level_matched(QUICEncryptionLevel level)
+{
+  for (auto l : this->_encryption_level_filter) {
+    if (l == level) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 void
-QUICStreamManager::on_stream_state_close(const QUICStream *stream)
+QUICStreamManagerImpl::on_stream_state_close(const QUICStream *stream)
 {
   auto direction = this->_context->connection_info()->direction();
   switch (QUICTypeUtil::detect_stream_type(stream->id())) {
@@ -491,20 +511,8 @@ QUICStreamManager::on_stream_state_close(const QUICStream *stream)
   }
 }
 
-bool
-QUICStreamManager::_is_level_matched(QUICEncryptionLevel level)
-{
-  for (auto l : this->_encryption_level_filter) {
-    if (l == level) {
-      return true;
-    }
-  }
-
-  return false;
-}
-
 uint64_t
-QUICStreamManager::_stream_id_to_nth_stream(QUICStreamId stream_id)
+QUICStreamManagerImpl::_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_native.h
similarity index 52%
copy from iocore/net/quic/QUICStreamManager.h
copy to iocore/net/quic/QUICStreamManager_native.h
index a52a375d6..34b991d50 100644
--- a/iocore/net/quic/QUICStreamManager.h
+++ b/iocore/net/quic/QUICStreamManager_native.h
@@ -23,46 +23,41 @@
 
 #pragma once
 
-#include "QUICTypes.h"
+#include "QUICStreamManager.h"
 #include "QUICBidirectionalStream.h"
 #include "QUICUnidirectionalStream.h"
-#include "QUICApplicationMap.h"
 #include "QUICFrameHandler.h"
 #include "QUICFrame.h"
 #include "QUICStreamFactory.h"
 #include "QUICLossDetector.h"
 #include "QUICPathManager.h"
-#include "QUICContext.h"
 
-class QUICTransportParameters;
-
-class QUICStreamManager : public QUICFrameHandler, public QUICFrameGenerator, public QUICStreamStateListener
+class QUICStreamManagerImpl : public QUICStreamManager, public QUICFrameHandler, public QUICFrameGenerator
 {
 public:
-  QUICStreamManager(QUICContext *context, QUICApplicationMap *app_map);
-  ~QUICStreamManager();
-
-  void init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
-                                const std::shared_ptr<const QUICTransportParameters> &remote_tp);
-  void set_max_streams_bidi(uint64_t max_streams);
-  void set_max_streams_uni(uint64_t max_streams);
-  uint64_t total_reordered_bytes() const;
-  uint64_t total_offset_received() const;
-  uint64_t total_offset_sent() const;
-
-  uint32_t stream_count() const;
-  QUICConnectionErrorUPtr create_stream(QUICStreamId stream_id);
-  QUICConnectionErrorUPtr create_uni_stream(QUICStreamId &new_stream_id);
-  QUICConnectionErrorUPtr create_bidi_stream(QUICStreamId &new_stream_id);
-  void reset_stream(QUICStreamId stream_id, QUICStreamErrorUPtr error);
-
-  void set_default_application(QUICApplication *app);
-
-  DLL<QUICStream> stream_list;
+  QUICStreamManagerImpl(QUICContext *context, QUICApplicationMap *app_map);
+  ~QUICStreamManagerImpl();
+
+  virtual void init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
+                                        const std::shared_ptr<const QUICTransportParameters> &remote_tp) override;
+  virtual void set_max_streams_bidi(uint64_t max_streams) override;
+  virtual void set_max_streams_uni(uint64_t max_streams) override;
+  virtual uint64_t total_reordered_bytes() const override;
+  virtual uint64_t total_offset_received() const override;
+  virtual uint64_t total_offset_sent() const override;
+
+  virtual uint32_t stream_count() const override;
+  virtual QUICStream *find_stream(QUICStreamId id) override;
+
+  virtual QUICConnectionErrorUPtr create_stream(QUICStreamId stream_id) override;
+  virtual QUICConnectionErrorUPtr create_uni_stream(QUICStreamId &new_stream_id) override;
+  virtual QUICConnectionErrorUPtr create_bidi_stream(QUICStreamId &new_stream_id) override;
+  virtual QUICConnectionErrorUPtr delete_stream(QUICStreamId &stream_id) override;
+  virtual void reset_stream(QUICStreamId stream_id, QUICStreamErrorUPtr error) override;
 
   // QUICFrameHandler
-  virtual std::vector<QUICFrameType> interests() override;
-  virtual QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override;
+  std::vector<QUICFrameType> interests() override;
+  QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override;
 
   // QUICFrameGenerator
   bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t timestamp) override;
@@ -72,13 +67,12 @@ public:
   // QUICStreamStateListener
   void on_stream_state_close(const QUICStream *stream) override;
 
+  DLL<QUICStreamBase> stream_list;
+
 protected:
   virtual bool _is_level_matched(QUICEncryptionLevel level) override;
 
 private:
-  QUICStream *_find_stream(QUICStreamId id);
-  QUICStream *_find_or_create_stream(QUICStreamId stream_id);
-  void _add_total_offset_sent(uint32_t sent_byte);
   QUICConnectionErrorUPtr _handle_frame(const QUICStreamFrame &frame);
   QUICConnectionErrorUPtr _handle_frame(const QUICRstStreamFrame &frame);
   QUICConnectionErrorUPtr _handle_frame(const QUICStopSendingFrame &frame);
@@ -86,23 +80,25 @@ private:
   QUICConnectionErrorUPtr _handle_frame(const QUICStreamDataBlockedFrame &frame);
   QUICConnectionErrorUPtr _handle_frame(const QUICMaxStreamsFrame &frame);
 
+  QUICStreamBase *_find_stream(QUICStreamId id);
+  QUICStreamBase *_find_or_create_stream(QUICStreamId stream_id);
+  void _add_total_offset_sent(uint32_t sent_byte);
   QUICStreamFactory _stream_factory;
 
-  QUICContext *_context                                       = nullptr;
-  QUICApplicationMap *_app_map                                = nullptr;
-  std::shared_ptr<const QUICTransportParameters> _local_tp    = nullptr;
-  std::shared_ptr<const QUICTransportParameters> _remote_tp   = nullptr;
-  QUICStreamId _local_max_streams_bidi                        = 0;
-  QUICStreamId _local_max_streams_uni                         = 0;
-  QUICStreamId _remote_max_streams_bidi                       = 0;
-  QUICStreamId _remote_max_streams_uni                        = 0;
-  QUICStreamId _next_stream_id_uni                            = 0;
-  QUICStreamId _next_stream_id_bidi                           = 0;
-  uint64_t _total_offset_sent                                 = 0;
+  std::shared_ptr<const QUICTransportParameters> _local_tp  = nullptr;
+  std::shared_ptr<const QUICTransportParameters> _remote_tp = nullptr;
+  QUICStreamId _local_max_streams_bidi                      = 0;
+  QUICStreamId _local_max_streams_uni                       = 0;
+  QUICStreamId _remote_max_streams_bidi                     = 0;
+  QUICStreamId _remote_max_streams_uni                      = 0;
+  QUICStreamId _next_stream_id_uni                          = 0;
+  QUICStreamId _next_stream_id_bidi                         = 0;
+  uint64_t _total_offset_sent                               = 0;
+
+  uint64_t _stream_id_to_nth_stream(QUICStreamId stream_id);
+
   std::array<QUICEncryptionLevel, 2> _encryption_level_filter = {
     QUICEncryptionLevel::ZERO_RTT,
     QUICEncryptionLevel::ONE_RTT,
   };
-
-  uint64_t _stream_id_to_nth_stream(QUICStreamId stream_id);
 };
diff --git a/iocore/net/quic/QUICStreamManager_quiche.cc b/iocore/net/quic/QUICStreamManager_quiche.cc
new file mode 100644
index 000000000..6321cf791
--- /dev/null
+++ b/iocore/net/quic/QUICStreamManager_quiche.cc
@@ -0,0 +1,126 @@
+/** @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 "QUICStreamManager_quiche.h"
+#include "QUICStream_quiche.h"
+
+QUICStreamManagerImpl::QUICStreamManagerImpl(QUICContext *context, QUICApplicationMap *app_map)
+  : QUICStreamManager(context, app_map)
+{
+}
+
+QUICStreamManagerImpl::~QUICStreamManagerImpl() {}
+
+void
+QUICStreamManagerImpl::init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
+                                                const std::shared_ptr<const QUICTransportParameters> &remote_tp)
+{
+}
+
+void
+QUICStreamManagerImpl::set_max_streams_bidi(uint64_t max_streams)
+{
+}
+
+void
+QUICStreamManagerImpl::set_max_streams_uni(uint64_t max_streams)
+{
+}
+
+uint64_t
+QUICStreamManagerImpl::total_reordered_bytes() const
+{
+  return 0;
+}
+
+uint64_t
+QUICStreamManagerImpl::total_offset_received() const
+{
+  return 0;
+}
+
+uint64_t
+QUICStreamManagerImpl::total_offset_sent() const
+{
+  return 0;
+}
+
+uint32_t
+QUICStreamManagerImpl::stream_count() const
+{
+  return 0;
+}
+
+QUICStream *
+QUICStreamManagerImpl::find_stream(QUICStreamId stream_id)
+{
+  for (QUICStreamImpl *s = this->stream_list.head; s; s = s->link.next) {
+    if (s->id() == stream_id) {
+      return s;
+    }
+  }
+  return nullptr;
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::create_stream(QUICStreamId stream_id)
+{
+  QUICStreamImpl *stream = new QUICStreamImpl(this->_context->connection_info(), stream_id);
+  this->stream_list.push(stream);
+
+  QUICApplication *application = this->_app_map->get(stream_id);
+  application->on_new_stream(*stream);
+  return nullptr;
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::create_uni_stream(QUICStreamId &new_stream_id)
+{
+  return nullptr;
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::create_bidi_stream(QUICStreamId &new_stream_id)
+{
+  return nullptr;
+}
+
+QUICConnectionErrorUPtr
+QUICStreamManagerImpl::delete_stream(QUICStreamId &stream_id)
+{
+  QUICStreamImpl *stream = static_cast<QUICStreamImpl *>(this->find_stream(stream_id));
+  stream_list.remove(stream);
+  delete stream;
+
+  return nullptr;
+}
+
+void
+QUICStreamManagerImpl::reset_stream(QUICStreamId stream_id, QUICStreamErrorUPtr error)
+{
+}
+
+void
+QUICStreamManagerImpl::on_stream_state_close(const QUICStream *stream)
+{
+}
diff --git a/iocore/net/quic/QUICStreamManager_quiche.h b/iocore/net/quic/QUICStreamManager_quiche.h
new file mode 100644
index 000000000..8cc409a63
--- /dev/null
+++ b/iocore/net/quic/QUICStreamManager_quiche.h
@@ -0,0 +1,57 @@
+/** @file
+ *
+ *  A brief file description
+ *
+ *  @section license License
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#pragma once
+
+#include "tscore/List.h"
+#include "QUICStreamManager.h"
+#include "QUICStream_quiche.h"
+
+class QUICStreamManagerImpl : public QUICStreamManager
+{
+public:
+  QUICStreamManagerImpl(QUICContext *context, QUICApplicationMap *app_map);
+  ~QUICStreamManagerImpl();
+
+  virtual void init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
+                                        const std::shared_ptr<const QUICTransportParameters> &remote_tp) override;
+  virtual void set_max_streams_bidi(uint64_t max_streams) override;
+  virtual void set_max_streams_uni(uint64_t max_streams) override;
+  virtual uint64_t total_reordered_bytes() const override;
+  virtual uint64_t total_offset_received() const override;
+  virtual uint64_t total_offset_sent() const override;
+
+  virtual uint32_t stream_count() const override;
+  virtual QUICStream *find_stream(QUICStreamId stream_id) override;
+
+  virtual QUICConnectionErrorUPtr create_stream(QUICStreamId stream_id) override;
+  virtual QUICConnectionErrorUPtr create_uni_stream(QUICStreamId &new_stream_id) override;
+  virtual QUICConnectionErrorUPtr create_bidi_stream(QUICStreamId &new_stream_id) override;
+  virtual QUICConnectionErrorUPtr delete_stream(QUICStreamId &stream_id) override;
+  virtual void reset_stream(QUICStreamId stream_id, QUICStreamErrorUPtr error) override;
+
+  // QUICStreamStateListener
+  void on_stream_state_close(const QUICStream *stream) override;
+
+  DLL<QUICStreamImpl> stream_list;
+};
diff --git a/iocore/net/quic/QUICStreamVCAdapter.cc b/iocore/net/quic/QUICStreamVCAdapter.cc
index 1dc018337..f268f228d 100644
--- a/iocore/net/quic/QUICStreamVCAdapter.cc
+++ b/iocore/net/quic/QUICStreamVCAdapter.cc
@@ -73,7 +73,7 @@ QUICStreamVCAdapter::_read(size_t len)
     if (block->size()) {
       block->consume(reader->start_offset);
       block->_end = std::min(block->start() + len, block->_buf_end);
-      this->_write_vio.ndone += len;
+      this->_write_vio.ndone += block->size();
     }
     reader->consume(block->size());
   }
diff --git a/iocore/net/quic/QUICStream_native.h b/iocore/net/quic/QUICStream_native.h
new file mode 100644
index 000000000..39aef372a
--- /dev/null
+++ b/iocore/net/quic/QUICStream_native.h
@@ -0,0 +1,70 @@
+/** @file
+ *
+ *  A brief file description
+ *
+ *  @section license License
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#pragma once
+
+#include "QUICStream.h"
+
+class QUICStreamBase : public QUICStream, public QUICFrameGenerator, public QUICFrameRetransmitter
+{
+public:
+  QUICStreamBase() : QUICStream() {}
+  QUICStreamBase(QUICConnectionInfoProvider *cinfo, QUICStreamId sid) : QUICStream(cinfo, sid) {}
+  virtual ~QUICStreamBase() {}
+
+  QUICOffset final_offset() const override;
+
+  virtual void stop_sending(QUICStreamErrorUPtr error) override;
+  virtual void reset(QUICStreamErrorUPtr error) override;
+
+  virtual void on_read() override;
+  virtual void on_eos() override;
+
+  virtual QUICConnectionErrorUPtr recv(const QUICStreamFrame &frame);
+  virtual QUICConnectionErrorUPtr recv(const QUICMaxStreamDataFrame &frame);
+  virtual QUICConnectionErrorUPtr recv(const QUICStreamDataBlockedFrame &frame);
+  virtual QUICConnectionErrorUPtr recv(const QUICStopSendingFrame &frame);
+  virtual QUICConnectionErrorUPtr recv(const QUICRstStreamFrame &frame);
+  virtual QUICConnectionErrorUPtr recv(const QUICCryptoFrame &frame);
+
+  QUICOffset reordered_bytes() const;
+  virtual QUICOffset largest_offset_received() const;
+  virtual QUICOffset largest_offset_sent() const;
+
+  void set_state_listener(QUICStreamStateListener *listener);
+
+  LINK(QUICStreamBase, link);
+
+protected:
+  QUICOffset _send_offset     = 0;
+  QUICOffset _reordered_bytes = 0;
+
+  QUICStreamStateListener *_state_listener = nullptr;
+
+  void _notify_state_change();
+
+  void _records_rst_stream_frame(QUICEncryptionLevel level, const QUICRstStreamFrame &frame);
+  void _records_stream_frame(QUICEncryptionLevel level, const QUICStreamFrame &frame);
+  void _records_stop_sending_frame(QUICEncryptionLevel level, const QUICStopSendingFrame &frame);
+  void _records_crypto_frame(QUICEncryptionLevel level, const QUICCryptoFrame &frame);
+};
diff --git a/iocore/net/quic/QUICStream_quiche.cc b/iocore/net/quic/QUICStream_quiche.cc
new file mode 100644
index 000000000..33ddd0224
--- /dev/null
+++ b/iocore/net/quic/QUICStream_quiche.cc
@@ -0,0 +1,94 @@
+/** @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 "QUICStream_quiche.h"
+#include "QUICStreamAdapter.h"
+
+QUICStreamImpl::QUICStreamImpl() {}
+
+QUICStreamImpl::QUICStreamImpl(QUICConnectionInfoProvider *cinfo, QUICStreamId sid) : QUICStream(cinfo, sid) {}
+
+QUICOffset
+QUICStreamImpl::final_offset() const
+{
+  return 0;
+}
+
+void
+QUICStreamImpl::stop_sending(QUICStreamErrorUPtr error)
+{
+}
+
+void
+QUICStreamImpl::reset(QUICStreamErrorUPtr error)
+{
+}
+
+void
+QUICStreamImpl::on_read()
+{
+}
+
+void
+QUICStreamImpl::on_eos()
+{
+}
+
+void
+QUICStreamImpl::receive_data(quiche_conn *quiche_con)
+{
+  uint8_t buf[4096];
+  bool fin;
+  ssize_t read_len = 0;
+
+  while ((read_len = quiche_conn_stream_recv(quiche_con, this->_id, buf, sizeof(buf), &fin)) > 0) {
+    this->_adapter->write(this->_received_bytes, buf, read_len, fin);
+    this->_received_bytes += read_len;
+  }
+
+  this->_adapter->encourge_read();
+}
+
+void
+QUICStreamImpl::send_data(quiche_conn *quiche_con)
+{
+  bool fin    = false;
+  ssize_t len = 0;
+
+  len = quiche_conn_stream_capacity(quiche_con, this->_id);
+  if (len <= 0) {
+    return;
+  }
+  Ptr<IOBufferBlock> block = this->_adapter->read(len);
+  if (this->_adapter->total_len() == this->_sent_bytes + block->size()) {
+    fin = true;
+  }
+  if (block->size() > 0 || fin) {
+    ssize_t written_len =
+      quiche_conn_stream_send(quiche_con, this->_id, reinterpret_cast<uint8_t *>(block->start()), block->size(), fin);
+    if (written_len >= 0) {
+      this->_sent_bytes += written_len;
+    }
+  }
+  this->_adapter->encourge_write();
+}
diff --git a/iocore/net/quic/QUICStreamFactory.h b/iocore/net/quic/QUICStream_quiche.h
similarity index 59%
copy from iocore/net/quic/QUICStreamFactory.h
copy to iocore/net/quic/QUICStream_quiche.h
index 43690edbd..c24282b9b 100644
--- a/iocore/net/quic/QUICStreamFactory.h
+++ b/iocore/net/quic/QUICStream_quiche.h
@@ -23,24 +23,29 @@
 
 #pragma once
 
-#include "QUICTypes.h"
+#include "QUICStream.h"
+#include <quiche.h>
 
-class QUICStream;
-
-// PS: this class function should not static because of  THREAD_ALLOC and THREAD_FREE
-class QUICStreamFactory
+class QUICStreamImpl : public QUICStream
 {
 public:
-  QUICStreamFactory(QUICRTTProvider *rtt_provider, QUICConnectionInfoProvider *info) : _rtt_provider(rtt_provider), _info(info) {}
-  ~QUICStreamFactory() {}
+  QUICStreamImpl();
+  QUICStreamImpl(QUICConnectionInfoProvider *cinfo, QUICStreamId sid);
+  void receive_data(quiche_conn *quiche_con);
+  void send_data(quiche_conn *quiche_con);
+
+  // QUICStream
+  virtual QUICOffset final_offset() const override;
+
+  virtual void stop_sending(QUICStreamErrorUPtr error) override;
+  virtual void reset(QUICStreamErrorUPtr error) override;
 
-  // create a bidistream, send only stream or receive only stream
-  QUICStream *create(QUICStreamId sid, uint64_t recv_max_stream_data, uint64_t send_max_stream_data);
+  virtual void on_read() override;
+  virtual void on_eos() override;
 
-  // delete stream by stream type
-  void delete_stream(QUICStream *stream);
+  LINK(QUICStreamImpl, link);
 
 private:
-  QUICRTTProvider *_rtt_provider    = nullptr;
-  QUICConnectionInfoProvider *_info = nullptr;
+  uint64_t _received_bytes = 0;
+  uint64_t _sent_bytes     = 0;
 };
diff --git a/iocore/net/quic/QUICTransportParameters.cc b/iocore/net/quic/QUICTransportParameters.cc
index 72d7f6de6..eff5e7da8 100644
--- a/iocore/net/quic/QUICTransportParameters.cc
+++ b/iocore/net/quic/QUICTransportParameters.cc
@@ -381,26 +381,24 @@ QUICTransportParametersInEncryptedExtensions::_validate_parameters(QUICVersion v
   decltype(this->_parameters)::const_iterator ite;
 
   // MUSTs
-  if (version == QUIC_SUPPORTED_VERSIONS[0]) { // draft-28
-    if ((ite = this->_parameters.find(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID)) != this->_parameters.end()) {
-      // We cannot check the length because it's not a fixed length.
-    } else {
-      return -(TP_ERROR_MUST_EXIST | QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID);
-    }
+  if ((ite = this->_parameters.find(QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID)) != this->_parameters.end()) {
+    // We cannot check the length because it's not a fixed length.
+  } else {
+    return -(TP_ERROR_MUST_EXIST | QUICTransportParameterId::INITIAL_SOURCE_CONNECTION_ID);
+  }
 
-    if ((ite = this->_parameters.find(QUICTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID)) != this->_parameters.end()) {
-      // We cannot check the length because it's not a fixed length.
-    } else {
-      return -(TP_ERROR_MUST_EXIST | QUICTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID);
-    }
+  if ((ite = this->_parameters.find(QUICTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID)) != this->_parameters.end()) {
+    // We cannot check the length because it's not a fixed length.
+  } else {
+    return -(TP_ERROR_MUST_EXIST | QUICTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID);
+  }
 
-    // MUSTs if the server sent a Retry packet, but MUST NOT if the server did not send a Retry packet
-    // TODO Check if the server sent Retry packet
-    if ((ite = this->_parameters.find(QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID)) != this->_parameters.end()) {
-      // return -(TP_ERROR_MUST_NOT_EXIST | QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID);
-    } else {
-      // return -(TP_ERROR_MUST_EXIST | QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID);
-    }
+  // MUSTs if the server sent a Retry packet, but MUST NOT if the server did not send a Retry packet
+  // TODO Check if the server sent Retry packet
+  if ((ite = this->_parameters.find(QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID)) != this->_parameters.end()) {
+    // return -(TP_ERROR_MUST_NOT_EXIST | QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID);
+  } else {
+    // return -(TP_ERROR_MUST_EXIST | QUICTransportParameterId::RETRY_SOURCE_CONNECTION_ID);
   }
 
   // MAYs
diff --git a/iocore/net/quic/QUICTypes.h b/iocore/net/quic/QUICTypes.h
index 78af5ea7c..76f1f16f7 100644
--- a/iocore/net/quic/QUICTypes.h
+++ b/iocore/net/quic/QUICTypes.h
@@ -51,8 +51,8 @@ using QUICFrameId      = uint64_t;
 // Note: Fix "Supported Version" field in test case of QUICPacketFactory_Create_VersionNegotiationPacket
 // Note: Fix QUIC_ALPN_PROTO_LIST in QUICConfig.cc
 constexpr QUICVersion QUIC_SUPPORTED_VERSIONS[] = {
+  0x00000001,
   0xff00001d,
-  0xff00001b,
 };
 constexpr QUICVersion QUIC_EXERCISE_VERSION1 = 0x1a2a3a4a;
 constexpr QUICVersion QUIC_EXERCISE_VERSION2 = 0x5a6a7a8a;
diff --git a/iocore/net/quic/QUICUnidirectionalStream.cc b/iocore/net/quic/QUICUnidirectionalStream.cc
index 599af3a35..fd53b4973 100644
--- a/iocore/net/quic/QUICUnidirectionalStream.cc
+++ b/iocore/net/quic/QUICUnidirectionalStream.cc
@@ -28,7 +28,7 @@
 // QUICSendStream
 //
 QUICSendStream::QUICSendStream(QUICConnectionInfoProvider *cinfo, QUICStreamId sid, uint64_t send_max_stream_data)
-  : QUICStream(cinfo, sid), _remote_flow_controller(send_max_stream_data, _id), _state(nullptr, &this->_progress_sa)
+  : QUICStreamBase(cinfo, sid), _remote_flow_controller(send_max_stream_data, _id), _state(nullptr, &this->_progress_sa)
 {
   QUICStreamFCDebug("[REMOTE] %" PRIu64 "/%" PRIu64, this->_remote_flow_controller.current_offset(),
                     this->_remote_flow_controller.current_limit());
@@ -241,7 +241,7 @@ QUICSendStream::reset(QUICStreamErrorUPtr error)
 //
 QUICReceiveStream::QUICReceiveStream(QUICRTTProvider *rtt_provider, QUICConnectionInfoProvider *cinfo, QUICStreamId sid,
                                      uint64_t recv_max_stream_data)
-  : QUICStream(cinfo, sid),
+  : QUICStreamBase(cinfo, sid),
     _local_flow_controller(rtt_provider, recv_max_stream_data, _id),
     _flow_control_buffer_size(recv_max_stream_data),
     _state(this, nullptr)
diff --git a/iocore/net/quic/QUICUnidirectionalStream.h b/iocore/net/quic/QUICUnidirectionalStream.h
index 315c24f0b..5cf6e06a3 100644
--- a/iocore/net/quic/QUICUnidirectionalStream.h
+++ b/iocore/net/quic/QUICUnidirectionalStream.h
@@ -24,8 +24,9 @@
 #pragma once
 
 #include "QUICStream.h"
+#include "QUICStream_native.h"
 
-class QUICSendStream : public QUICStream
+class QUICSendStream : public QUICStreamBase
 {
 public:
   QUICSendStream(QUICConnectionInfoProvider *cinfo, QUICStreamId sid, uint64_t send_max_stream_data);
@@ -66,7 +67,7 @@ private:
   void _on_frame_lost(QUICFrameInformationUPtr &info) override;
 };
 
-class QUICReceiveStream : public QUICStream, public QUICTransferProgressProvider
+class QUICReceiveStream : public QUICStreamBase, public QUICTransferProgressProvider
 {
 public:
   QUICReceiveStream(QUICRTTProvider *rtt_provider, QUICConnectionInfoProvider *cinfo, QUICStreamId sid,
diff --git a/iocore/net/quic/test/test_QUICKeyGenerator.cc b/iocore/net/quic/test/test_QUICKeyGenerator.cc
index cd9ff38f2..aa61a303f 100644
--- a/iocore/net/quic/test/test_QUICKeyGenerator.cc
+++ b/iocore/net/quic/test/test_QUICKeyGenerator.cc
@@ -42,16 +42,17 @@ TEST_CASE("draft-23~27 Test Vectors", "[quic]")
   {
     QUICKeyGenerator keygen(QUICKeyGenerator::Context::CLIENT);
 
-    QUICConnectionId cid = {reinterpret_cast<const uint8_t *>("\xc6\x54\xef\xd8\xa3\x1b\x47\x92"), 8};
+    QUICConnectionId cid = {reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x08"), 8};
 
-    uint8_t expected_client_key[] = {0xfc, 0x4a, 0x14, 0x7a, 0x7e, 0xe9, 0x70, 0x29, 0x1b, 0x8f, 0x1c, 0x3, 0x2d, 0x2c, 0x40, 0xf9};
-    uint8_t expected_client_iv[]  = {0x1e, 0x6a, 0x5d, 0xdb, 0x7c, 0x1d, 0x1a, 0xa7, 0xa0, 0xfd, 0x70, 0x5};
-    uint8_t expected_client_hp[] = {0x43, 0x1d, 0x22, 0x82, 0xb4, 0x7b, 0xb9, 0x3f, 0xeb, 0xd2, 0xcf, 0x19, 0x85, 0x21, 0xe2, 0xbe};
+    uint8_t expected_client_key[] = {0x1f, 0x36, 0x96, 0x13, 0xdd, 0x76, 0xd5, 0x46,
+                                     0x77, 0x30, 0xef, 0xcb, 0xe3, 0xb1, 0xa2, 0x2d};
+    uint8_t expected_client_iv[]  = {0xfa, 0x04, 0x4b, 0x2f, 0x42, 0xa3, 0xfd, 0x3b, 0x46, 0xfb, 0x25, 0x5c};
+    uint8_t expected_client_hp[] = {0x9f, 0x50, 0x44, 0x9e, 0x04, 0xa0, 0xe8, 0x10, 0x28, 0x3a, 0x1e, 0x99, 0x33, 0xad, 0xed, 0xd2};
 
     QUICPacketProtectionKeyInfo pp_key_info;
     pp_key_info.set_cipher_initial(EVP_aes_128_gcm());
     pp_key_info.set_cipher_for_hp_initial(EVP_aes_128_ecb());
-    keygen.generate(0xff00001b, pp_key_info.encryption_key_for_hp(QUICKeyPhase::INITIAL),
+    keygen.generate(0x00000001, pp_key_info.encryption_key_for_hp(QUICKeyPhase::INITIAL),
                     pp_key_info.encryption_key(QUICKeyPhase::INITIAL), pp_key_info.encryption_iv(QUICKeyPhase::INITIAL),
                     pp_key_info.encryption_iv_len(QUICKeyPhase::INITIAL), cid);
 
@@ -67,17 +68,17 @@ TEST_CASE("draft-23~27 Test Vectors", "[quic]")
   {
     QUICKeyGenerator keygen(QUICKeyGenerator::Context::SERVER);
 
-    QUICConnectionId cid = {reinterpret_cast<const uint8_t *>("\xc6\x54\xef\xd8\xa3\x1b\x47\x92"), 8};
+    QUICConnectionId cid = {reinterpret_cast<const uint8_t *>("\x83\x94\xc8\xf0\x3e\x51\x57\x08"), 8};
 
-    uint8_t expected_server_key[] = {0x60, 0xc0, 0x2f, 0xa6, 0x12, 0x1e, 0xb1, 0xab,
-                                     0xa4, 0x35, 0x1f, 0x2a, 0x63, 0xb0, 0xac, 0xf8};
-    uint8_t expected_server_iv[]  = {0x38, 0xd, 0xf3, 0xc0, 0xf2, 0x8d, 0x94, 0x7, 0x76, 0x5c, 0x55, 0xa1};
-    uint8_t expected_server_hp[] = {0x92, 0xe8, 0x67, 0xb1, 0x20, 0xb1, 0x3f, 0x40, 0x9c, 0x1a, 0xa8, 0xef, 0x54, 0x30, 0x53, 0x51};
+    uint8_t expected_server_key[] = {0xcf, 0x3a, 0x53, 0x31, 0x65, 0x3c, 0x36, 0x4c,
+                                     0x88, 0xf0, 0xf3, 0x79, 0xb6, 0x06, 0x7e, 0x37};
+    uint8_t expected_server_iv[]  = {0x0a, 0xc1, 0x49, 0x3c, 0xa1, 0x90, 0x58, 0x53, 0xb0, 0xbb, 0xa0, 0x3e};
+    uint8_t expected_server_hp[] = {0xc2, 0x06, 0xb8, 0xd9, 0xb9, 0xf0, 0xf3, 0x76, 0x44, 0x43, 0x0b, 0x49, 0x0e, 0xea, 0xa3, 0x14};
 
     QUICPacketProtectionKeyInfo pp_key_info;
     pp_key_info.set_cipher_initial(EVP_aes_128_gcm());
     pp_key_info.set_cipher_for_hp_initial(EVP_aes_128_ecb());
-    keygen.generate(0xff00001b, pp_key_info.encryption_key_for_hp(QUICKeyPhase::INITIAL),
+    keygen.generate(0x00000001, pp_key_info.encryption_key_for_hp(QUICKeyPhase::INITIAL),
                     pp_key_info.encryption_key(QUICKeyPhase::INITIAL), pp_key_info.encryption_iv(QUICKeyPhase::INITIAL),
                     pp_key_info.encryption_iv_len(QUICKeyPhase::INITIAL), cid);
 
diff --git a/iocore/net/quic/test/test_QUICPacketFactory.cc b/iocore/net/quic/test/test_QUICPacketFactory.cc
index 57bf6b348..348ec348d 100644
--- a/iocore/net/quic/test/test_QUICPacketFactory.cc
+++ b/iocore/net/quic/test/test_QUICPacketFactory.cc
@@ -56,8 +56,8 @@ TEST_CASE("QUICPacketFactory_Create_VersionNegotiationPacket", "[quic]")
     0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Destination Connection ID
     0x08,                                           // SCID Len
     0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Source Connection ID
+    0x00, 0x00, 0x00, 0x01,                         // Supported Version
     0xff, 0x00, 0x00, 0x1d,                         // Supported Version
-    0xff, 0x00, 0x00, 0x1b,                         // Supported Version
     0x5a, 0x6a, 0x7a, 0x8a,                         // Exercise Version
   };
   uint8_t buf[1024] = {0};
diff --git a/iocore/net/quic/test/test_QUICStreamManager.cc b/iocore/net/quic/test/test_QUICStreamManager.cc
index ad9443deb..4f214aa75 100644
--- a/iocore/net/quic/test/test_QUICStreamManager.cc
+++ b/iocore/net/quic/test/test_QUICStreamManager.cc
@@ -26,6 +26,7 @@
 #include <memory>
 
 #include "quic/QUICStreamManager.h"
+#include "quic/QUICStreamManager_native.h"
 #include "quic/QUICFrame.h"
 #include "quic/Mock.h"
 
@@ -39,7 +40,7 @@ TEST_CASE("QUICStreamManager_NewStream", "[quic]")
   MockQUICApplication mock_app(&connection);
   app_map.set_default(&mock_app);
   MockQUICConnectionInfoProvider cinfo_provider;
-  QUICStreamManager sm(&context, &app_map);
+  QUICStreamManagerImpl sm(&context, &app_map);
 
   uint8_t local_tp_buf[] = {
     0x08,      // parameter id - initial_max_streams_bidi
@@ -103,7 +104,7 @@ TEST_CASE("QUICStreamManager_first_initial_map", "[quic]")
   MockQUICApplication mock_app(&connection);
   app_map.set_default(&mock_app);
   MockQUICConnectionInfoProvider cinfo_provider;
-  QUICStreamManager sm(&context, &app_map);
+  QUICStreamManagerImpl sm(&context, &app_map);
   std::shared_ptr<QUICTransportParameters> local_tp  = std::make_shared<QUICTransportParametersInEncryptedExtensions>();
   std::shared_ptr<QUICTransportParameters> remote_tp = std::make_shared<QUICTransportParametersInClientHello>();
   sm.init_flow_control_params(local_tp, remote_tp);
@@ -128,7 +129,7 @@ TEST_CASE("QUICStreamManager_total_offset_received", "[quic]")
   MockQUICConnection connection;
   MockQUICApplication mock_app(&connection);
   app_map.set_default(&mock_app);
-  QUICStreamManager sm(&context, &app_map);
+  QUICStreamManagerImpl sm(&context, &app_map);
 
   uint8_t local_tp_buf[] = {
     0x08,                  // parameter id - initial_max_streams_bidi
@@ -183,7 +184,7 @@ TEST_CASE("QUICStreamManager_total_offset_sent", "[quic]")
   MockQUICConnection connection;
   MockQUICApplication mock_app(&connection);
   app_map.set_default(&mock_app);
-  QUICStreamManager sm(&context, &app_map);
+  QUICStreamManagerImpl sm(&context, &app_map);
 
   uint8_t local_tp_buf[] = {
     0x08,                  // parameter id - initial_max_streams_bidi
@@ -251,7 +252,7 @@ TEST_CASE("QUICStreamManager_max_streams", "[quic]")
   MockQUICConnection connection;
   MockQUICApplication mock_app(&connection);
   app_map.set_default(&mock_app);
-  QUICStreamManager sm(&context, &app_map);
+  QUICStreamManagerImpl sm(&context, &app_map);
 
   uint8_t local_tp_buf[] = {
     0x08, // parameter id - initial_max_streams_bidi
diff --git a/lib/records/RecHttp.cc b/lib/records/RecHttp.cc
index 18d4a9c50..2fde6e5b1 100644
--- a/lib/records/RecHttp.cc
+++ b/lib/records/RecHttp.cc
@@ -45,8 +45,8 @@ const char *const TS_ALPN_PROTOCOL_HTTP_1_1      = IP_PROTO_TAG_HTTP_1_1.data();
 const char *const TS_ALPN_PROTOCOL_HTTP_2_0      = IP_PROTO_TAG_HTTP_2_0.data();
 const char *const TS_ALPN_PROTOCOL_HTTP_3        = IP_PROTO_TAG_HTTP_3.data();
 const char *const TS_ALPN_PROTOCOL_HTTP_QUIC     = IP_PROTO_TAG_HTTP_QUIC.data();
-const char *const TS_ALPN_PROTOCOL_HTTP_3_D27    = IP_PROTO_TAG_HTTP_3_D27.data();
-const char *const TS_ALPN_PROTOCOL_HTTP_QUIC_D27 = IP_PROTO_TAG_HTTP_QUIC_D27.data();
+const char *const TS_ALPN_PROTOCOL_HTTP_3_D29    = IP_PROTO_TAG_HTTP_3_D29.data();
+const char *const TS_ALPN_PROTOCOL_HTTP_QUIC_D29 = IP_PROTO_TAG_HTTP_QUIC_D29.data();
 
 const char *const TS_ALPN_PROTOCOL_GROUP_HTTP  = "http";
 const char *const TS_ALPN_PROTOCOL_GROUP_HTTP2 = "http2";
@@ -56,8 +56,8 @@ const char *const TS_PROTO_TAG_HTTP_1_1      = TS_ALPN_PROTOCOL_HTTP_1_1;
 const char *const TS_PROTO_TAG_HTTP_2_0      = TS_ALPN_PROTOCOL_HTTP_2_0;
 const char *const TS_PROTO_TAG_HTTP_3        = TS_ALPN_PROTOCOL_HTTP_3;
 const char *const TS_PROTO_TAG_HTTP_QUIC     = TS_ALPN_PROTOCOL_HTTP_QUIC;
-const char *const TS_PROTO_TAG_HTTP_3_D27    = TS_ALPN_PROTOCOL_HTTP_3_D27;
-const char *const TS_PROTO_TAG_HTTP_QUIC_D27 = TS_ALPN_PROTOCOL_HTTP_QUIC_D27;
+const char *const TS_PROTO_TAG_HTTP_3_D29    = TS_ALPN_PROTOCOL_HTTP_3_D29;
+const char *const TS_PROTO_TAG_HTTP_QUIC_D29 = TS_ALPN_PROTOCOL_HTTP_QUIC_D29;
 const char *const TS_PROTO_TAG_TLS_1_3       = IP_PROTO_TAG_TLS_1_3.data();
 const char *const TS_PROTO_TAG_TLS_1_2       = IP_PROTO_TAG_TLS_1_2.data();
 const char *const TS_PROTO_TAG_TLS_1_1       = IP_PROTO_TAG_TLS_1_1.data();
@@ -76,8 +76,8 @@ int TS_ALPN_PROTOCOL_INDEX_HTTP_1_1      = SessionProtocolNameRegistry::INVALID;
 int TS_ALPN_PROTOCOL_INDEX_HTTP_2_0      = SessionProtocolNameRegistry::INVALID;
 int TS_ALPN_PROTOCOL_INDEX_HTTP_3        = SessionProtocolNameRegistry::INVALID;
 int TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC     = SessionProtocolNameRegistry::INVALID;
-int TS_ALPN_PROTOCOL_INDEX_HTTP_3_D27    = SessionProtocolNameRegistry::INVALID;
-int TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC_D27 = SessionProtocolNameRegistry::INVALID;
+int TS_ALPN_PROTOCOL_INDEX_HTTP_3_D29    = SessionProtocolNameRegistry::INVALID;
+int TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC_D29 = SessionProtocolNameRegistry::INVALID;
 
 // Predefined protocol sets for ease of use.
 SessionProtocolSet HTTP_PROTOCOL_SET;
@@ -721,10 +721,10 @@ ts_session_protocol_well_known_name_indices_init()
   TS_ALPN_PROTOCOL_INDEX_HTTP_1_1   = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_1_1});
   TS_ALPN_PROTOCOL_INDEX_HTTP_2_0   = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_2_0});
   TS_ALPN_PROTOCOL_INDEX_HTTP_3     = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_3});
-  TS_ALPN_PROTOCOL_INDEX_HTTP_3_D27 = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_3_D27});
+  TS_ALPN_PROTOCOL_INDEX_HTTP_3_D29 = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_3_D29});
   TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC  = globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_QUIC});
-  TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC_D27 =
-    globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_QUIC_D27});
+  TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC_D29 =
+    globalSessionProtocolNameRegistry.toIndexConst(std::string_view{TS_ALPN_PROTOCOL_HTTP_QUIC_D29});
 
   // Now do the predefined protocol sets.
   HTTP_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_0_9);
@@ -738,8 +738,8 @@ ts_session_protocol_well_known_name_indices_init()
 
   DEFAULT_QUIC_SESSION_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_3);
   DEFAULT_QUIC_SESSION_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC);
-  DEFAULT_QUIC_SESSION_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_3_D27);
-  DEFAULT_QUIC_SESSION_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC_D27);
+  DEFAULT_QUIC_SESSION_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_3_D29);
+  DEFAULT_QUIC_SESSION_PROTOCOL_SET.markIn(TS_ALPN_PROTOCOL_INDEX_HTTP_QUIC_D29);
 
   DEFAULT_NON_TLS_SESSION_PROTOCOL_SET = HTTP_PROTOCOL_SET;
 
@@ -748,8 +748,8 @@ ts_session_protocol_well_known_name_indices_init()
   TSProtoTags.insert(TS_PROTO_TAG_HTTP_2_0);
   TSProtoTags.insert(TS_PROTO_TAG_HTTP_3);
   TSProtoTags.insert(TS_PROTO_TAG_HTTP_QUIC);
-  TSProtoTags.insert(TS_PROTO_TAG_HTTP_3_D27);
-  TSProtoTags.insert(TS_PROTO_TAG_HTTP_QUIC_D27);
+  TSProtoTags.insert(TS_PROTO_TAG_HTTP_3_D29);
+  TSProtoTags.insert(TS_PROTO_TAG_HTTP_QUIC_D29);
   TSProtoTags.insert(TS_PROTO_TAG_TLS_1_3);
   TSProtoTags.insert(TS_PROTO_TAG_TLS_1_2);
   TSProtoTags.insert(TS_PROTO_TAG_TLS_1_1);
diff --git a/proxy/http/HttpProxyServerMain.cc b/proxy/http/HttpProxyServerMain.cc
index 6bf058208..6b5a1fa9e 100644
--- a/proxy/http/HttpProxyServerMain.cc
+++ b/proxy/http/HttpProxyServerMain.cc
@@ -251,11 +251,11 @@ MakeHttpProxyAcceptor(HttpProxyAcceptor &acceptor, HttpProxyPort &port, unsigned
 
     quic->enableProtocols(port.m_session_protocol_preference);
 
-    // HTTP/0.9 over QUIC draft-27 (for interop only, will be removed)
-    quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_QUIC_D27, new Http3SessionAccept(accept_opt));
+    // HTTP/0.9 over QUIC draft-29 (for interop only, will be removed)
+    quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_QUIC_D29, new Http3SessionAccept(accept_opt));
 
-    // HTTP/3 draft-27
-    quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_3_D27, new Http3SessionAccept(accept_opt));
+    // HTTP/3 draft-29
+    quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_3_D29, new Http3SessionAccept(accept_opt));
 
     // HTTP/0.9 over QUIC (for interop only, will be removed)
     quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_QUIC, new Http3SessionAccept(accept_opt));
diff --git a/proxy/http3/Http3App.cc b/proxy/http3/Http3App.cc
index 1de466acb..b6361d4bd 100644
--- a/proxy/http3/Http3App.cc
+++ b/proxy/http3/Http3App.cc
@@ -137,13 +137,19 @@ Http3App::main_event_handler(int event, Event *data)
     }
     break;
   case VC_EVENT_WRITE_READY:
-  case VC_EVENT_WRITE_COMPLETE:
     if (is_bidirectional) {
       this->_handle_bidi_stream_on_write_ready(event, vio);
     } else {
       this->_handle_uni_stream_on_write_ready(event, vio);
     }
     break;
+  case VC_EVENT_WRITE_COMPLETE:
+    if (is_bidirectional) {
+      this->_handle_bidi_stream_on_write_complete(event, vio);
+    } else {
+      this->_handle_uni_stream_on_write_complete(event, vio);
+    }
+    break;
   case VC_EVENT_EOS:
     if (is_bidirectional) {
       this->_handle_bidi_stream_on_eos(event, vio);
@@ -281,6 +287,12 @@ Http3App::_handle_uni_stream_on_write_ready(int /* event */, VIO *vio)
   }
 }
 
+void
+Http3App::_handle_uni_stream_on_write_complete(int /* event */, VIO *vio)
+{
+  // QUICStreamVCAdapter *adapter = static_cast<QUICStreamVCAdapter *>(vio->vc_server);
+}
+
 void
 Http3App::_handle_bidi_stream_on_eos(int /* event */, VIO *vio)
 {
@@ -327,6 +339,22 @@ Http3App::_handle_bidi_stream_on_write_ready(int event, VIO *vio)
   }
 }
 
+void
+Http3App::_handle_bidi_stream_on_write_complete(int event, VIO *vio)
+{
+  QUICStreamVCAdapter *adapter = static_cast<QUICStreamVCAdapter *>(vio->vc_server);
+
+  QUICStreamId stream_id = adapter->stream().id();
+  Http3Transaction *txn  = static_cast<Http3Transaction *>(this->_ssn->get_transaction(stream_id));
+  if (txn != nullptr) {
+    SCOPED_MUTEX_LOCK(lock, txn->mutex, this_ethread());
+    txn->handleEvent(event);
+  }
+  // FIXME There may be data to read
+  this->_ssn->remove_transaction(txn);
+  this->_qc->stream_manager()->delete_stream(stream_id);
+}
+
 //
 // SETTINGS frame handler
 //
@@ -396,7 +424,7 @@ Http3SettingsFramer::generate_frame()
 
   this->_is_sent = true;
 
-  Http3Config::scoped_config params;
+  ts::Http3Config::scoped_config params;
 
   Http3SettingsFrame *frame = http3SettingsFrameAllocator.alloc();
   new (frame) Http3SettingsFrame();
diff --git a/proxy/http3/Http3App.h b/proxy/http3/Http3App.h
index 5de5aea05..6cc8bee49 100644
--- a/proxy/http3/Http3App.h
+++ b/proxy/http3/Http3App.h
@@ -68,9 +68,11 @@ protected:
 private:
   void _handle_uni_stream_on_read_ready(int event, VIO *vio);
   void _handle_uni_stream_on_write_ready(int event, VIO *vio);
+  void _handle_uni_stream_on_write_complete(int event, VIO *vio);
   void _handle_uni_stream_on_eos(int event, VIO *vio);
   void _handle_bidi_stream_on_read_ready(int event, VIO *vio);
   void _handle_bidi_stream_on_write_ready(int event, VIO *vio);
+  void _handle_bidi_stream_on_write_complete(int event, VIO *vio);
   void _handle_bidi_stream_on_eos(int event, VIO *vio);
 
   void _set_qpack_stream(Http3StreamType type, QUICStreamVCAdapter *adapter);
diff --git a/proxy/http3/Http3Config.cc b/proxy/http3/Http3Config.cc
index a152af5aa..055838bc2 100644
--- a/proxy/http3/Http3Config.cc
+++ b/proxy/http3/Http3Config.cc
@@ -23,13 +23,13 @@
 
 #include "Http3Config.h"
 
-int Http3Config::_config_id = 0;
+int ts::Http3Config::_config_id = 0;
 
 //
 // Http3ConfigParams
 //
 void
-Http3ConfigParams::initialize()
+ts::Http3ConfigParams::initialize()
 {
   REC_EstablishStaticConfigInt32U(this->_header_table_size, "proxy.config.http3.header_table_size");
   REC_EstablishStaticConfigInt32U(this->_max_header_list_size, "proxy.config.http3.max_header_list_size");
@@ -39,31 +39,31 @@ Http3ConfigParams::initialize()
 }
 
 uint32_t
-Http3ConfigParams::header_table_size() const
+ts::Http3ConfigParams::header_table_size() const
 {
   return this->_header_table_size;
 }
 
 uint32_t
-Http3ConfigParams::max_header_list_size() const
+ts::Http3ConfigParams::max_header_list_size() const
 {
   return this->_max_header_list_size;
 }
 
 uint32_t
-Http3ConfigParams::qpack_blocked_streams() const
+ts::Http3ConfigParams::qpack_blocked_streams() const
 {
   return this->_qpack_blocked_streams;
 }
 
 uint32_t
-Http3ConfigParams::num_placeholders() const
+ts::Http3ConfigParams::num_placeholders() const
 {
   return this->_num_placeholders;
 }
 
 uint32_t
-Http3ConfigParams::max_settings() const
+ts::Http3ConfigParams::max_settings() const
 {
   return this->_max_settings;
 }
@@ -72,29 +72,29 @@ Http3ConfigParams::max_settings() const
 // Http3Config
 //
 void
-Http3Config::startup()
+ts::Http3Config::startup()
 {
   reconfigure();
 }
 
 void
-Http3Config::reconfigure()
+ts::Http3Config::reconfigure()
 {
   Http3ConfigParams *params;
   params = new Http3ConfigParams;
   // re-read configuration
   params->initialize();
-  Http3Config::_config_id = configProcessor.set(Http3Config::_config_id, params);
+  ts::Http3Config::_config_id = configProcessor.set(ts::Http3Config::_config_id, params);
 }
 
-Http3ConfigParams *
-Http3Config::acquire()
+ts::Http3ConfigParams *
+ts::Http3Config::acquire()
 {
-  return static_cast<Http3ConfigParams *>(configProcessor.get(Http3Config::_config_id));
+  return static_cast<ts::Http3ConfigParams *>(configProcessor.get(ts::Http3Config::_config_id));
 }
 
 void
-Http3Config::release(Http3ConfigParams *params)
+ts::Http3Config::release(Http3ConfigParams *params)
 {
-  configProcessor.release(Http3Config::_config_id, params);
+  configProcessor.release(ts::Http3Config::_config_id, params);
 }
diff --git a/proxy/http3/Http3Config.h b/proxy/http3/Http3Config.h
index 27ae9a274..5efbbefa2 100644
--- a/proxy/http3/Http3Config.h
+++ b/proxy/http3/Http3Config.h
@@ -25,6 +25,8 @@
 
 #include "ProxyConfig.h"
 
+namespace ts
+{
 class Http3ConfigParams : public ConfigInfo
 {
 public:
@@ -60,3 +62,5 @@ public:
 private:
   static int _config_id;
 };
+
+} // namespace ts
diff --git a/proxy/http3/Http3Frame.cc b/proxy/http3/Http3Frame.cc
index 6bb195e1a..3b7c61101 100644
--- a/proxy/http3/Http3Frame.cc
+++ b/proxy/http3/Http3Frame.cc
@@ -396,7 +396,7 @@ Http3FrameFactory::create_null_frame()
 Http3FrameUPtr
 Http3FrameFactory::create(const uint8_t *buf, size_t len)
 {
-  Http3Config::scoped_config params;
+  ts::Http3Config::scoped_config params;
   Http3Frame *frame   = nullptr;
   Http3FrameType type = Http3Frame::type(buf, len);
 
diff --git a/proxy/http3/Http3Session.cc b/proxy/http3/Http3Session.cc
index b19d50113..60d0f0f2c 100644
--- a/proxy/http3/Http3Session.cc
+++ b/proxy/http3/Http3Session.cc
@@ -51,6 +51,14 @@ HQSession::add_transaction(HQTransaction *trans)
   return;
 }
 
+void
+HQSession::remove_transaction(HQTransaction *trans)
+{
+  this->_transaction_list.remove(trans);
+
+  return;
+}
+
 const char *
 HQSession::get_protocol_string() const
 {
diff --git a/proxy/http3/Http3Session.h b/proxy/http3/Http3Session.h
index bfb37d676..4d78b27a9 100644
--- a/proxy/http3/Http3Session.h
+++ b/proxy/http3/Http3Session.h
@@ -53,7 +53,8 @@ public:
   int get_transact_count() const override;
 
   // HQSession
-  void add_transaction(HQTransaction *);
+  void add_transaction(HQTransaction *trans);
+  void remove_transaction(HQTransaction *trans);
   HQTransaction *get_transaction(QUICStreamId);
 
 private:
diff --git a/proxy/http3/Http3SessionAccept.cc b/proxy/http3/Http3SessionAccept.cc
index 1639456a6..4ad848605 100644
--- a/proxy/http3/Http3SessionAccept.cc
+++ b/proxy/http3/Http3SessionAccept.cc
@@ -62,11 +62,11 @@ Http3SessionAccept::accept(NetVConnection *netvc, MIOBuffer *iobuf, IOBufferRead
 
   std::string_view alpn = qvc->negotiated_application_name();
 
-  if (IP_PROTO_TAG_HTTP_QUIC.compare(alpn) == 0 || IP_PROTO_TAG_HTTP_QUIC_D27.compare(alpn) == 0) {
+  if (IP_PROTO_TAG_HTTP_QUIC.compare(alpn) == 0 || IP_PROTO_TAG_HTTP_QUIC_D29.compare(alpn) == 0) {
     Debug("http3", "[%s] start HTTP/0.9 app (ALPN=%.*s)", qvc->cids().data(), static_cast<int>(alpn.length()), alpn.data());
 
     new Http09App(qvc, std::move(session_acl), this->options);
-  } else if (IP_PROTO_TAG_HTTP_3.compare(alpn) == 0 || IP_PROTO_TAG_HTTP_3_D27.compare(alpn) == 0) {
+  } else if (IP_PROTO_TAG_HTTP_3.compare(alpn) == 0 || IP_PROTO_TAG_HTTP_3_D29.compare(alpn) == 0) {
     Debug("http3", "[%s] start HTTP/3 app (ALPN=%.*s)", qvc->cids().data(), static_cast<int>(alpn.length()), alpn.data());
 
     Http3App *app = new Http3App(qvc, std::move(session_acl), this->options);
diff --git a/proxy/http3/Http3Transaction.cc b/proxy/http3/Http3Transaction.cc
index b8ffca7ee..6b4bc2db8 100644
--- a/proxy/http3/Http3Transaction.cc
+++ b/proxy/http3/Http3Transaction.cc
@@ -450,7 +450,7 @@ Http3Transaction::is_response_body_sent() const
 int64_t
 Http3Transaction::_process_read_vio()
 {
-  if (this->_read_vio.cont == nullptr || this->_read_vio.op == VIO::NONE) {
+  if (this->_info.read_vio->cont == nullptr || this->_info.read_vio->op == VIO::NONE) {
     return 0;
   }
 
@@ -463,17 +463,18 @@ Http3Transaction::_process_read_vio()
     return 0;
   }
 
-  SCOPED_MUTEX_LOCK(lock, this->_read_vio.mutex, this_ethread());
+  SCOPED_MUTEX_LOCK(lock, this->_info.read_vio->mutex, this_ethread());
 
   uint64_t nread = 0;
   this->_frame_dispatcher.on_read_ready(this->_info.adapter.stream().id(), *this->_info.read_vio->get_reader(), nread);
+  this->_info.read_vio->ndone += nread;
   return nread;
 }
 
 int64_t
 Http3Transaction::_process_write_vio()
 {
-  if (this->_write_vio.cont == nullptr || this->_write_vio.op == VIO::NONE) {
+  if (this->_info.write_vio->cont == nullptr || this->_info.write_vio->op == VIO::NONE) {
     return 0;
   }
 
@@ -486,15 +487,15 @@ Http3Transaction::_process_write_vio()
     return 0;
   }
 
-  SCOPED_MUTEX_LOCK(lock, this->_write_vio.mutex, this_ethread());
+  SCOPED_MUTEX_LOCK(lock, this->_info.write_vio->mutex, this_ethread());
 
   size_t nwritten = 0;
   bool all_done   = false;
   this->_frame_collector.on_write_ready(this->_info.adapter.stream().id(), *this->_info.write_vio->get_writer(), nwritten,
                                         all_done);
-  this->_info.write_vio->nbytes += nwritten;
+  this->_sent_bytes += nwritten;
   if (all_done) {
-    this->_info.write_vio->done();
+    this->_info.write_vio->nbytes = this->_sent_bytes;
   }
 
   return nwritten;
diff --git a/proxy/http3/Http3Transaction.h b/proxy/http3/Http3Transaction.h
index b1ddbb6e7..155d51933 100644
--- a/proxy/http3/Http3Transaction.h
+++ b/proxy/http3/Http3Transaction.h
@@ -82,6 +82,8 @@ protected:
   MIOBuffer _read_vio_buf = CLIENT_CONNECTION_FIRST_READ_BUFFER_SIZE_INDEX;
   QUICStreamVCAdapter::IOInfo &_info;
 
+  size_t _sent_bytes = 0;
+
   VIO _read_vio;
   VIO _write_vio;
   Event *_read_event  = nullptr;
diff --git a/proxy/http3/test/main.cc b/proxy/http3/test/main.cc
index e9a7307e4..ef2b119b0 100644
--- a/proxy/http3/test/main.cc
+++ b/proxy/http3/test/main.cc
@@ -56,7 +56,7 @@ struct EventProcessorListener : Catch::TestEventListenerBase {
     Thread *main_thread = new EThread;
     main_thread->set_specific();
 
-    Http3Config::startup();
+    ts::Http3Config::startup();
   }
 };
 CATCH_REGISTER_LISTENER(EventProcessorListener);
diff --git a/src/traffic_quic/Makefile.inc b/src/traffic_quic/Makefile.inc
index 447156904..45a8cc88b 100644
--- a/src/traffic_quic/Makefile.inc
+++ b/src/traffic_quic/Makefile.inc
@@ -64,3 +64,7 @@ traffic_quic_traffic_quic_LDADD = \
 	@BORINGOCSP_LIBS@ \
 	@LIBPCRE@
 
+if USE_QUICHE
+traffic_quic_traffic_quic_LDADD += \
+  $(QUICHE_LIB)
+endif
diff --git a/src/traffic_quic/quic_client.cc b/src/traffic_quic/quic_client.cc
index 8b3301a65..dbdfa895f 100644
--- a/src/traffic_quic/quic_client.cc
+++ b/src/traffic_quic/quic_client.cc
@@ -29,6 +29,7 @@
 
 #include "Http3Transaction.h"
 #include "P_QUICNetVConnection.h"
+#include "quic/QUICStreamManager.h"
 
 // OpenSSL protocol-lists format (vector of 8-bit length-prefixed, byte strings)
 // https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_alpn_protos.html
diff --git a/src/traffic_quic/traffic_quic.cc b/src/traffic_quic/traffic_quic.cc
index d4dcecb19..c54bfa0fb 100644
--- a/src/traffic_quic/traffic_quic.cc
+++ b/src/traffic_quic/traffic_quic.cc
@@ -111,7 +111,7 @@ main(int argc, const char **argv)
   http_init();
   hpack_huffman_init();
 
-  Http3Config::startup();
+  ts::Http3Config::startup();
 
   QUICClient client(&config);
   eventProcessor.schedule_in(&client, 1, ET_NET);
diff --git a/src/traffic_server/Makefile.inc b/src/traffic_server/Makefile.inc
index 1c448ad4c..409953d82 100644
--- a/src/traffic_server/Makefile.inc
+++ b/src/traffic_server/Makefile.inc
@@ -109,4 +109,8 @@ traffic_server_traffic_server_LDADD += \
   $(top_builddir)/proxy/http3/libhttp3.a \
   $(top_builddir)/iocore/net/quic/libquic.a \
   $(top_builddir)/iocore/eventsystem/libinkevent.a
+if USE_QUICHE
+traffic_server_traffic_server_LDADD += \
+  $(QUICHE_LIB)
+endif
 endif
diff --git a/src/traffic_server/traffic_server.cc b/src/traffic_server/traffic_server.cc
index dea006b8c..3890d17b6 100644
--- a/src/traffic_server/traffic_server.cc
+++ b/src/traffic_server/traffic_server.cc
@@ -1934,7 +1934,7 @@ main(int /* argc ATS_UNUSED */, const char **argv)
   // has other dependencies. Hopefully not in prep_HttpProxyServer().
   HttpConfig::startup();
 #if TS_USE_QUIC == 1
-  Http3Config::startup();
+  ts::Http3Config::startup();
 #endif
 
   /* Set up the machine with the outbound address if that's set,
diff --git a/src/tscore/ink_inet.cc b/src/tscore/ink_inet.cc
index 815f10656..50e13e328 100644
--- a/src/tscore/ink_inet.cc
+++ b/src/tscore/ink_inet.cc
@@ -50,10 +50,10 @@ const std::string_view IP_PROTO_TAG_HTTP_0_9("http/0.9"sv);
 const std::string_view IP_PROTO_TAG_HTTP_1_0("http/1.0"sv);
 const std::string_view IP_PROTO_TAG_HTTP_1_1("http/1.1"sv);
 const std::string_view IP_PROTO_TAG_HTTP_2_0("h2"sv);         // HTTP/2 over TLS
-const std::string_view IP_PROTO_TAG_HTTP_QUIC("hq-29"sv);     // HTTP/0.9 over QUIC
-const std::string_view IP_PROTO_TAG_HTTP_3("h3-29"sv);        // HTTP/3 over QUIC
-const std::string_view IP_PROTO_TAG_HTTP_QUIC_D27("hq-27"sv); // HTTP/0.9 over QUIC (draft-27)
-const std::string_view IP_PROTO_TAG_HTTP_3_D27("h3-27"sv);    // HTTP/3 over QUIC (draft-27)
+const std::string_view IP_PROTO_TAG_HTTP_QUIC("hq"sv);        // HTTP/0.9 over QUIC
+const std::string_view IP_PROTO_TAG_HTTP_3("h3"sv);           // HTTP/3 over QUIC
+const std::string_view IP_PROTO_TAG_HTTP_QUIC_D29("hq-29"sv); // HTTP/0.9 over QUIC (draft-29)
+const std::string_view IP_PROTO_TAG_HTTP_3_D29("h3-29"sv);    // HTTP/3 over QUIC (draft-29)
 
 const std::string_view UNIX_PROTO_TAG{"unix"sv};