Add QUIC draft-23 support

Squashed commit of the following:

commit 8aab554452ee9f9f770b2406b0df0d033eaae8e0
Merge: fa823387f 32b5dae81
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Tue Oct 8 15:14:18 2019 -0700

    Merge branch 'master' into quic-latest

    * master:
      Add filename to error message in header_rewrite
      Turn on certificate verification, in permissive mode for now
      Concurrent SRV request failures
      Fix use-after-free problem related to logging headers
      ssl_multicert.config -> params->configFilePath
      Adds build targets on CI for 9.0.x
      weak mutex macros
      Fix a build issue on enabling FIPS
      Change the ready checks for ats to use the actual ats port and not the server port. Add ready check on server port for the server
      Address possibe use after free issue in HttpVCTable::remove_entry
      YAML config:  output erroneous keyword and line number in YAML file it appears on in diags.log.
      Convert old sni configuration file in lua to yaml
      First cut at a layout for Release Notes
      Clear api set bit to avoid crash in following redirect.

commit fa823387f81aefe42cabf5a449977dc861b90587
Author: scw00 <scw00@apache.org>
Date:   Fri Sep 27 14:04:31 2019 +0800

    QUIC: Fix test_QUICKeyGenerator

commit e62f4af7a3ee6978986cbd490875286ce1fa2730
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Fri Sep 27 16:35:07 2019 +0900

    Fix a compile warning

commit 887732b49527b4266b4b7956db3f627355b3bd14
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Tue Sep 10 13:01:15 2019 +0900

    Update initial salt

commit c2b9be3a83919b0c863e2d5880956aaf2096e3a7
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Tue Sep 10 10:16:20 2019 +0900

    Update QUIC transport error codes

commit 9d843ebf6e9399ff4dabd62952d7dc6a98f65f93
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Tue Sep 10 09:58:26 2019 +0900

    Rename DISABLE_MIGRATION to DISABLE_ACTIVE_MIGRATION

commit d1951e98e86702f0769947c7541f64aed2045962
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Tue Sep 10 09:54:22 2019 +0900

    Update the length of unpredictable bits field in Stateless Reset Packet

commit 617dd31a7bd904d839abc4fde50eb3f5280ce1e9
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Tue Sep 10 09:51:47 2019 +0900

    Update draft version numbers to 23

commit 2e78599cc634a85f9552f22132bfbdd084319391
Merge: 212fdd319 70de21d9c
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Fri Sep 27 10:24:45 2019 +0900

    Merge branch 'master' into quic-latest

    * master: (23 commits)
      Rearrange config reload notifications
      Removed hardcoded sni.yaml configuration filename in logs
      Clarify docs on the change from redirect_enabled.
      Make code buildable with BoringSSL
      Fix debug output for global_user_agent_header.
      Add documentation for TSHttpTxnRedoCacheLookup
      Add example plugin to show how to use TSRedoCacheLookup.
      TSHttpTxnRedoCacheLookup.
      Fix reference to SMDebug.
      adding TSHttpTxnRedoCacheLookup
      Add AUTest using h2spec
      Track scheduled events to (read|write)_vio.cont from Http2Stream
      Cleanup AuTest for HTTP/2
      Fix AuTest for HTTP/2 using httpbin
      Add base64 encoding/decoding to encryption/decryption, and general code cleanup.
      Update documentation for connect_attempts_timeout.
      Track SSL session cache evictions performed due to full bucket
      Perform a SSL quiet shutdown when close-notify is not sent
      Remove hard coded filename in error message
      Substitution string has changed
      ...

commit 212fdd3198a846eca22e7bbe73a0765c3e4782ee
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Fri Sep 27 10:20:28 2019 +0900

    Fix unit tests

commit 6f470fa831d4401bc85703ad233218e535df9d90
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Thu Sep 26 17:36:39 2019 +0900

    Fix build issues on macOS (or clang)

commit a786041f3e27d34d009636a781cf3bd6972f0882
Author: scw00 <scw00@apache.org>
Date:   Thu Sep 19 08:29:25 2019 +0800

    QUIC: Remove useless default constructor in QUICStreamManager

commit edc4b672b98e38dd95f6d9a80e271e999cd3297a
Author: scw00 <scw00@apache.org>
Date:   Tue Sep 17 14:03:14 2019 +0800

    QUIC: Rename QUICCongestionController.cc to QUICNewRenoCongestionController.cc

commit 45c3f138633466f2bc31dd45bbd617b00e14938b
Author: scw00 <scw00@apache.org>
Date:   Wed Sep 11 14:09:34 2019 +0800

    QUIC: Remove unnecessary allocator

commit 256a74c54f93cf86cd7fc8d866f195b23df2f2f0
Author: scw00 <scw00@apache.org>
Date:   Wed Sep 11 11:30:04 2019 +0800

    QUIC: Add Token Creator

commit 5980fa47083aca5df6d4f04e388ddab5344cdd2b
Author: scw00 <scw00@apache.org>
Date:   Mon Sep 9 15:24:23 2019 +0800

    QUIC: add more infomation to QUICPacketProtectionKeyInfo

commit 814f39e8269d8c19064b698825c2b9441c9eacb5
Author: scw00 <scw00@apache.org>
Date:   Mon Sep 9 12:32:20 2019 +0800

    QUIC: Hidden more pp_key_info detail in QUICContext

commit fecacfbd973b7c5e3b10c21e94fdfff4300768e5
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Tue Sep 10 14:33:52 2019 +0900

    Stabilize unit tests

commit e8565bbee89663a1c89b4052c0fc32ea80bccfb6
Author: scw00 <scw00@apache.org>
Date:   Wed Aug 7 10:55:54 2019 +0800

    QUIC: Adds QUICContext to wrap some common params

commit 5b04932d62ff66602923c7e63250660fd0992e84
Merge: 5c7b68a0d 92d4ef142
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Fri Sep 6 11:16:38 2019 +0900

    Merge branch 'master' into quic-latest

    * master:
      Check F_GETPIPE_SZ is defined to fix macOS build
      Update Server IP in Transaction when attaching a session from the pool
      Add unit tests for MIOBuffer:write()
      Allow disabling HTTP/2 priority frames limit
      Add implementation for TextView::rtrim(char const*)
      Doc: Guide to remap plugin API.
      Add helper functions to apply api timeouts consistently.
      address review comments
      pipe buffer size for log.pipe should be configurable
      Provide stats for the recently introduced HTTP/2 rate limits

commit 5c7b68a0d337fefaf1d4cdde31574c0ab9144034
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Wed Sep 4 14:40:30 2019 +0900

    Send PreferredAddress only if necessary

commit 2b5e2ce18e6367f142f2acad345489716486660a
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Tue Sep 3 17:04:27 2019 +0900

    Fix memory leaks detected by ASAN

commit 13bdd9d05f5348c0aaa37f7669f1fa425c434503
Merge: e7430fabb c65f0a1fb
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Tue Sep 3 14:12:38 2019 +0900

    Merge branch 'master' into quic-latest

    * master: (30 commits)
      Cleanup: Remove unused AllocType and unused functions
      Ran clang-format
      ProxySsn renaming member vars
      cachekey: added --key-type (for parent selection)
      Bumped the version of master to 10.0.0
      ProxySsn Refactor move code to cc
      1) add autest for log filter; 2) support filter for duplicated param name in query parm of URL
      code clean from review comments
      move code to utility function to reduce the interface size
      fix typo issue
      fix the bug if filter word included in param value
      fix the bug if filter word included in param value
      correct the size of DNS buffers
      cachekey: added --canonical-prefix parameter
      static linking asan, tsan, lsan
      Fixed const issue with magick plugin
      CI: added support for disabling curl in the build
      Adds the v9.0.x Docs link to main docs page
      Updates the CI build scripts, adds QUIC support
      Expose client SSL stats via API & Lua
      ...

commit e7430fabb29f91bf6d146834d7eb5ba867b130a0
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Tue Sep 3 14:07:01 2019 +0900

    Fix QUICPinger and tests for it

commit f040d5fa7bcb170d054d5f0773a7aba38312bd3b
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Tue Sep 3 12:33:26 2019 +0900

    Print a raw value of unknown transport parameter

commit 13f9f63aef7e8b76d5aa11048d29112ccfaae533
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Tue Aug 27 14:25:00 2019 +0900

    Use active_connection_id_limit advertised

commit 0e4622e62613fd8b7b9a8d65e2613131a7d1d9c6
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Thu Aug 22 16:38:38 2019 +0900

    Assert sending a frame that is allowed on the encryption level

commit 72b1f79c0af422a1e383d7becf370a580bcfd159
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Thu Aug 22 16:23:53 2019 +0900

    Send PING only on Short packets

commit e7a6cd6292ba54b6804b79298188e7042a73577a
Merge: 3c8e6d12f 5886fb212
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Wed Aug 21 11:42:41 2019 +0900

    Merge branch 'master' into quic-latest

    * master:
      Fix inactivity timeout on QUIC
      Cleanup: Remove unused empty files
      Cleanup: unifdef WRITE_AND_TRANSFER
      Explain how SRV origin selection works
      Doc: fix build errors.
      HTTP/2 fix with realloc
      Fix clang-tidy's mistakes
      Ran make clang-tidy
      Updated clang-tidy configuration, scripts, and Makefiles
      Make proxy.config.http.per_server.min_keep_alive_connections overridable
      Make TS_NULL_MLOC a valid C compile-time constant.
      Doc: Remove python 2 vestiges from conf.py, traffic-server.py.

     Conflicts:
    	iocore/net/QUICNetVConnection.cc

commit 3c8e6d12f9335dc7b55ca45dafb21fc4c3c3c0b8
Merge: 7a9d7fd63 aa319a461
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Mon Aug 19 09:29:44 2019 +0900

    Merge branch 'master' into quic-latest

    * master: (48 commits)
      Fix H2 internal counters
      Reactivate active timeout enforcement
      Avoid AWS auth v4 path/query param double encoding
      Fix QUIC build
      Cleanup: Signal READ_READY event only if the buffer is readable
      Cleanup: Remove unused function & old comments
      Make client_context_dump test resilient to dump order changes
      Record HttpSM Id on new transaction
      make check race condition fix
      Doc: Improve handling of build when documentation is not enabled.
      fix.
      Limit resources used by regex_remap to prevent crashes on resource exhaustion.
      compress plugin: document undocumented options
      Allocate DependencyTree only if HTTP/2 stream priority is enabled
      HTTP/2 rate limiting
      Fixes various issues found in docs
      Remove double call of the SNI action on TLS accept.
      Refactor the alpn/npn handling into common ALPNSupport class
      Update for QUIC integration.
      Fixed the InkAPI to provide the TSVConnProtocolEnable/Disable functions. Update documentation and updated the example plugin.
      ...

commit 7a9d7fd633dfdcd8a931dbbf35b76fa590707581
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Fri Aug 16 11:56:12 2019 +0900

    fix typo

commit 5b5efeccb23e88b25e7b644a4a828208679f6416
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Mon Aug 5 11:08:49 2019 +0900

    Print sending Retry packets

commit d222be218ef38f3f26e8a68b64f691b984f10799
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Fri Aug 2 09:32:09 2019 +0900

    Min/Max length of PreferredAddress

commit fb350263dc0b8cc73d557c8ed1220afccfb9b244
Merge: 4938b7f85 e068b7685
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Thu Aug 1 16:57:48 2019 +0900

    Merge branch 'master' into quic-latest

    * master:
      Add support for updating Container fields as well
      Preserve the raw log fields when wiping using case insensitive contains
      Add soft limit for HTTP Request URI and Header field length. Add a default body_factory template when rejecting a request that's too long
      Doc: Minor typo in CONTRIBUTING.md
      Add QUIC draft-20 support
      Initialize EventIO
      fixed datatype in example plugin
      Cleanup debug logs around SSLWriteBuffer
      Remove unnecesary function name on debug logs in SSLNetVConnection
      TS API - Adds the TSHttpTxnNextHopNameGet() function.

     Conflicts:
    	.gitignore
    	iocore/net/P_QUICNetVConnection.h
    	iocore/net/QUICNetVConnection.cc
    	iocore/net/QUICPacketHandler.cc
    	iocore/net/quic/Makefile.am
    	iocore/net/quic/Mock.h
    	iocore/net/quic/QUICAckFrameCreator.cc
    	iocore/net/quic/QUICAckFrameCreator.h
    	iocore/net/quic/QUICAltConnectionManager.cc
    	iocore/net/quic/QUICAltConnectionManager.h
    	iocore/net/quic/QUICBidirectionalStream.cc
    	iocore/net/quic/QUICBidirectionalStream.h
    	iocore/net/quic/QUICConfig.h
    	iocore/net/quic/QUICCongestionController.cc
    	iocore/net/quic/QUICCryptoStream.cc
    	iocore/net/quic/QUICCryptoStream.h
    	iocore/net/quic/QUICDebugNames.cc
    	iocore/net/quic/QUICFlowController.cc
    	iocore/net/quic/QUICFlowController.h
    	iocore/net/quic/QUICFrame.cc
    	iocore/net/quic/QUICFrame.h
    	iocore/net/quic/QUICFrameDispatcher.cc
    	iocore/net/quic/QUICFrameDispatcher.h
    	iocore/net/quic/QUICFrameGenerator.cc
    	iocore/net/quic/QUICFrameGenerator.h
    	iocore/net/quic/QUICHandshake.cc
    	iocore/net/quic/QUICHandshake.h
    	iocore/net/quic/QUICKeyGenerator.cc
    	iocore/net/quic/QUICLossDetector.cc
    	iocore/net/quic/QUICLossDetector.h
    	iocore/net/quic/QUICPacket.cc
    	iocore/net/quic/QUICPacket.h
    	iocore/net/quic/QUICPacketFactory.cc
    	iocore/net/quic/QUICPacketFactory.h
    	iocore/net/quic/QUICPacketHeaderProtector.cc
    	iocore/net/quic/QUICPacketReceiveQueue.cc
    	iocore/net/quic/QUICPacketReceiveQueue.h
    	iocore/net/quic/QUICPathValidator.cc
    	iocore/net/quic/QUICPathValidator.h
    	iocore/net/quic/QUICPinger.cc
    	iocore/net/quic/QUICPinger.h
    	iocore/net/quic/QUICStreamManager.cc
    	iocore/net/quic/QUICStreamManager.h
    	iocore/net/quic/QUICTransportParameters.h
    	iocore/net/quic/QUICTypes.cc
    	iocore/net/quic/QUICTypes.h
    	iocore/net/quic/QUICUnidirectionalStream.cc
    	iocore/net/quic/QUICUnidirectionalStream.h
    	iocore/net/quic/test/test_QUICAckFrameCreator.cc
    	iocore/net/quic/test/test_QUICFlowController.cc
    	iocore/net/quic/test/test_QUICFrame.cc
    	iocore/net/quic/test/test_QUICFrameDispatcher.cc
    	iocore/net/quic/test/test_QUICInvariants.cc
    	iocore/net/quic/test/test_QUICKeyGenerator.cc
    	iocore/net/quic/test/test_QUICLossDetector.cc
    	iocore/net/quic/test/test_QUICPacket.cc
    	iocore/net/quic/test/test_QUICPacketFactory.cc
    	iocore/net/quic/test/test_QUICPacketHeaderProtector.cc
    	iocore/net/quic/test/test_QUICStream.cc
    	iocore/net/quic/test/test_QUICStreamManager.cc
    	iocore/net/quic/test/test_QUICType.cc
    	mgmt/RecordsConfig.cc
    	src/traffic_quic/quic_client.cc
    	src/tscore/ink_inet.cc

commit 4938b7f854cb06411d6fd061bdbde28a53074103
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Thu Aug 1 14:21:50 2019 +0900

    Make QUICFrame:to_io_buffer_block pure virtual

commit baaf97c51637116fcdd1ba3bf475788115c8662c
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Thu Aug 1 14:04:13 2019 +0900

    Convert QUICAckFrame::store to to_io_buffer_block

commit ad565245ed077205a6955a77d3250e2a39f5775b
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Thu Aug 1 12:21:42 2019 +0900

    Convert QUICUnknownFrame::store to to_io_buffer_block

commit 9cb75727328f8f02d9938a056ec5f5ac6091374c
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Thu Aug 1 12:15:30 2019 +0900

    Convert QUICRetireConnectionIdFrame::store to to_io_buffer_block

commit cc452b953d0cfb00be3abee374a99251d3139889
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Thu Aug 1 12:06:40 2019 +0900

    Convert QUICNewTokenFrame::store to to_io_buffer_block

commit 748d66cfad7e7181bd7b1aa50ebb9bed51f00b81
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Thu Aug 1 11:50:25 2019 +0900

    Convert QUICCryptoFrame::store to to_io_buffer_block

commit 2b38e5f88289a4e7d590d767c2b7c9a1942a184a
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Wed Jul 31 17:27:51 2019 +0900

    Convert QUICPathResponseFrame::store to to_io_buffer_block

commit e0f2c609212a617ae59ce0346dc9327a83421cc6
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Wed Jul 31 17:18:23 2019 +0900

    Convert QUICPathChallengeFrame::store to to_io_buffer_block

commit edd2963e2e0b12f6e14364bd5cdd4473e1804600
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Wed Jul 31 17:10:00 2019 +0900

    Convert QUICNewConnectionIdFrame::store to to_io_buffer_block

commit 93b79fb2e6386ce840130b08f46c8e58e56ffd2d
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Wed Jul 31 16:52:38 2019 +0900

    Convert QUICStreamIdBlockedFrame::store to to_io_buffer_block

commit d3b5ea831572403b037bba8585fa592acb585d14
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Wed Jul 31 16:44:58 2019 +0900

    Convert QUICStreamDataBlockedFrame::store to to_io_buffer_block

commit a1390e86c95fffc8e62f7c4ea471e77bddd6554b
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Wed Jul 31 16:40:27 2019 +0900

    Convert QUICDataBlockediFrame::store to to_io_buffer_block

commit f419cd9752aa1312454fe819a08e0516f90a3ba2
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Wed Jul 31 16:32:35 2019 +0900

    Convert QUICPaddingFrame::store to to_io_buffer_block

commit 6f5da6078bcb76cd1f0aabfc7c38defe7886e5d2
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Wed Jul 31 16:23:10 2019 +0900

    Convert QUICMaxStreamsFrame::store to to_io_buffer_block

commit 08501a8886e35241ca08ed9cd558e6dce5a18735
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Wed Jul 31 16:18:21 2019 +0900

    Convert QUICMaxStreamDataFrame::store to to_io_buffer_block

commit ac4a732c6f3f574f0558895b708bd557b50b95ce
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Wed Jul 31 16:13:36 2019 +0900

    Convert QUICMaxDataFrame::store to to_io_buffer_block

commit d996d4bcab89786783b9b4065c3ccf5ebc1008f0
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Wed Jul 31 16:05:08 2019 +0900

    Convert QUICPingFrame::store to to_io_buffer_block

commit 578772f84d2fcf425ee06b661a5a7b2a655cf6a1
Merge: eb2a9d31c 78995bf4e
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Tue Jul 23 15:03:38 2019 +0900

    Merge branch 'master' into quic-latest

    * master: (46 commits)
      Add dest addr information to UDPPacket class
      Update UDPNet for QUIC
      Add HKDF wrapper
      Use un-deprecated records for SSL server verification
      Removes proxy.config.http.cache.allow_empty_doc
      Deprecate the mysql_remap plugin. See #5395
      Removes the stale_while_revalidate plugin. See #5395
      Removes the memcached_remap plugin. See #5395
      Removes the hipes plugin. See #5395
      Removes the header_normalize plugin. See #5395
      Removes the buffer_upload plugin. See #5395
      Removes the balancer plugin. See #5395
      Fixes spelling in lib/records
      Fixes memory leak in traffic_crashlog
      Promotes certifier to stable, see #5394
      Promotes remap_purge to stable, see #5394
      Promotes prefetch to stable, see #5394
      Promotes multiplexer to stable, see #5394
      Promotes cache_range_requests to stable, see #5394
      fix If-Match and If-Unmodified-Since priority problem,about rfc https://tools.ietf.org/html/rfc7232#section-3.3
      ...

     Conflicts:
    	build/crypto.m4
    	configure.ac
    	include/tscore/HKDF.h
    	iocore/net/P_UDPNet.h
    	iocore/net/P_UnixNet.h
    	iocore/net/UnixUDPNet.cc
    	src/tscore/HKDF_boringssl.cc
    	src/tscore/HKDF_openssl.cc

commit eb2a9d31c6c5eca02596181085509aa155126e16
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Mon Jul 22 12:25:23 2019 +0900

    Fix frame parsers for CON_CLOSE, STOP_SENDING and RST_STREAM

    Error codes were read as 16 bit integers

commit 0dc9a3e542734909cd3466b555ecf56461fbd7e7
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Mon Jul 22 09:43:54 2019 +0900

    Fix CONNECTION_CLOSE frame parser

commit a62388d1b538e0d2617465928124a4b7573119ef
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Mon Jul 22 07:36:02 2019 +0900

    Set default value for initial_max_stream_data_uni to 4096

    http-22 says it should be 1024 or greater.

commit 0e46a6bccc582f02d65d4e11c228e423e35508b6
Author: Masaori Koshiba <masaori@apache.org>
Date:   Mon Jul 22 02:07:32 2019 +0900

    Fix storing QUICConnectionId::ZERO

commit 5db9a3b1cba535e6dac6664139c953b136e8adcb
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Fri Jul 19 23:39:13 2019 +0900

    Update max cid length

commit 6e6351e721d784121f40f0f381124d624f7e5a39
Author: Masaori Koshiba <masaori@apache.org>
Date:   Fri Jul 19 15:00:10 2019 +0900

    Fix buffer length for VN packet

commit bcf842a12a0a9ec67ca1d7e5e95f5c9be6a806ab
Author: Masaori Koshiba <masaori@apache.org>
Date:   Fri Jul 19 14:45:59 2019 +0900

    Fix stateless retry on server side

    Prior this change, getting the token filed offset of INITIAL packet
    was wrong when stateless retry is enabled.

commit ab8508b28a48c0653bd9187ce4511cb96d76ec34
Author: Masaori Koshiba <masaori@apache.org>
Date:   Fri Jul 19 11:30:35 2019 +0900

    Fix QUICFrame & unit tests for NEW_CONNECTION_ID

commit 62f7174c6ec2428218f9b457f9b37a816a622e6a
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Thu Jul 18 16:26:25 2019 +0900

    Add getter for retire_prior_to field

commit 3fff0dec8aa7abc8ef34a3a9da50ac3336de06ab
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Thu Jul 18 16:14:27 2019 +0900

    Add Retire Prior To field to sending NCID frames

commit 2944fe85c21816ae5bcbeba8199f26c035049f8b
Author: Masaori Koshiba <masaori@apache.org>
Date:   Thu Jul 18 15:57:30 2019 +0900

    Add tests for static functions of QUICPacketLongHeader

commit 1ce476c91cd50e66c916daa1446f7d2480f19604
Author: Masaori Koshiba <masaori@apache.org>
Date:   Thu Jul 18 12:28:55 2019 +0900

    Fix getting packet length on dequeue from QUICPacketReceiveQueue

commit 142e11e2cd4b6bfc4fb91db9b8fbcbd72123950c
Author: Masaori Koshiba <masaori@apache.org>
Date:   Thu Jul 18 11:21:33 2019 +0900

    Update to support new RETRY packet format

commit 16d7129f14d259d290bbb2b06aa69ecb107ea906
Author: Masaori Koshiba <masaori@apache.org>
Date:   Thu Jul 18 11:00:27 2019 +0900

    Fix sampling offset for packet header protection & unit tests

commit 6f8682715cb604f838711b9ec6af5ed99db95f16
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Thu Jul 18 08:56:53 2019 +0900

    Fix tests for QUICFrameDispatcher

commit c647ed4dac3ca538252857e8674aa3b7ec47dd05
Author: Masaori Koshiba <masaori@apache.org>
Date:   Wed Jul 17 16:20:48 2019 +0900

    Fix unit tests for QUICPacketFactory

commit f140407023a3377d9c4ce420d4e33f66da735a8e
Author: Masaori Koshiba <masaori@apache.org>
Date:   Wed Jul 17 16:17:47 2019 +0900

    Update storing long header packet & unit tests

commit 7cc4b20ad7c82f9eaa2bda787897a0a1bb4b0560
Author: Masaori Koshiba <masaori@apache.org>
Date:   Wed Jul 17 16:16:29 2019 +0900

    Fix loading long header packet

commit c47874c3653191fb90543fbed5d6b734d69fba86
Author: Masaori Koshiba <masaori@apache.org>
Date:   Wed Jul 17 14:54:38 2019 +0900

    Fix unit tests for QUICInvariants

commit 87afcb077642df9c4902c15a7fc42f58b9a5f060
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Tue Jul 16 21:38:00 2019 +0900

    Update Long Header CID parser

commit 86a4317b4fd2ef19456cf5d231d78c50d99db09d
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Tue Jul 9 17:10:20 2019 +0900

    Use variable-length integer for error code fields on QUIC frames

commit e105d33d7569739174abffddb89c491e3e729cb0
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Fri Jul 5 17:10:55 2019 +0900

    Make ErrorCode 62 bit unsigned integer

commit 13d003757239833d8eff2e94aa387dc2dddb1dff
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Fri Jul 5 16:40:39 2019 +0900

    Update initial_salt

commit f0602a6e187e456b85e34b97d4aff6d2be9789ed
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Fri Jul 5 16:16:42 2019 +0900

    Add active_connection_id_limit Transport Parameter

commit 923916d7f0bbea19367fdfc7b2ddf430c5a54233
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Thu Jul 4 17:22:48 2019 +0900

    Update draft version numbers to 22

commit e95f03599f42743e8774908c9a8d0879f5c59890
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Thu Jul 11 12:40:22 2019 +0900

    Fix a heap-use-after-free in path validation

commit c444068c28fe1693e056596faf3039ad0adde8e3
Merge: eb4092333 8510a1c24
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Wed Jul 10 11:51:27 2019 +0900

    Merge branch 'master' into quic-latest

    * master:
      Log H2 errors with the codes
      Separate out common header compression logic to hdrs directory
      TSIOBufferReaderCopy: copy data from a reader to a fixed buffer.
      Remove unused header file ink_inout.h
      Remove unused LibBulkIO
      Reverse internal order of HPACK Dynamic Table Entries
      More Autest cleanup

     Conflicts:
    	proxy/hdrs/Makefile.am
    	proxy/http2/HPACK.cc

commit eb4092333da5734acd9cf6b933a97f6b160c0b14
Author: Masakazu Kitajo <maskit@apache.org>
Date:   Tue Jul 9 09:25:35 2019 +0900

    Fix QUICPath::operator==

commit 10805ba0dfe346dfbe93dad24656110e6d5fb250
Author: scw00 <scw00@apache.org>
Date:   Mon Jul 8 10:08:29 2019 +0800

    QUIC: remove useless params in padder

commit b0f248334200ec10be9d87d8593d7f46b4c88104
Author: scw00 <scw00@apache.org>
Date:   Mon Jul 8 09:25:28 2019 +0800

    QUIC: Add inline generators into QUICFrameGenerator

commit b76299f09e128e17d7103ee5ca5c72f3f4f67b22
Author: scw00 <scw00@apache.org>
Date:   Sun Jul 7 13:20:11 2019 +0800

    QUIC: Add QUICPinger test

commit fdff5b96f24e0d681884a1553ae1203161e9efaa
Author: scw00 <scw00@apache.org>
Date:   Sun Jul 7 12:21:08 2019 +0800

    QUIC: Fix test

commit 816305894503400861fc4e830f464c6a3a3b1b41
Author: scw00 <scw00@apache.org>
Date:   Sun Jul 7 12:07:34 2019 +0800

    QUIC: Using weight for QUICFrameGenerators

commit 1eeb804564afb7db5164c8db90e6b5bcedd86b3e
Author: scw00 <scw00@apache.org>
Date:   Thu Jul 4 08:58:15 2019 +0800

    QUIC: Split QUICFrameGenerator into two queue
diff --git a/.gitignore b/.gitignore
index 23cdc5d..9d79cbd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -93,29 +93,7 @@
 
 iocore/net/test_certlookup
 iocore/net/test_UDPNet
-iocore/net/quic/test_QUICAckFrameCreator
-iocore/net/quic/test_QUICAddrVerifyState
-iocore/net/quic/test_QUICAltConnectionManager
-iocore/net/quic/test_QUICFlowController
-iocore/net/quic/test_QUICFrame
-iocore/net/quic/test_QUICFrameDispatcher
-iocore/net/quic/test_QUICFrameRetransmitter
-iocore/net/quic/test_QUICHandshake
-iocore/net/quic/test_QUICHandshakeProtocol
-iocore/net/quic/test_QUICIncomingFrameBuffer
-iocore/net/quic/test_QUICInvariants
-iocore/net/quic/test_QUICKeyGenerator
-iocore/net/quic/test_QUICLossDetector
-iocore/net/quic/test_QUICPacket
-iocore/net/quic/test_QUICPacketHeaderProtector
-iocore/net/quic/test_QUICPacketFactory
-iocore/net/quic/test_QUICStream
-iocore/net/quic/test_QUICStreamManager
-iocore/net/quic/test_QUICStreamState
-iocore/net/quic/test_QUICTransportParameters
-iocore/net/quic/test_QUICType
-iocore/net/quic/test_QUICTypeUtil
-iocore/net/quic/test_QUICVersionNegotiator
+iocore/net/quic/test_QUIC*
 iocore/aio/test_AIO
 iocore/eventsystem/test_IOBuffer
 iocore/eventsystem/test_EventSystem
diff --git a/iocore/eventsystem/I_Thread.h b/iocore/eventsystem/I_Thread.h
index 116463f..2ab995a 100644
--- a/iocore/eventsystem/I_Thread.h
+++ b/iocore/eventsystem/I_Thread.h
@@ -123,11 +123,9 @@
   ProxyAllocator http2ClientSessionAllocator;
   ProxyAllocator http2StreamAllocator;
   ProxyAllocator quicClientSessionAllocator;
-  ProxyAllocator quicHandshakeAllocator;
   ProxyAllocator quicBidiStreamAllocator;
   ProxyAllocator quicSendStreamAllocator;
   ProxyAllocator quicReceiveStreamAllocator;
-  ProxyAllocator quicStreamManagerAllocator;
   ProxyAllocator httpServerSessionAllocator;
   ProxyAllocator hdrHeapAllocator;
   ProxyAllocator strHeapAllocator;
diff --git a/iocore/net/P_QUICNetVConnection.h b/iocore/net/P_QUICNetVConnection.h
index 925a4fe..d39824e 100644
--- a/iocore/net/P_QUICNetVConnection.h
+++ b/iocore/net/P_QUICNetVConnection.h
@@ -54,14 +54,18 @@
 #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/QUICAddrVerifyState.h"
 #include "quic/QUICPacketProtectionKeyInfo.h"
+#include "quic/QUICContext.h"
+#include "quic/QUICTokenCreator.h"
 
 // Size of connection ids for debug log : e.g. aaaaaaaa-bbbbbbbb\0
 static constexpr size_t MAX_CIDS_SIZE = 8 + 1 + 8 + 1;
@@ -127,11 +131,7 @@
  *    WRITE:
  *      Do nothing
  **/
-class QUICNetVConnection : public UnixNetVConnection,
-                           public QUICConnection,
-                           public QUICFrameGenerator,
-                           public RefCountObj,
-                           public ALPNSupport
+class QUICNetVConnection : public UnixNetVConnection, public QUICConnection, public RefCountObj, public ALPNSupport
 {
   using super = UnixNetVConnection; ///< Parent type.
 
@@ -145,6 +145,9 @@
   // 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;
@@ -197,11 +200,6 @@
   std::vector<QUICFrameType> interests() override;
   QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override;
 
-  // QUICFrameGenerator
-  bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override;
-  QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            ink_hrtime timestamp) override;
-
   int in_closed_queue = 0;
 
   bool shouldDestroy();
@@ -231,7 +229,6 @@
   QUICPacketFactory _packet_factory;
   QUICFrameFactory _frame_factory;
   QUICAckFrameManager _ack_frame_manager;
-  QUICPinger _pinger;
   QUICPacketHeaderProtector _ph_protector;
   QUICRTTMeasure _rtt_measure;
   QUICApplicationMap *_application_map = nullptr;
@@ -240,6 +237,8 @@
 
   // 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;
@@ -251,8 +250,10 @@
   QUICConnectionTable *_ctable                      = nullptr;
   QUICAltConnectionManager *_alt_con_manager        = nullptr;
   QUICPathValidator *_path_validator                = nullptr;
+  QUICPathManager *_path_manager                    = nullptr;
+  QUICTokenCreator *_token_creator                  = nullptr;
 
-  std::vector<QUICFrameGenerator *> _frame_generators;
+  QUICFrameGeneratorManager _frame_generators;
 
   QUICPacketReceiveQueue _packet_recv_queue = {this->_packet_factory, this->_ph_protector};
 
@@ -276,11 +277,6 @@
   void _close_closed_event(Event *data);
   Event *_closed_event = nullptr;
 
-  void _schedule_path_validation_timeout(ink_hrtime interval);
-  void _unschedule_path_validation_timeout();
-  void _close_path_validation_timeout(Event *data);
-  Event *_path_validation_timeout = nullptr;
-
   void _schedule_ack_manager_periodic(ink_hrtime interval);
   void _unschedule_ack_manager_periodic();
   Event *_ack_manager_periodic = nullptr;
@@ -296,7 +292,6 @@
                                   std::vector<QUICFrameInfo> &frames);
   QUICPacketUPtr _packetize_frames(QUICEncryptionLevel level, uint64_t max_packet_size, std::vector<QUICFrameInfo> &frames);
   void _packetize_closing_frame();
-  Ptr<IOBufferBlock> _generate_padding_frame(size_t frame_size);
   QUICPacketUPtr _build_packet(QUICEncryptionLevel level, Ptr<IOBufferBlock> parent_block, bool retransmittable, bool probing,
                                bool crypto);
 
@@ -324,7 +319,7 @@
                                  const std::shared_ptr<const QUICTransportParameters> &remote_tp);
   void _handle_error(QUICConnectionErrorUPtr error);
   QUICPacketUPtr _dequeue_recv_packet(QUICPacketCreationResult &result);
-  void _validate_new_path();
+  void _validate_new_path(const QUICPath &path);
 
   int _complete_handshake_if_possible();
   void _switch_to_handshake_state();
@@ -337,7 +332,6 @@
   void _start_application();
 
   void _handle_periodic_ack_event();
-  void _handle_path_validation_timeout(Event *data);
   void _handle_idle_timeout();
 
   QUICConnectionErrorUPtr _handle_frame(const QUICNewConnectionIdFrame &frame);
@@ -352,19 +346,16 @@
   QUICPacketUPtr _the_final_packet = QUICPacketFactory::create_null_packet();
   QUICStatelessResetToken _reset_token;
 
-  ats_unique_buf _av_token       = {nullptr};
-  size_t _av_token_len           = 0;
-  bool _is_resumption_token_sent = false;
+  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
-  bool _has_ack_eliciting_packet_out = true;
-
   QUICAddrVerifyState _verfied_state;
 
-  // QUICFrameGenerator
-  void _on_frame_lost(QUICFrameInformationUPtr &info) override;
+  std::unique_ptr<QUICContextImpl> _context;
 };
 
 typedef int (QUICNetVConnection::*QUICNetVConnHandler)(int, void *);
diff --git a/iocore/net/QUICNetVConnection.cc b/iocore/net/QUICNetVConnection.cc
index 7070727..22d2e61 100644
--- a/iocore/net/QUICNetVConnection.cc
+++ b/iocore/net/QUICNetVConnection.cc
@@ -64,7 +64,7 @@
 static constexpr ink_hrtime WRITE_READY_INTERVAL      = HRTIME_MSECONDS(2);
 static constexpr uint32_t PACKET_PER_EVENT            = 256;
 static constexpr uint32_t MAX_CONSECUTIVE_STREAMS     = 8; ///< Interrupt sending STREAM frames to send ACK frame
-static constexpr uint32_t MIN_PKT_PAYLOAD_LEN         = 3; ///< Minimum payload length for sampling for header protection
+// static constexpr uint32_t MIN_PKT_PAYLOAD_LEN         = 3; ///< Minimum payload length for sampling for header protection
 
 static constexpr uint32_t STATE_CLOSING_MAX_SEND_PKT_NUM  = 8; ///< Max number of sending packets which contain a closing frame.
 static constexpr uint32_t STATE_CLOSING_MAX_RECV_PKT_WIND = 1 << STATE_CLOSING_MAX_SEND_PKT_NUM;
@@ -178,83 +178,21 @@
     }
   }
 
+  uint8_t
+  active_cid_limit() const override
+  {
+    if (this->_ctx == NET_VCONNECTION_IN) {
+      return this->_params->active_cid_limit_in();
+    } else {
+      return this->_params->active_cid_limit_out();
+    }
+  }
+
 private:
   const QUICConfigParams *_params;
   NetVConnectionContext_t _ctx;
 };
 
-class QUICCCConfigQCP : public QUICCCConfig
-{
-public:
-  QUICCCConfigQCP(const QUICConfigParams *params) : _params(params) {}
-
-  uint32_t
-  max_datagram_size() const override
-  {
-    return this->_params->cc_max_datagram_size();
-  }
-
-  uint32_t
-  initial_window() const override
-  {
-    return this->_params->cc_initial_window();
-  }
-
-  uint32_t
-  minimum_window() const override
-  {
-    return this->_params->cc_minimum_window();
-  }
-
-  float
-  loss_reduction_factor() const override
-  {
-    return this->_params->cc_loss_reduction_factor();
-  }
-
-  uint32_t
-  persistent_congestion_threshold() const override
-  {
-    return this->_params->cc_persistent_congestion_threshold();
-  }
-
-private:
-  const QUICConfigParams *_params;
-};
-
-class QUICLDConfigQCP : public QUICLDConfig
-{
-public:
-  QUICLDConfigQCP(const QUICConfigParams *params) : _params(params) {}
-
-  uint32_t
-  packet_threshold() const override
-  {
-    return this->_params->ld_packet_threshold();
-  }
-
-  float
-  time_threshold() const override
-  {
-    return this->_params->ld_time_threshold();
-  }
-
-  ink_hrtime
-  granularity() const override
-  {
-    return this->_params->ld_granularity();
-  }
-
-  ink_hrtime
-  initial_rtt() const override
-  {
-    return this->_params->ld_initial_rtt();
-  }
-
-private:
-  const QUICConfigParams *_params;
-};
-
 QUICNetVConnection::QUICNetVConnection() : _packet_factory(this->_pp_key_info), _ph_protector(this->_pp_key_info) {}
 
 QUICNetVConnection::~QUICNetVConnection()
@@ -263,7 +201,6 @@
   this->_unschedule_packet_write_ready();
   this->_unschedule_closing_timeout();
   this->_unschedule_closed_event();
-  this->_unschedule_path_validation_timeout();
 }
 
 // XXX This might be called on ET_UDP thread
@@ -419,8 +356,9 @@
 QUICNetVConnection::start()
 {
   ink_release_assert(this->thread != nullptr);
-
+  this->_context = std::make_unique<QUICContextImpl>(&this->_rtt_measure, this, &this->_pp_key_info);
   this->_five_tuple.update(this->local_addr, this->remote_addr, SOCK_DGRAM);
+  QUICPath trusted_path = {{}, {}};
   // Version 0x00000001 uses stream 0 for cryptographic handshake with TLS 1.3, but newer version may not
   if (this->direction() == NET_VCONNECTION_IN) {
     QUICCertConfig::scoped_config server_cert;
@@ -434,6 +372,7 @@
     this->_ack_frame_manager.set_max_ack_delay(this->_quic_config->max_ack_delay_in());
     this->_schedule_ack_manager_periodic(this->_quic_config->max_ack_delay_in());
   } else {
+    trusted_path = {this->local_addr, this->remote_addr};
     QUICTPConfigQCP tp_config(this->_quic_config, NET_VCONNECTION_OUT);
     this->_pp_key_info.set_context(QUICPacketProtectionKeyInfo::Context::CLIENT);
     this->_ack_frame_manager.set_ack_delay_exponent(this->_quic_config->ack_delay_exponent_out());
@@ -450,27 +389,47 @@
   this->_frame_dispatcher = new QUICFrameDispatcher(this);
 
   // Create frame handlers
-  QUICCCConfigQCP cc_config(this->_quic_config);
-  QUICLDConfigQCP ld_config(this->_quic_config);
-  this->_rtt_measure.init(ld_config);
-  this->_congestion_controller = new QUICCongestionController(this->_rtt_measure, this, cc_config);
-  this->_loss_detector         = new QUICLossDetector(this, this->_congestion_controller, &this->_rtt_measure, ld_config);
+  this->_pinger = new QUICPinger();
+  this->_padder = new QUICPadder(this->netvc_context);
+  this->_rtt_measure.init(this->_context->ld_config());
+  this->_congestion_controller = new QUICNewRenoCongestionController(*_context);
+  this->_loss_detector =
+    new QUICLossDetector(*_context, this->_congestion_controller, &this->_rtt_measure, this->_pinger, this->_padder);
   this->_frame_dispatcher->add_handler(this->_loss_detector);
 
   this->_remote_flow_controller = new QUICRemoteConnectionFlowController(UINT64_MAX);
   this->_local_flow_controller  = new QUICLocalConnectionFlowController(&this->_rtt_measure, UINT64_MAX);
-  this->_path_validator         = new QUICPathValidator();
+  this->_path_validator         = new QUICPathValidator(*this, [this](bool succeeded) {
+    if (succeeded) {
+      this->_alt_con_manager->drop_cid(this->_peer_old_quic_connection_id);
+      // FIXME This is a kind of workaround for connection migration.
+      // This PING make peer to send an ACK frame so that ATS can detect packet loss.
+      // It would be better if QUICLossDetector could detect the loss in another way.
+      this->ping();
+    }
+  });
   this->_stream_manager         = new QUICStreamManager(this, &this->_rtt_measure, this->_application_map);
+  this->_path_manager           = new QUICPathManager(*this, *this->_path_validator);
+  this->_path_manager->set_trusted_path(trusted_path);
+  this->_token_creator = new QUICTokenCreator(this->_context.get());
+
+  static constexpr int QUIC_STREAM_MANAGER_WEIGHT = QUICFrameGeneratorWeight::AFTER_DATA - 1;
+  static constexpr int QUIC_PINGER_WEIGHT         = QUICFrameGeneratorWeight::LATE + 1;
+  static constexpr int QUIC_PADDER_WEIGHT         = QUICFrameGeneratorWeight::LATE + 2;
 
   // Register frame generators
-  this->_frame_generators.push_back(this->_handshake_handler);      // CRYPTO
-  this->_frame_generators.push_back(this->_path_validator);         // PATH_CHALLENGE, PATH_RESPOSNE
-  this->_frame_generators.push_back(this->_local_flow_controller);  // MAX_DATA
-  this->_frame_generators.push_back(this->_remote_flow_controller); // DATA_BLOCKED
-  this->_frame_generators.push_back(this);                          // NEW_TOKEN
-  this->_frame_generators.push_back(this->_stream_manager);         // STREAM, MAX_STREAM_DATA, STREAM_DATA_BLOCKED
-  this->_frame_generators.push_back(&this->_ack_frame_manager);     // ACK
-  this->_frame_generators.push_back(&this->_pinger);                // PING
+  this->_frame_generators.add_generator(*this->_handshake_handler, QUICFrameGeneratorWeight::EARLY); // CRYPTO
+  this->_frame_generators.add_generator(*this->_path_validator, QUICFrameGeneratorWeight::EARLY); // PATH_CHALLENGE, PATH_RESPOSNE
+  this->_frame_generators.add_generator(*this->_local_flow_controller, QUICFrameGeneratorWeight::BEFORE_DATA);  // MAX_DATA
+  this->_frame_generators.add_generator(*this->_remote_flow_controller, QUICFrameGeneratorWeight::BEFORE_DATA); // DATA_BLOCKED
+  this->_frame_generators.add_generator(*this->_token_creator, QUICFrameGeneratorWeight::BEFORE_DATA);          // NEW_TOKEN
+  this->_frame_generators.add_generator(*this->_stream_manager,
+                                        QUIC_STREAM_MANAGER_WEIGHT); // STREAM, MAX_STREAM_DATA, STREAM_DATA_BLOCKED
+  this->_frame_generators.add_generator(this->_ack_frame_manager, QUICFrameGeneratorWeight::BEFORE_DATA); // ACK
+
+  this->_frame_generators.add_generator(*this->_pinger, QUIC_PINGER_WEIGHT); // PING
+  // Warning: padder should be tail of the frame generators
+  this->_frame_generators.add_generator(*this->_padder, QUIC_PADDER_WEIGHT); // PADDING
 
   // Register frame handlers
   this->_frame_dispatcher->add_handler(this);
@@ -542,6 +501,13 @@
 }
 
 void
+QUICNetVConnection::set_local_addr()
+{
+  int local_sa_size = sizeof(local_addr);
+  ATS_UNUSED_RETURN(safe_getsockname(this->_udp_con->getFd(), &local_addr.sa, &local_sa_size));
+}
+
+void
 QUICNetVConnection::reenable(VIO *vio)
 {
   return;
@@ -566,6 +532,9 @@
   // FIXME: complete do_io_xxxx instead
   this->read.enabled = 1;
 
+  this->set_local_addr();
+  this->remote_addr = con.addr;
+
   this->start();
 
   // start QUIC handshake
@@ -672,7 +641,7 @@
 void
 QUICNetVConnection::ping()
 {
-  this->_pinger.request(QUICEncryptionLevel::ONE_RTT);
+  this->_pinger->request();
 }
 
 void
@@ -808,9 +777,6 @@
     // Reschedule WRITE_READY
     this->_schedule_packet_write_ready(true);
     break;
-  case QUIC_EVENT_PATH_VALIDATION_TIMEOUT:
-    this->_handle_path_validation_timeout(data);
-    break;
   case VC_EVENT_INACTIVITY_TIMEOUT:
     // Start Immediate Close because of Idle Timeout
     this->_handle_idle_timeout();
@@ -844,9 +810,6 @@
     // Reschedule WRITE_READY
     this->_schedule_packet_write_ready(true);
     break;
-  case QUIC_EVENT_PATH_VALIDATION_TIMEOUT:
-    this->_handle_path_validation_timeout(data);
-    break;
   case VC_EVENT_INACTIVITY_TIMEOUT:
     // Start Immediate Close because of Idle Timeout
     this->_handle_idle_timeout();
@@ -877,9 +840,6 @@
     this->_close_packet_write_ready(data);
     this->_state_closing_send_packet();
     break;
-  case QUIC_EVENT_PATH_VALIDATION_TIMEOUT:
-    this->_handle_path_validation_timeout(data);
-    break;
   case QUIC_EVENT_CLOSING_TIMEOUT:
     this->_close_closing_timeout(data);
     this->_switch_to_close_state();
@@ -908,9 +868,6 @@
     // This should be the only difference between this and closing_state.
     this->_close_packet_write_ready(data);
     break;
-  case QUIC_EVENT_PATH_VALIDATION_TIMEOUT:
-    this->_handle_path_validation_timeout(data);
-    break;
   case QUIC_EVENT_CLOSING_TIMEOUT:
     this->_close_closing_timeout(data);
     this->_switch_to_close_state();
@@ -933,7 +890,6 @@
     this->_unschedule_ack_manager_periodic();
     this->_unschedule_packet_write_ready();
     this->_unschedule_closing_timeout();
-    this->_unschedule_path_validation_timeout();
     this->_close_closed_event(data);
     this->next_inactivity_timeout_at = 0;
     this->next_activity_timeout_at   = 0;
@@ -1139,12 +1095,16 @@
 
   // Start handshake
   if (this->netvc_context == NET_VCONNECTION_IN) {
+    this->local_addr.sa  = packet.to().sa;
+    this->remote_addr.sa = packet.from().sa;
+    this->_five_tuple.update(this->local_addr, this->remote_addr, SOCK_DGRAM);
+
     if (!this->_alt_con_manager) {
       this->_alt_con_manager =
         new QUICAltConnectionManager(this, *this->_ctable, this->_peer_quic_connection_id, this->_quic_config->instance_id(),
-                                     this->_quic_config->num_alt_connection_ids(), this->_quic_config->preferred_address_ipv4(),
+                                     this->_quic_config->active_cid_limit_in(), this->_quic_config->preferred_address_ipv4(),
                                      this->_quic_config->preferred_address_ipv6());
-      this->_frame_generators.push_back(this->_alt_con_manager);
+      this->_frame_generators.add_generator(*this->_alt_con_manager, QUICFrameGeneratorWeight::EARLY);
       this->_frame_dispatcher->add_handler(this->_alt_con_manager);
     }
     QUICTPConfigQCP tp_config(this->_quic_config, NET_VCONNECTION_IN);
@@ -1159,6 +1119,14 @@
       }
     }
   } else {
+    if (!this->_alt_con_manager) {
+      this->_alt_con_manager =
+        new QUICAltConnectionManager(this, *this->_ctable, this->_peer_quic_connection_id, this->_quic_config->instance_id(),
+                                     this->_quic_config->active_cid_limit_out());
+      this->_frame_generators.add_generator(*this->_alt_con_manager, QUICFrameGeneratorWeight::BEFORE_DATA);
+      this->_frame_dispatcher->add_handler(this->_alt_con_manager);
+    }
+
     // on client side, _handshake_handler is already started. Just process packet like _state_handshake_process_handshake_packet()
     error = this->_recv_and_ack(packet);
   }
@@ -1184,6 +1152,8 @@
   this->_av_token     = ats_unique_malloc(this->_av_token_len);
   memcpy(this->_av_token.get(), packet.payload(), this->_av_token_len);
 
+  this->_padder->set_av_token_len(this->_av_token_len);
+
   // discard all transport state
   this->_handshake_handler->reset();
   this->_packet_factory.reset();
@@ -1366,7 +1336,7 @@
   uint32_t packet_count = 0;
   uint32_t error        = 0;
   while (error == 0 && packet_count < PACKET_PER_EVENT) {
-    uint32_t window = this->_congestion_controller->open_window();
+    uint32_t window = this->_congestion_controller->credit();
 
     if (window == 0) {
       break;
@@ -1418,7 +1388,9 @@
         written += len;
 
         int dcil = (this->_peer_quic_connection_id == QUICConnectionId::ZERO()) ? 0 : this->_peer_quic_connection_id.length();
-        this->_ph_protector.protect(buf, len, dcil);
+        if (!this->_ph_protector.protect(buf, len, dcil)) {
+          ink_assert(!"failed to protect buffer");
+        }
 
         QUICConDebug("[TX] %s packet #%" PRIu64 " size=%zu", QUICDebugNames::packet_type(packet->type()), packet->packet_number(),
                      len);
@@ -1507,25 +1479,9 @@
   return parent_block;
 }
 
-// FIXME QUICNetVConnection should not know the actual type value of PADDING frame
-Ptr<IOBufferBlock>
-QUICNetVConnection::_generate_padding_frame(size_t frame_size)
-{
-  Ptr<IOBufferBlock> block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
-  block->alloc(iobuffer_size_to_index(frame_size));
-  memset(block->start(), 0, frame_size);
-  block->fill(frame_size);
-
-  ink_assert(frame_size == static_cast<size_t>(block->size()));
-
-  return block;
-}
-
 QUICPacketUPtr
 QUICNetVConnection::_packetize_frames(QUICEncryptionLevel level, uint64_t max_packet_size, std::vector<QUICFrameInfo> &frames)
 {
-  ink_hrtime timestamp = Thread::get_hrtime();
-
   QUICPacketUPtr packet = QUICPacketFactory::create_null_packet();
   if (max_packet_size <= MAX_PACKET_OVERHEAD) {
     return packet;
@@ -1546,30 +1502,43 @@
   first_block->alloc(iobuffer_size_to_index(0));
   first_block->fill(0);
 
-  if (!this->_has_ack_eliciting_packet_out) {
-    // Sent too much ack_only packet. At this moment we need to packetize a ping frame
-    // to force peer send ack frame.
-    this->_pinger.request(level);
-  }
-
+  uint32_t seq_num   = this->_seq_num++;
   size_t size_added  = 0;
   bool ack_eliciting = false;
   bool crypto        = false;
   uint8_t frame_instance_buffer[QUICFrame::MAX_INSTANCE_SIZE]; // This is for a frame instance but not serialized frame data
   QUICFrame *frame = nullptr;
-  for (auto g : this->_frame_generators) {
-    while (g->will_generate_frame(level, timestamp)) {
+  for (auto g : this->_frame_generators.generators()) {
+    // a non-ack_eliciting packet is ready, but we can not send continuous two ack_eliciting packets.
+    while (g->will_generate_frame(level, len, ack_eliciting, seq_num)) {
       // FIXME will_generate_frame should receive more parameters so we don't need extra checks
-      if (g == this->_remote_flow_controller && !this->_stream_manager->will_generate_frame(level, timestamp)) {
-        break;
-      }
-      if (g == this->_stream_manager && this->_path_validator->is_validating()) {
+      if (g == this->_remote_flow_controller && !this->_stream_manager->will_generate_frame(level, len, ack_eliciting, seq_num)) {
         break;
       }
 
+      if (g == this->_stream_manager) {
+        // Don't send DATA frames if current path is not validated
+        // FIXME will_generate_frame should receive more parameters so we don't need extra checks
+        if (auto path = this->_path_manager->get_verified_path(); !path.remote_ep().isValid()) {
+          break;
+        }
+      }
+
       // Common block
-      frame = g->generate_frame(frame_instance_buffer, level, this->_remote_flow_controller->credit(), max_frame_size, timestamp);
+      frame =
+        g->generate_frame(frame_instance_buffer, level, this->_remote_flow_controller->credit(), max_frame_size, len, seq_num);
       if (frame) {
+        // Some frame types must not be sent on Initial and Handshake packets
+        switch (auto t = frame->type(); level) {
+        case QUICEncryptionLevel::INITIAL:
+        case QUICEncryptionLevel::HANDSHAKE:
+          ink_assert(t == QUICFrameType::CRYPTO || t == QUICFrameType::ACK || t == QUICFrameType::PADDING ||
+                     t == QUICFrameType::CONNECTION_CLOSE);
+          break;
+        default:
+          break;
+        }
+
         ++frame_count;
         probing |= frame->is_probing_frame();
         if (frame->is_flow_controlled()) {
@@ -1588,13 +1557,11 @@
           }
         }
 
-        if (!ack_eliciting && frame->type() != QUICFrameType::ACK) {
+        if (!ack_eliciting && frame->ack_eliciting()) {
           ack_eliciting = true;
-          this->_pinger.cancel(level);
         }
 
-        if (frame->type() == QUICFrameType::CRYPTO &&
-            (level == QUICEncryptionLevel::INITIAL || level == QUICEncryptionLevel::HANDSHAKE)) {
+        if (frame->type() == QUICFrameType::CRYPTO && frame->ack_eliciting()) {
           crypto = true;
         }
 
@@ -1608,31 +1575,8 @@
 
   // Schedule a packet
   if (len != 0) {
-    if (level == QUICEncryptionLevel::INITIAL && this->netvc_context == NET_VCONNECTION_OUT) {
-      // Pad with PADDING frames
-      uint64_t min_size = this->_minimum_quic_packet_size();
-      if (this->_av_token) {
-        min_size = min_size - this->_av_token_len;
-      }
-      min_size = std::min(min_size, max_packet_size);
-
-      if (min_size > len) {
-        Ptr<IOBufferBlock> pad_block = _generate_padding_frame(min_size - len);
-        last_block->next             = pad_block;
-        len += pad_block->size();
-      }
-    } else {
-      // Pad with PADDING frames to make sure payload length satisfy the minimum length for sampling for header protection
-      if (MIN_PKT_PAYLOAD_LEN > len) {
-        Ptr<IOBufferBlock> pad_block = _generate_padding_frame(MIN_PKT_PAYLOAD_LEN - len);
-        last_block->next             = pad_block;
-        len += pad_block->size();
-      }
-    }
-
     // Packet is retransmittable if it's not ack only packet
-    packet                              = this->_build_packet(level, first_block, ack_eliciting, probing, crypto);
-    this->_has_ack_eliciting_packet_out = ack_eliciting;
+    packet = this->_build_packet(level, first_block, ack_eliciting, probing, crypto);
   }
 
   return packet;
@@ -1683,7 +1627,8 @@
     *has_non_probing_frame = false;
   }
 
-  error = this->_frame_dispatcher->receive_frames(level, payload, size, ack_only, is_flow_controlled, has_non_probing_frame);
+  error =
+    this->_frame_dispatcher->receive_frames(level, payload, size, ack_only, is_flow_controlled, has_non_probing_frame, &packet);
   if (error != nullptr) {
     return error;
   }
@@ -1987,33 +1932,6 @@
 }
 
 void
-QUICNetVConnection::_schedule_path_validation_timeout(ink_hrtime interval)
-{
-  if (!this->_path_validation_timeout) {
-    QUICConDebug("Schedule %s event in %" PRIu64 "ms", QUICDebugNames::quic_event(QUIC_EVENT_PATH_VALIDATION_TIMEOUT),
-                 interval / HRTIME_MSECOND);
-    this->_path_validation_timeout = this->thread->schedule_in_local(this, interval, QUIC_EVENT_PATH_VALIDATION_TIMEOUT);
-  }
-}
-
-void
-QUICNetVConnection::_unschedule_path_validation_timeout()
-{
-  if (this->_path_validation_timeout) {
-    QUICConDebug("Unschedule %s event", QUICDebugNames::quic_event(QUIC_EVENT_PATH_VALIDATION_TIMEOUT));
-    this->_path_validation_timeout->cancel();
-    this->_path_validation_timeout = nullptr;
-  }
-}
-
-void
-QUICNetVConnection::_close_path_validation_timeout(Event *data)
-{
-  ink_assert(this->_path_validation_timeout == data);
-  this->_path_validation_timeout = nullptr;
-}
-
-void
 QUICNetVConnection::_start_application()
 {
   if (!this->_application_started) {
@@ -2029,7 +1947,7 @@
 
     if (netvc_context == NET_VCONNECTION_IN) {
       if (!this->setSelectedProtocol(app_name, app_name_len)) {
-        this->_handle_error(std::make_unique<QUICConnectionError>(QUICTransErrorCode::VERSION_NEGOTIATION_ERROR));
+        this->_handle_error(std::make_unique<QUICConnectionError>(QUICTransErrorCode::PROTOCOL_VIOLATION));
       } else {
         this->endpoint()->handleEvent(NET_EVENT_ACCEPT, this);
       }
@@ -2055,15 +1973,22 @@
 
     SET_HANDLER((NetVConnHandler)&QUICNetVConnection::state_connection_established);
 
+    std::shared_ptr<const QUICTransportParameters> remote_tp = this->_handshake_handler->remote_transport_parameters();
+
+    uint64_t active_cid_limit = remote_tp->getAsUInt(QUICTransportParameterId::ACTIVE_CONNECTION_ID_LIMIT);
+    if (active_cid_limit) {
+      this->_alt_con_manager->set_remote_active_cid_limit(active_cid_limit);
+    }
+
     if (this->direction() == NET_VCONNECTION_OUT) {
-      std::shared_ptr<const QUICTransportParameters> remote_tp = this->_handshake_handler->remote_transport_parameters();
-      const uint8_t *pref_addr_buf;
       uint16_t len;
-      pref_addr_buf          = remote_tp->getAsBytes(QUICTransportParameterId::PREFERRED_ADDRESS, len);
-      this->_alt_con_manager = new QUICAltConnectionManager(this, *this->_ctable, this->_peer_quic_connection_id,
-                                                            this->_quic_config->instance_id(), 1, {pref_addr_buf, len});
-      this->_frame_generators.push_back(this->_alt_con_manager);
-      this->_frame_dispatcher->add_handler(this->_alt_con_manager);
+      const uint8_t *pref_addr_buf = remote_tp->getAsBytes(QUICTransportParameterId::PREFERRED_ADDRESS, len);
+      if (pref_addr_buf) {
+        this->_alt_con_manager->set_remote_preferred_address({pref_addr_buf, len});
+      }
+    } else {
+      QUICPath trusted_path = {this->local_addr, this->remote_addr};
+      this->_path_manager->set_trusted_path(trusted_path);
     }
   } else {
     // Illegal state change
@@ -2135,7 +2060,6 @@
 QUICNetVConnection::_switch_to_close_state()
 {
   this->_unschedule_closing_timeout();
-  this->_unschedule_path_validation_timeout();
 
   if (this->_complete_handshake_if_possible() != 0) {
     QUICConDebug("Switching state without handshake completion");
@@ -2155,13 +2079,12 @@
 }
 
 void
-QUICNetVConnection::_validate_new_path()
+QUICNetVConnection::_validate_new_path(const QUICPath &path)
 {
-  this->_path_validator->validate();
   // Not sure how long we should wait. The spec says just "enough time".
   // Use the same time amount as the closing timeout.
   ink_hrtime rto = this->_rtt_measure.current_pto_period();
-  this->_schedule_path_validation_timeout(3 * rto);
+  this->_path_manager->open_new_path(path, 3 * rto);
 }
 
 void
@@ -2253,6 +2176,7 @@
   }
 
   if (this->connection_id() == dcid) {
+    QUICConDebug("Maybe NAT rebinding");
     // On client side (NET_VCONNECTION_OUT), nothing to do any more
     if (this->netvc_context == NET_VCONNECTION_IN) {
       Connection con;
@@ -2260,9 +2184,12 @@
       this->con.move(con);
       this->set_remote_addr();
       this->_udp_con = p.udp_con();
-      this->_validate_new_path();
+
+      QUICPath new_path = {p.to(), p.from()};
+      this->_validate_new_path(new_path);
     }
   } else {
+    QUICConDebug("Different CID");
     if (this->_alt_con_manager->migrate_to(dcid, this->_reset_token)) {
       // DCID of received packet is local cid
       this->_update_local_cid(dcid);
@@ -2276,7 +2203,9 @@
         this->_udp_con = p.udp_con();
 
         this->_update_peer_cid(this->_alt_con_manager->migrate_to_alt_cid());
-        this->_validate_new_path();
+
+        QUICPath new_path = {this->local_addr, con.addr};
+        this->_validate_new_path(new_path);
       }
     } else {
       char dcid_str[QUICConnectionId::MAX_HEX_STR_LENGTH];
@@ -2298,13 +2227,12 @@
   ink_assert(this->netvc_context == NET_VCONNECTION_OUT);
 
   QUICConnectionErrorUPtr error = nullptr;
-  ink_hrtime timestamp          = Thread::get_hrtime();
 
   std::shared_ptr<const QUICTransportParameters> remote_tp = this->_handshake_handler->remote_transport_parameters();
 
-  if (this->_connection_migration_initiated || remote_tp->contains(QUICTransportParameterId::DISABLE_MIGRATION) ||
+  if (this->_connection_migration_initiated || remote_tp->contains(QUICTransportParameterId::DISABLE_ACTIVE_MIGRATION) ||
       !this->_alt_con_manager->is_ready_to_migrate() ||
-      this->_alt_con_manager->will_generate_frame(QUICEncryptionLevel::ONE_RTT, timestamp)) {
+      this->_alt_con_manager->will_generate_frame(QUICEncryptionLevel::ONE_RTT, 0, true, this->_seq_num++)) {
     return error;
   }
 
@@ -2313,7 +2241,9 @@
 
   this->_update_peer_cid(this->_alt_con_manager->migrate_to_alt_cid());
 
-  this->_validate_new_path();
+  auto current_path = this->_path_manager->get_verified_path();
+  QUICPath new_path = {current_path.local_ep(), current_path.remote_ep()};
+  this->_validate_new_path(new_path);
 
   return error;
 }
@@ -2321,10 +2251,9 @@
 void
 QUICNetVConnection::_handle_periodic_ack_event()
 {
-  ink_hrtime timestamp = Thread::get_hrtime();
-  bool need_schedule   = false;
+  bool need_schedule = false;
   for (int i = static_cast<int>(this->_minimum_encryption_level); i <= static_cast<int>(QUICEncryptionLevel::ONE_RTT); ++i) {
-    if (this->_ack_frame_manager.will_generate_frame(QUIC_ENCRYPTION_LEVELS[i], timestamp)) {
+    if (this->_ack_frame_manager.will_generate_frame(QUIC_ENCRYPTION_LEVELS[i], 0, true, this->_seq_num++)) {
       need_schedule = true;
       break;
     }
@@ -2336,68 +2265,3 @@
     this->_schedule_packet_write_ready();
   }
 }
-
-void
-QUICNetVConnection::_handle_path_validation_timeout(Event *data)
-{
-  this->_close_path_validation_timeout(data);
-  if (this->_path_validator->is_validated()) {
-    QUICConDebug("Path validated");
-    this->_alt_con_manager->drop_cid(this->_peer_old_quic_connection_id);
-    // FIXME This is a kind of workaround for connection migration.
-    // This PING make peer to send an ACK frame so that ATS can detect packet loss.
-    // It would be better if QUICLossDetector could detect the loss in another way.
-    this->ping();
-  } else {
-    QUICConDebug("Path validation failed");
-    this->_switch_to_close_state();
-  }
-}
-
-// QUICFrameGenerator
-bool
-QUICNetVConnection::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp)
-{
-  if (!this->_is_level_matched(level)) {
-    return false;
-  }
-
-  return !this->_is_resumption_token_sent;
-}
-
-QUICFrame *
-QUICNetVConnection::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                                   ink_hrtime timestamp)
-{
-  QUICFrame *frame = nullptr;
-
-  if (!this->_is_level_matched(level)) {
-    return frame;
-  }
-
-  if (this->_is_resumption_token_sent) {
-    return frame;
-  }
-
-  if (this->direction() == NET_VCONNECTION_IN) {
-    // TODO Make expiration period configurable
-    QUICResumptionToken token(this->get_remote_endpoint(), this->connection_id(), Thread::get_hrtime() + HRTIME_HOURS(24));
-    frame = QUICFrameFactory::create_new_token_frame(buf, token, this->_issue_frame_id(), this);
-    if (frame) {
-      if (frame->size() < maximum_frame_size) {
-        this->_is_resumption_token_sent = true;
-      } else {
-        // Cancel generating frame
-        frame = nullptr;
-      }
-    }
-  }
-
-  return frame;
-}
-
-void
-QUICNetVConnection::_on_frame_lost(QUICFrameInformationUPtr &info)
-{
-  this->_is_resumption_token_sent = false;
-}
diff --git a/iocore/net/QUICPacketHandler.cc b/iocore/net/QUICPacketHandler.cc
index fc906b2..230acbb 100644
--- a/iocore/net/QUICPacketHandler.cc
+++ b/iocore/net/QUICPacketHandler.cc
@@ -30,8 +30,7 @@
 #include "QUICDebugNames.h"
 #include "QUICEvents.h"
 
-static constexpr int LONG_HDR_OFFSET_CONNECTION_ID = 6;
-static constexpr char debug_tag[]                  = "quic_sec";
+static constexpr char debug_tag[] = "quic_sec";
 
 #define QUICDebug(fmt, ...) Debug(debug_tag, fmt, ##__VA_ARGS__)
 #define QUICDebugQC(qc, fmt, ...) Debug(debug_tag, "[%s] " fmt, qc->cids().data(), ##__VA_ARGS__)
@@ -381,9 +380,10 @@
   }
 
   // TODO: refine packet parsers in here, QUICPacketLongHeader, and QUICPacketReceiveQueue
-  size_t token_length            = 0;
-  uint8_t token_length_field_len = 0;
-  if (!QUICPacketLongHeader::token_length(token_length, &token_length_field_len, buf, buf_len)) {
+  size_t token_length              = 0;
+  uint8_t token_length_field_len   = 0;
+  size_t token_length_field_offset = 0;
+  if (!QUICPacketLongHeader::token_length(token_length, token_length_field_len, token_length_field_offset, buf, buf_len)) {
     return -1;
   }
 
@@ -393,19 +393,18 @@
     local_cid.randomize();
     QUICPacketUPtr retry_packet = QUICPacketFactory::create_retry_packet(scid, local_cid, dcid, token);
 
+    QUICDebug("[TX] %s packet ODCID=%" PRIx64, QUICDebugNames::packet_type(retry_packet->type()),
+              static_cast<uint64_t>(static_cast<const QUICPacketLongHeader &>(retry_packet->header()).original_dcid()));
     this->_send_packet(*retry_packet, connection, from, 1200, nullptr, 0);
 
     return -2;
   } else {
-    uint8_t dcil, scil;
-    QUICPacketLongHeader::dcil(dcil, buf, buf_len);
-    QUICPacketLongHeader::scil(scil, buf, buf_len);
-    const uint8_t *token = buf + LONG_HDR_OFFSET_CONNECTION_ID + dcil + scil + token_length_field_len;
+    size_t token_offset = token_length_field_offset + token_length_field_len;
 
-    if (QUICAddressValidationToken::type(token) == QUICAddressValidationToken::Type::RETRY) {
-      QUICRetryToken token1(token, token_length);
-      if (token1.is_valid(from)) {
-        *original_cid = token1.original_dcid();
+    if (QUICAddressValidationToken::type(buf + token_offset) == QUICAddressValidationToken::Type::RETRY) {
+      QUICRetryToken token(buf + token_offset, token_length);
+      if (token.is_valid(from)) {
+        *original_cid = token.original_dcid();
         return 0;
       } else {
         return -3;
diff --git a/iocore/net/quic/Makefile.am b/iocore/net/quic/Makefile.am
index e13d1de..73c4e5f 100644
--- a/iocore/net/quic/Makefile.am
+++ b/iocore/net/quic/Makefile.am
@@ -56,7 +56,7 @@
   QUICVersionNegotiator.cc \
   QUICLossDetector.cc \
   QUICStreamManager.cc \
-  QUICCongestionController.cc \
+  QUICNewRenoCongestionController.cc \
   QUICFlowController.cc \
   QUICStreamState.cc \
   QUICStream.cc \
@@ -82,6 +82,7 @@
   QUICApplicationMap.cc \
   QUICIncomingFrameBuffer.cc \
   QUICPacketReceiveQueue.cc \
+  QUICPathManager.cc \
   QUICPathValidator.cc \
   QUICPinger.cc \
   QUICFrameGenerator.cc \
@@ -90,7 +91,10 @@
   QUICBidirectionalStream.cc \
   QUICCryptoStream.cc \
   QUICUnidirectionalStream.cc \
-  QUICStreamFactory.cc
+  QUICStreamFactory.cc \
+  QUICPadder.cc \
+  QUICContext.cc \
+  QUICTokenCreator.cc
 
 #
 # Check Programs
@@ -109,6 +113,7 @@
   test_QUICPacket \
   test_QUICPacketHeaderProtector \
   test_QUICPacketFactory \
+  test_QUICPathValidator \
   test_QUICStream \
   test_QUICStreamManager \
   test_QUICStreamState \
@@ -117,7 +122,8 @@
   test_QUICTypeUtil \
   test_QUICVersionNegotiator \
   test_QUICFrameRetransmitter \
-  test_QUICAddrVerifyState
+  test_QUICAddrVerifyState \
+  test_QUICPinger
 
 TESTS = $(check_PROGRAMS)
 
@@ -226,6 +232,13 @@
   $(test_main_SOURCES) \
   ./test/test_QUICPacketFactory.cc
 
+test_QUICPathValidator_CPPFLAGS = $(test_CPPFLAGS)
+test_QUICPathValidator_LDFLAGS = @AM_LDFLAGS@
+test_QUICPathValidator_LDADD = $(test_LDADD)
+test_QUICPathValidator_SOURCES = \
+  $(test_main_SOURCES) \
+  ./test/test_QUICPathValidator.cc
+
 test_QUICPacketHeaderProtector_CPPFLAGS = $(test_CPPFLAGS)
 test_QUICPacketHeaderProtector_LDFLAGS = @AM_LDFLAGS@
 test_QUICPacketHeaderProtector_LDADD = $(test_LDADD)
@@ -296,6 +309,13 @@
   $(test_main_SOURCES) \
   ./test/test_QUICAddrVerifyState.cc
 
+test_QUICPinger_CPPFLAGS = $(test_CPPFLAGS)
+test_QUICPinger_LDFLAGS = @AM_LDFLAGS@
+test_QUICPinger_LDADD = $(test_LDADD)
+test_QUICPinger_SOURCES = \
+  $(test_main_SOURCES) \
+  ./test/test_QUICPinger.cc
+
 #
 # clang-tidy
 #
diff --git a/iocore/net/quic/Mock.h b/iocore/net/quic/Mock.h
index d4692b0..535dff5 100644
--- a/iocore/net/quic/Mock.h
+++ b/iocore/net/quic/Mock.h
@@ -30,13 +30,147 @@
 #include "QUICLossDetector.h"
 #include "QUICEvents.h"
 
+class MockQUICContext;
+
 using namespace std::literals;
-std::string_view negotiated_application_name_sv = "h3-20"sv;
+std::string_view negotiated_application_name_sv = "h3-23"sv;
+
+class MockQUICLDConfig : public QUICLDConfig
+{
+  uint32_t
+  packet_threshold() const
+  {
+    return 3;
+  }
+
+  float
+  time_threshold() const
+  {
+    return 1.25;
+  }
+
+  ink_hrtime
+  granularity() const
+  {
+    return HRTIME_MSECONDS(1);
+  }
+
+  ink_hrtime
+  initial_rtt() const
+  {
+    return HRTIME_MSECONDS(100);
+  }
+};
+
+class MockQUICCCConfig : public QUICCCConfig
+{
+  uint32_t
+  max_datagram_size() const
+  {
+    return 1200;
+  }
+
+  uint32_t
+  initial_window() const
+  {
+    return 10;
+  }
+
+  uint32_t
+  minimum_window() const
+  {
+    return 2;
+  }
+
+  float
+  loss_reduction_factor() const
+  {
+    return 0.5;
+  }
+
+  uint32_t
+  persistent_congestion_threshold() const
+  {
+    return 2;
+  }
+};
+
+class MockQUICConnectionInfoProvider : public QUICConnectionInfoProvider
+{
+  QUICConnectionId
+  connection_id() const override
+  {
+    return {reinterpret_cast<const uint8_t *>("\x00"), 1};
+  }
+
+  QUICConnectionId
+  peer_connection_id() const override
+  {
+    return {reinterpret_cast<const uint8_t *>("\x00"), 1};
+  }
+
+  QUICConnectionId
+  original_connection_id() const override
+  {
+    return {reinterpret_cast<const uint8_t *>("\x00"), 1};
+  }
+
+  QUICConnectionId
+  first_connection_id() const override
+  {
+    return {reinterpret_cast<const uint8_t *>("\x00"), 1};
+  }
+
+  const QUICFiveTuple
+  five_tuple() const override
+  {
+    return QUICFiveTuple();
+  }
+
+  std::string_view
+  cids() const override
+  {
+    using namespace std::literals;
+    return std::string_view("00000000-00000000"sv);
+  }
+
+  uint32_t
+  pmtu() const override
+  {
+    return 1280;
+  }
+
+  NetVConnectionContext_t
+  direction() const override
+  {
+    return NET_VCONNECTION_OUT;
+  }
+
+  int
+  select_next_protocol(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in,
+                       unsigned inlen) const override
+  {
+    return SSL_TLSEXT_ERR_OK;
+  }
+
+  bool
+  is_closed() const override
+  {
+    return false;
+  }
+
+  std::string_view
+  negotiated_application_name() const override
+  {
+    return negotiated_application_name_sv;
+  }
+};
 
 class MockQUICStreamManager : public QUICStreamManager
 {
 public:
-  MockQUICStreamManager() : QUICStreamManager() {}
+  MockQUICStreamManager(QUICConnectionInfoProvider *info) : QUICStreamManager(info, nullptr, nullptr) {}
+
   // Override
   virtual QUICConnectionErrorUPtr
   handle_frame(QUICEncryptionLevel level, const QUICFrame &f) override
@@ -263,92 +397,18 @@
   int _transmit_count   = 0;
   int _retransmit_count = 0;
   Ptr<ProxyMutex> _mutex;
-  int _totalFrameCount = 0;
-  int _frameCount[256] = {0};
-  MockQUICStreamManager _stream_manager;
+  int _totalFrameCount                  = 0;
+  int _frameCount[256]                  = {0};
+  MockQUICStreamManager _stream_manager = {this};
 
   QUICTransportParametersInEncryptedExtensions dummy_transport_parameters();
   NetVConnectionContext_t _direction;
 };
 
-class MockQUICConnectionInfoProvider : public QUICConnectionInfoProvider
-{
-  QUICConnectionId
-  connection_id() const override
-  {
-    return {reinterpret_cast<const uint8_t *>("\x00"), 1};
-  }
-
-  QUICConnectionId
-  peer_connection_id() const override
-  {
-    return {reinterpret_cast<const uint8_t *>("\x00"), 1};
-  }
-
-  QUICConnectionId
-  original_connection_id() const override
-  {
-    return {reinterpret_cast<const uint8_t *>("\x00"), 1};
-  }
-
-  QUICConnectionId
-  first_connection_id() const override
-  {
-    return {reinterpret_cast<const uint8_t *>("\x00"), 1};
-  }
-
-  const QUICFiveTuple
-  five_tuple() const override
-  {
-    return QUICFiveTuple();
-  }
-
-  std::string_view
-  cids() const override
-  {
-    using namespace std::literals;
-    return std::string_view("00000000-00000000"sv);
-  }
-
-  uint32_t
-  pmtu() const override
-  {
-    return 1280;
-  }
-
-  NetVConnectionContext_t
-  direction() const override
-  {
-    return NET_VCONNECTION_OUT;
-  }
-
-  int
-  select_next_protocol(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in,
-                       unsigned inlen) const override
-  {
-    return SSL_TLSEXT_ERR_OK;
-  }
-
-  bool
-  is_closed() const override
-  {
-    return false;
-  }
-
-  std::string_view
-  negotiated_application_name() const override
-  {
-    return negotiated_application_name_sv;
-  }
-};
-
 class MockQUICCongestionController : public QUICCongestionController
 {
 public:
-  MockQUICCongestionController(QUICConnectionInfoProvider *info, const QUICCCConfig &cc_config)
-    : QUICCongestionController(this->_rtt_measure, info, cc_config)
-  {
-  }
+  MockQUICCongestionController() {}
   // Override
   virtual void
   on_packets_lost(const std::map<QUICPacketNumber, QUICPacketInfo *> &packets) override
@@ -358,6 +418,32 @@
     }
   }
 
+  virtual void
+  on_packet_sent(size_t bytes_sent) override
+  {
+  }
+  virtual void
+  on_packet_acked(const QUICPacketInfo &acked_packet) override
+  {
+  }
+  virtual void
+  process_ecn(const QUICPacketInfo &acked_largest_packet, const QUICAckFrame::EcnSection *ecn_section) override
+  {
+  }
+  virtual void
+  add_extra_credit() override
+  {
+  }
+  virtual void
+  reset() override
+  {
+  }
+  virtual uint32_t
+  credit() const override
+  {
+    return 0;
+  }
+
   // for Test
   int
   getStreamFrameCount()
@@ -388,16 +474,90 @@
 private:
   int _totalFrameCount = 0;
   int _frameCount[256] = {0};
+};
 
+class MockQUICPacketProtectionKeyInfo : public QUICPacketProtectionKeyInfo
+{
+public:
+  const EVP_CIPHER *
+  get_cipher(QUICKeyPhase phase) const override
+  {
+    return EVP_aes_128_gcm();
+  }
+
+  size_t
+  get_tag_len(QUICKeyPhase phase) const override
+  {
+    return EVP_GCM_TLS_TAG_LEN;
+  }
+
+  const size_t *encryption_iv_len(QUICKeyPhase) const override
+  {
+    static size_t dummy = 12;
+    return &dummy;
+  }
+};
+
+class MockQUICContext : public QUICContext, public QUICLDContext, public QUICCCContext
+{
+public:
+  MockQUICContext()
+  {
+    _info      = std::make_unique<MockQUICConnectionInfoProvider>();
+    _key_info  = std::make_unique<MockQUICPacketProtectionKeyInfo>();
+    _ld_config = std::make_unique<MockQUICLDConfig>();
+    _cc_config = std::make_unique<MockQUICCCConfig>();
+  }
+
+  virtual QUICConnectionInfoProvider *
+  connection_info() const override
+  {
+    return _info.get();
+  }
+  virtual QUICConfig::scoped_config
+  config() const override
+  {
+    return _config;
+  }
+  virtual QUICRTTProvider *
+  rtt_provider() const override
+  {
+    return const_cast<QUICRTTMeasure *>(&_rtt_measure);
+  }
+
+  virtual QUICPacketProtectionKeyInfo *
+  key_info() const override
+  {
+    return _key_info.get();
+  }
+
+  virtual QUICLDConfig &
+  ld_config() const override
+  {
+    return *_ld_config;
+  }
+
+  virtual QUICCCConfig &
+  cc_config() const override
+  {
+    return *_cc_config;
+  }
+
+private:
+  QUICConfig::scoped_config _config;
   QUICRTTMeasure _rtt_measure;
+  std::unique_ptr<QUICConnectionInfoProvider> _info;
+  std::unique_ptr<QUICPacketProtectionKeyInfo> _key_info;
+  std::unique_ptr<QUICLDConfig> _ld_config;
+  std::unique_ptr<QUICCCConfig> _cc_config;
 };
 
 class MockQUICLossDetector : public QUICLossDetector
 {
 public:
-  MockQUICLossDetector(QUICConnectionInfoProvider *info, QUICCongestionController *cc, QUICRTTMeasure *rtt_measure,
-                       const QUICLDConfig &ld_config)
-    : QUICLossDetector(info, cc, rtt_measure, ld_config)
+  MockQUICLossDetector(MockQUICContext &context)
+    : QUICLossDetector(context, &_cc, &_rtt_measure, &this->_pinger, &this->_padder),
+      _padder(NetVConnectionContext_t::NET_VCONNECTION_UNSET)
   {
   }
   void
@@ -409,6 +569,12 @@
   on_packet_sent(QUICPacketUPtr packet)
   {
   }
+
+private:
+  QUICPinger _pinger;
+  QUICPadder _padder;
+  QUICRTTMeasure _rtt_measure;
+  MockQUICCongestionController _cc;
 };
 
 class MockQUICApplication : public QUICApplication
@@ -436,26 +602,36 @@
   }
 };
 
-class MockQUICPacketProtectionKeyInfo : public QUICPacketProtectionKeyInfo
+class MockQUICPacket : public QUICPacket
 {
 public:
-  const EVP_CIPHER *
-  get_cipher(QUICKeyPhase phase) const override
+  const IpEndpoint &
+  from() const override
   {
-    return EVP_aes_128_gcm();
+    return this->_from;
   }
 
-  size_t
-  get_tag_len(QUICKeyPhase phase) const override
+  const IpEndpoint &
+  to() const override
   {
-    return EVP_GCM_TLS_TAG_LEN;
+    return this->_to;
   }
 
-  const size_t *encryption_iv_len(QUICKeyPhase) const override
+  void
+  set_to(const IpEndpoint ep)
   {
-    static size_t dummy = 12;
-    return &dummy;
+    this->_to = ep;
   }
+
+  void
+  set_from(const IpEndpoint ep)
+  {
+    this->_from = ep;
+  }
+
+private:
+  IpEndpoint _to;
+  IpEndpoint _from;
 };
 
 class MockQUICHandshakeProtocol : public QUICHandshakeProtocol
@@ -646,14 +822,14 @@
 {
 public:
   bool
-  will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override
+  will_generate_frame(QUICEncryptionLevel level, size_t connection_credit, bool ack_eliciting, uint32_t seq_num) override
   {
     return true;
   }
 
   QUICFrame *
   generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                 ink_hrtime timestamp) override
+                 size_t current_packet_size, uint32_t seq_num) override
   {
     QUICFrame *frame              = QUICFrameFactory::create_ping_frame(buf, 0, this);
     QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
@@ -670,63 +846,3 @@
     lost_frame_count++;
   }
 };
-
-class MockQUICLDConfig : public QUICLDConfig
-{
-  uint32_t
-  packet_threshold() const
-  {
-    return 3;
-  }
-
-  float
-  time_threshold() const
-  {
-    return 1.25;
-  }
-
-  ink_hrtime
-  granularity() const
-  {
-    return HRTIME_MSECONDS(1);
-  }
-
-  ink_hrtime
-  initial_rtt() const
-  {
-    return HRTIME_MSECONDS(100);
-  }
-};
-
-class MockQUICCCConfig : public QUICCCConfig
-{
-  uint32_t
-  max_datagram_size() const
-  {
-    return 1200;
-  }
-
-  uint32_t
-  initial_window() const
-  {
-    return 10;
-  }
-
-  uint32_t
-  minimum_window() const
-  {
-    return 2;
-  }
-
-  float
-  loss_reduction_factor() const
-  {
-    return 0.5;
-  }
-
-  uint32_t
-  persistent_congestion_threshold() const
-  {
-    return 2;
-  }
-};
diff --git a/iocore/net/quic/QUICAckFrameCreator.cc b/iocore/net/quic/QUICAckFrameCreator.cc
index 27923b0..98ca898 100644
--- a/iocore/net/quic/QUICAckFrameCreator.cc
+++ b/iocore/net/quic/QUICAckFrameCreator.cc
@@ -61,7 +61,7 @@
  */
 QUICFrame *
 QUICAckFrameManager::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /* connection_credit */,
-                                    uint16_t maximum_frame_size, ink_hrtime timestamp)
+                                    uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num)
 {
   QUICAckFrame *ack_frame = nullptr;
 
@@ -87,7 +87,8 @@
 }
 
 bool
-QUICAckFrameManager::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp)
+QUICAckFrameManager::will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting,
+                                         uint32_t seq_num)
 {
   // No ACK frame on ZERO_RTT level
   if (!this->_is_level_matched(level) || level == QUICEncryptionLevel::ZERO_RTT) {
diff --git a/iocore/net/quic/QUICAckFrameCreator.h b/iocore/net/quic/QUICAckFrameCreator.h
index 08b50d4..58e28e8 100644
--- a/iocore/net/quic/QUICAckFrameCreator.h
+++ b/iocore/net/quic/QUICAckFrameCreator.h
@@ -100,13 +100,13 @@
   /*
    * Returns true only if should send ack.
    */
-  bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override;
+  bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) override;
 
   /*
    * Calls create directly.
    */
   QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            ink_hrtime timestamp) override;
+                            size_t current_packet_size, uint32_t seq_num) override;
 
   QUICFrameId issue_frame_id();
   uint8_t ack_delay_exponent() const;
diff --git a/iocore/net/quic/QUICAltConnectionManager.cc b/iocore/net/quic/QUICAltConnectionManager.cc
index faca19e..4e39e93 100644
--- a/iocore/net/quic/QUICAltConnectionManager.cc
+++ b/iocore/net/quic/QUICAltConnectionManager.cc
@@ -21,6 +21,9 @@
   limitations under the License.
  */
 
+#include "algorithm"
+#include "tscore/ink_assert.h"
+#include "tscore/ink_defs.h"
 #include "QUICAltConnectionManager.h"
 #include "QUICConnectionTable.h"
 
@@ -30,43 +33,47 @@
 
 QUICAltConnectionManager::QUICAltConnectionManager(QUICConnection *qc, QUICConnectionTable &ctable,
                                                    const QUICConnectionId &peer_initial_cid, uint32_t instance_id,
-                                                   uint8_t num_alt_con, const QUICPreferredAddress &preferred_address)
-  : _qc(qc), _ctable(ctable), _instance_id(instance_id), _nids(num_alt_con)
-{
-  // Sequence number of the initial CID is 0
-  this->_alt_quic_connection_ids_remote.push_back({0, peer_initial_cid, {}, {true}});
-
-  // Sequence number of the preferred address is 1 if available
-  if (preferred_address.is_available()) {
-    this->_alt_quic_connection_ids_remote.push_back({1, preferred_address.cid(), preferred_address.token(), {false}});
-  }
-
-  this->_alt_quic_connection_ids_local = static_cast<AltConnectionInfo *>(ats_malloc(sizeof(AltConnectionInfo) * this->_nids));
-}
-
-QUICAltConnectionManager::QUICAltConnectionManager(QUICConnection *qc, QUICConnectionTable &ctable,
-                                                   const QUICConnectionId &peer_initial_cid, uint32_t instance_id,
-                                                   uint8_t num_alt_con, const IpEndpoint *preferred_endpoint_ipv4,
+                                                   uint8_t local_active_cid_limit, const IpEndpoint *preferred_endpoint_ipv4,
                                                    const IpEndpoint *preferred_endpoint_ipv6)
-  : _qc(qc), _ctable(ctable), _instance_id(instance_id), _nids(num_alt_con)
+  : _qc(qc), _ctable(ctable), _instance_id(instance_id), _local_active_cid_limit(local_active_cid_limit)
 {
   // Sequence number of the initial CID is 0
   this->_alt_quic_connection_ids_remote.push_back({0, peer_initial_cid, {}, {true}});
 
-  this->_alt_quic_connection_ids_local = static_cast<AltConnectionInfo *>(ats_malloc(sizeof(AltConnectionInfo) * this->_nids));
-  this->_init_alt_connection_ids(preferred_endpoint_ipv4, preferred_endpoint_ipv6);
+  if ((preferred_endpoint_ipv4 && !ats_ip_addr_port_eq(*preferred_endpoint_ipv4, qc->five_tuple().source())) ||
+      (preferred_endpoint_ipv6 && !ats_ip_addr_port_eq(*preferred_endpoint_ipv6, qc->five_tuple().source()))) {
+    this->_alt_quic_connection_ids_local[0] = this->_generate_next_alt_con_info();
+    // This alt cid will be advertised via Transport Parameter, so no need to advertise it via NCID frame
+    this->_alt_quic_connection_ids_local[0].advertised = true;
+
+    IpEndpoint empty_endpoint_ipv4;
+    IpEndpoint empty_endpoint_ipv6;
+    empty_endpoint_ipv4.sa.sa_family = AF_UNSPEC;
+    empty_endpoint_ipv6.sa.sa_family = AF_UNSPEC;
+    if (preferred_endpoint_ipv4 == nullptr) {
+      preferred_endpoint_ipv4 = &empty_endpoint_ipv4;
+    }
+    if (preferred_endpoint_ipv6 == nullptr) {
+      preferred_endpoint_ipv6 = &empty_endpoint_ipv6;
+    }
+
+    // FIXME Check nullptr dereference
+    this->_local_preferred_address =
+      new QUICPreferredAddress(*preferred_endpoint_ipv4, *preferred_endpoint_ipv6, this->_alt_quic_connection_ids_local[0].id,
+                               this->_alt_quic_connection_ids_local[0].token);
+  }
 }
 
 QUICAltConnectionManager::~QUICAltConnectionManager()
 {
   ats_free(this->_alt_quic_connection_ids_local);
-  delete this->_preferred_address;
+  delete this->_local_preferred_address;
 }
 
 const QUICPreferredAddress *
 QUICAltConnectionManager::preferred_address() const
 {
-  return this->_preferred_address;
+  return this->_local_preferred_address;
 }
 
 std::vector<QUICFrameType>
@@ -118,32 +125,9 @@
 }
 
 void
-QUICAltConnectionManager::_init_alt_connection_ids(const IpEndpoint *preferred_endpoint_ipv4,
-                                                   const IpEndpoint *preferred_endpoint_ipv6)
+QUICAltConnectionManager::_init_alt_connection_ids()
 {
-  if (preferred_endpoint_ipv4 || preferred_endpoint_ipv6) {
-    this->_alt_quic_connection_ids_local[0] = this->_generate_next_alt_con_info();
-    // This alt cid will be advertised via Transport Parameter
-    this->_alt_quic_connection_ids_local[0].advertised = true;
-
-    IpEndpoint empty_endpoint_ipv4;
-    IpEndpoint empty_endpoint_ipv6;
-    empty_endpoint_ipv4.sa.sa_family = AF_UNSPEC;
-    empty_endpoint_ipv6.sa.sa_family = AF_UNSPEC;
-    if (preferred_endpoint_ipv4 == nullptr) {
-      preferred_endpoint_ipv4 = &empty_endpoint_ipv4;
-    }
-    if (preferred_endpoint_ipv6 == nullptr) {
-      preferred_endpoint_ipv6 = &empty_endpoint_ipv6;
-    }
-
-    // FIXME Check nullptr dereference
-    this->_preferred_address =
-      new QUICPreferredAddress(*preferred_endpoint_ipv4, *preferred_endpoint_ipv6, this->_alt_quic_connection_ids_local[0].id,
-                               this->_alt_quic_connection_ids_local[0].token);
-  }
-
-  for (int i = (preferred_endpoint_ipv4 || preferred_endpoint_ipv6 ? 1 : 0); i < this->_nids; ++i) {
+  for (int i = 0; i < this->_remote_active_cid_limit; ++i) {
     this->_alt_quic_connection_ids_local[i] = this->_generate_next_alt_con_info();
   }
   this->_need_advertise = true;
@@ -152,19 +136,24 @@
 bool
 QUICAltConnectionManager::_update_alt_connection_id(uint64_t chosen_seq_num)
 {
-  for (int i = 0; i < this->_nids; ++i) {
-    if (_alt_quic_connection_ids_local[i].seq_num == chosen_seq_num) {
-      _alt_quic_connection_ids_local[i] = this->_generate_next_alt_con_info();
-      this->_need_advertise             = true;
-      return true;
-    }
-  }
-
   // Seq 0 is special so it's not in the array
   if (chosen_seq_num == 0) {
     return true;
   }
 
+  // Seq 1 is for Preferred Address
+  if (chosen_seq_num == 1) {
+    return true;
+  }
+
+  for (int i = 0; i < this->_remote_active_cid_limit; ++i) {
+    if (this->_alt_quic_connection_ids_local[i].seq_num == chosen_seq_num) {
+      this->_alt_quic_connection_ids_local[i] = this->_generate_next_alt_con_info();
+      this->_need_advertise                   = true;
+      return true;
+    }
+  }
+
   return false;
 }
 
@@ -177,8 +166,15 @@
     error = std::make_unique<QUICConnectionError>(QUICTransErrorCode::PROTOCOL_VIOLATION, "received zero-length cid",
                                                   QUICFrameType::NEW_CONNECTION_ID);
   } else {
-    this->_alt_quic_connection_ids_remote.push_back(
-      {frame.sequence(), frame.connection_id(), frame.stateless_reset_token(), {false}});
+    int unused = std::count_if(this->_alt_quic_connection_ids_remote.begin(), this->_alt_quic_connection_ids_remote.end(),
+                               [](AltConnectionInfo info) { return info.used == false && info.seq_num != 1; });
+    if (unused > this->_local_active_cid_limit) {
+      error = std::make_unique<QUICConnectionError>(QUICTransErrorCode::PROTOCOL_VIOLATION, "received too many alt CIDs",
+                                                    QUICFrameType::NEW_CONNECTION_ID);
+    } else {
+      this->_alt_quic_connection_ids_remote.push_back(
+        {frame.sequence(), frame.connection_id(), frame.stateless_reset_token(), {false}});
+    }
   }
 
   return error;
@@ -214,10 +210,6 @@
 QUICConnectionId
 QUICAltConnectionManager::migrate_to_alt_cid()
 {
-  if (this->_qc->direction() == NET_VCONNECTION_OUT) {
-    this->_init_alt_connection_ids();
-  }
-
   for (auto &info : this->_alt_quic_connection_ids_remote) {
     if (info.used) {
       continue;
@@ -233,7 +225,14 @@
 bool
 QUICAltConnectionManager::migrate_to(const QUICConnectionId &cid, QUICStatelessResetToken &new_reset_token)
 {
-  for (unsigned int i = 0; i < this->_nids; ++i) {
+  if (this->_local_preferred_address) {
+    if (cid == this->_local_preferred_address->cid()) {
+      new_reset_token = this->_local_preferred_address->token();
+      return true;
+    }
+  }
+
+  for (int i = 0; i < this->_remote_active_cid_limit; ++i) {
     AltConnectionInfo &info = this->_alt_quic_connection_ids_local[i];
     if (info.id == cid) {
       // Migrate connection
@@ -260,13 +259,33 @@
 void
 QUICAltConnectionManager::invalidate_alt_connections()
 {
-  for (unsigned int i = 0; i < this->_nids; ++i) {
+  int n = this->_remote_active_cid_limit + ((this->_local_preferred_address == nullptr) ? 1 : 0);
+
+  for (int i = 0; i < n; ++i) {
     this->_ctable.erase(this->_alt_quic_connection_ids_local[i].id, this->_qc);
   }
 }
 
+void
+QUICAltConnectionManager::set_remote_preferred_address(const QUICPreferredAddress &preferred_address)
+{
+  ink_assert(preferred_address.is_available());
+
+  // Sequence number of the preferred address is 1 if available
+  this->_alt_quic_connection_ids_remote.push_back({1, preferred_address.cid(), preferred_address.token(), {false}});
+}
+
+void
+QUICAltConnectionManager::set_remote_active_cid_limit(uint8_t active_cid_limit)
+{
+  this->_remote_active_cid_limit =
+    std::min(static_cast<unsigned int>(active_cid_limit), countof(this->_alt_quic_connection_ids_local));
+  this->_init_alt_connection_ids();
+}
+
 bool
-QUICAltConnectionManager::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp)
+QUICAltConnectionManager::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;
@@ -280,7 +299,7 @@
  */
 QUICFrame *
 QUICAltConnectionManager::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /* connection_credit */,
-                                         uint16_t maximum_frame_size, ink_hrtime timestamp)
+                                         uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num)
 {
   QUICFrame *frame = nullptr;
   if (!this->_is_level_matched(level)) {
@@ -288,10 +307,10 @@
   }
 
   if (this->_need_advertise) {
-    int count = this->_nids;
-    for (int i = 0; i < count; ++i) {
+    for (int i = 0; i < this->_remote_active_cid_limit; ++i) {
       if (!this->_alt_quic_connection_ids_local[i].advertised) {
-        frame = QUICFrameFactory::create_new_connection_id_frame(buf, this->_alt_quic_connection_ids_local[i].seq_num,
+        // FIXME Should send a sequence number for retire_prior_to. Sending 0 for now.
+        frame = QUICFrameFactory::create_new_connection_id_frame(buf, this->_alt_quic_connection_ids_local[i].seq_num, 0,
                                                                  this->_alt_quic_connection_ids_local[i].id,
                                                                  this->_alt_quic_connection_ids_local[i].token);
 
@@ -310,12 +329,11 @@
   }
 
   if (!this->_retired_seq_nums.empty()) {
-    if (auto s = this->_retired_seq_nums.front()) {
-      frame = QUICFrameFactory::create_retire_connection_id_frame(buf, s);
-      this->_records_retire_connection_id_frame(level, static_cast<const QUICRetireConnectionIdFrame &>(*frame));
-      this->_retired_seq_nums.pop();
-      return frame;
-    }
+    auto s = this->_retired_seq_nums.front();
+    frame  = QUICFrameFactory::create_retire_connection_id_frame(buf, s);
+    this->_records_retire_connection_id_frame(level, static_cast<const QUICRetireConnectionIdFrame &>(*frame));
+    this->_retired_seq_nums.pop();
+    return frame;
   }
 
   return frame;
@@ -351,7 +369,7 @@
   switch (info->type) {
   case QUICFrameType::NEW_CONNECTION_ID: {
     AltConnectionInfo *frame_info = reinterpret_cast<AltConnectionInfo *>(info->data);
-    for (int i = 0; i < this->_nids; ++i) {
+    for (int i = 0; i < this->_remote_active_cid_limit; ++i) {
       if (this->_alt_quic_connection_ids_local[i].seq_num == frame_info->seq_num) {
         ink_assert(this->_alt_quic_connection_ids_local[i].advertised);
         this->_alt_quic_connection_ids_local[i].advertised = false;
diff --git a/iocore/net/quic/QUICAltConnectionManager.h b/iocore/net/quic/QUICAltConnectionManager.h
index 501ab99..09b99ca 100644
--- a/iocore/net/quic/QUICAltConnectionManager.h
+++ b/iocore/net/quic/QUICAltConnectionManager.h
@@ -35,16 +35,8 @@
 class QUICAltConnectionManager : public QUICFrameHandler, public QUICFrameGenerator
 {
 public:
-  /**
-   * Constructor for clients
-   */
   QUICAltConnectionManager(QUICConnection *qc, QUICConnectionTable &ctable, const QUICConnectionId &peer_initial_cid,
-                           uint32_t instance_id, uint8_t num_alt_con, const QUICPreferredAddress &preferred_address);
-  /**
-   * Constructor for servers
-   */
-  QUICAltConnectionManager(QUICConnection *qc, QUICConnectionTable &ctable, const QUICConnectionId &peer_initial_cid,
-                           uint32_t instance_id, uint8_t num_alt_con, const IpEndpoint *preferred_endpoint_ipv4 = nullptr,
+                           uint32_t instance_id, uint8_t active_cid_limit, const IpEndpoint *preferred_endpoint_ipv4 = nullptr,
                            const IpEndpoint *preferred_endpoint_ipv6 = nullptr);
   ~QUICAltConnectionManager();
 
@@ -68,6 +60,9 @@
 
   void drop_cid(const QUICConnectionId &cid);
 
+  void set_remote_preferred_address(const QUICPreferredAddress &preferred_address);
+  void set_remote_active_cid_limit(uint8_t active_cid_limit);
+
   /**
    * Invalidate all CIDs prepared
    */
@@ -83,9 +78,9 @@
   virtual QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override;
 
   // QUICFrameGenerator
-  bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override;
+  bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) override;
   QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            ink_hrtime timestamp) override;
+                            size_t current_packet_size, uint32_t seq_num) override;
 
 private:
   struct AltConnectionInfo {
@@ -100,18 +95,18 @@
 
   QUICConnection *_qc = nullptr;
   QUICConnectionTable &_ctable;
-  AltConnectionInfo *_alt_quic_connection_ids_local;
+  AltConnectionInfo _alt_quic_connection_ids_local[8]; // 8 is perhaps enough
   std::vector<AltConnectionInfo> _alt_quic_connection_ids_remote;
   std::queue<uint64_t> _retired_seq_nums;
-  uint32_t _instance_id                    = 0;
-  uint8_t _nids                            = 1;
-  uint64_t _alt_quic_connection_id_seq_num = 0;
-  bool _need_advertise                     = false;
-  QUICPreferredAddress *_preferred_address = nullptr;
+  uint32_t _instance_id                          = 0;
+  uint8_t _local_active_cid_limit                = 0;
+  uint8_t _remote_active_cid_limit               = 0;
+  uint64_t _alt_quic_connection_id_seq_num       = 0;
+  bool _need_advertise                           = false;
+  QUICPreferredAddress *_local_preferred_address = nullptr;
 
   AltConnectionInfo _generate_next_alt_con_info();
-  void _init_alt_connection_ids(const IpEndpoint *preferred_endpoint_ipv4 = nullptr,
-                                const IpEndpoint *preferred_endpoint_ipv6 = nullptr);
+  void _init_alt_connection_ids();
   bool _update_alt_connection_id(uint64_t chosen_seq_num);
 
   void _records_new_connection_id_frame(QUICEncryptionLevel level, const QUICNewConnectionIdFrame &frame);
diff --git a/iocore/net/quic/QUICBidirectionalStream.cc b/iocore/net/quic/QUICBidirectionalStream.cc
index bbd98d9..c028998 100644
--- a/iocore/net/quic/QUICBidirectionalStream.cc
+++ b/iocore/net/quic/QUICBidirectionalStream.cc
@@ -352,15 +352,16 @@
 }
 
 bool
-QUICBidirectionalStream::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp)
+QUICBidirectionalStream::will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting,
+                                             uint32_t seq_num)
 {
-  return this->_local_flow_controller.will_generate_frame(level, timestamp) || !this->is_retransmited_frame_queue_empty() ||
-         this->_write_vio.get_reader()->is_read_avail_more_than(0);
+  return this->_local_flow_controller.will_generate_frame(level, current_packet_size, ack_eliciting, seq_num) ||
+         !this->is_retransmited_frame_queue_empty() || this->_write_vio.get_reader()->is_read_avail_more_than(0);
 }
 
 QUICFrame *
 QUICBidirectionalStream::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit,
-                                        uint16_t maximum_frame_size, ink_hrtime timestamp)
+                                        uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num)
 {
   SCOPED_MUTEX_LOCK(lock, this->_write_vio.mutex, this_ethread());
 
@@ -391,7 +392,7 @@
   }
 
   // MAX_STREAM_DATA
-  frame = this->_local_flow_controller.generate_frame(buf, level, UINT16_MAX, maximum_frame_size, timestamp);
+  frame = this->_local_flow_controller.generate_frame(buf, level, UINT16_MAX, maximum_frame_size, current_packet_size, seq_num);
   if (frame) {
     return frame;
   }
@@ -427,7 +428,8 @@
     uint64_t stream_credit = this->_remote_flow_controller.credit();
     if (stream_credit == 0) {
       // STREAM_DATA_BLOCKED
-      frame = this->_remote_flow_controller.generate_frame(buf, level, UINT16_MAX, maximum_frame_size, timestamp);
+      frame =
+        this->_remote_flow_controller.generate_frame(buf, level, UINT16_MAX, maximum_frame_size, current_packet_size, seq_num);
       return frame;
     }
 
diff --git a/iocore/net/quic/QUICBidirectionalStream.h b/iocore/net/quic/QUICBidirectionalStream.h
index fcbd5d7..d64f899 100644
--- a/iocore/net/quic/QUICBidirectionalStream.h
+++ b/iocore/net/quic/QUICBidirectionalStream.h
@@ -44,9 +44,9 @@
   int state_stream_closed(int event, void *data);
 
   // QUICFrameGenerator
-  bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override;
+  bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) override;
   QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            ink_hrtime timestamp) override;
+                            size_t current_packet_size, uint32_t seq_num) override;
 
   virtual QUICConnectionErrorUPtr recv(const QUICStreamFrame &frame) override;
   virtual QUICConnectionErrorUPtr recv(const QUICMaxStreamDataFrame &frame) override;
diff --git a/iocore/net/quic/QUICConfig.cc b/iocore/net/quic/QUICConfig.cc
index 600cef8..1cfe703 100644
--- a/iocore/net/quic/QUICConfig.cc
+++ b/iocore/net/quic/QUICConfig.cc
@@ -117,7 +117,6 @@
 {
   REC_EstablishStaticConfigInt32U(this->_instance_id, "proxy.config.quic.instance_id");
   REC_EstablishStaticConfigInt32(this->_connection_table_size, "proxy.config.quic.connection_table.size");
-  REC_EstablishStaticConfigInt32U(this->_num_alt_connection_ids, "proxy.config.quic.num_alt_connection_ids");
   REC_EstablishStaticConfigInt32U(this->_stateless_retry, "proxy.config.quic.server.stateless_retry_enabled");
   REC_EstablishStaticConfigInt32U(this->_vn_exercise_enabled, "proxy.config.quic.client.vn_exercise_enabled");
   REC_EstablishStaticConfigInt32U(this->_cm_exercise_enabled, "proxy.config.quic.client.cm_exercise_enabled");
@@ -158,6 +157,8 @@
   REC_EstablishStaticConfigInt32U(this->_ack_delay_exponent_out, "proxy.config.quic.ack_delay_exponent_out");
   REC_EstablishStaticConfigInt32U(this->_max_ack_delay_in, "proxy.config.quic.max_ack_delay_in");
   REC_EstablishStaticConfigInt32U(this->_max_ack_delay_out, "proxy.config.quic.max_ack_delay_out");
+  REC_EstablishStaticConfigInt32U(this->_active_cid_limit_in, "proxy.config.quic.active_cid_limit_in");
+  REC_EstablishStaticConfigInt32U(this->_active_cid_limit_out, "proxy.config.quic.active_cid_limit_out");
 
   // Loss Detection
   REC_EstablishStaticConfigInt32U(this->_ld_packet_threshold, "proxy.config.quic.loss_detection.packet_threshold");
@@ -226,12 +227,6 @@
 }
 
 uint32_t
-QUICConfigParams::num_alt_connection_ids() const
-{
-  return this->_num_alt_connection_ids;
-}
-
-uint32_t
 QUICConfigParams::stateless_retry() const
 {
   return this->_stateless_retry;
@@ -345,6 +340,18 @@
   return this->_max_ack_delay_out;
 }
 
+uint8_t
+QUICConfigParams::active_cid_limit_in() const
+{
+  return this->_active_cid_limit_in;
+}
+
+uint8_t
+QUICConfigParams::active_cid_limit_out() const
+{
+  return this->_active_cid_limit_out;
+}
+
 const char *
 QUICConfigParams::server_supported_groups() const
 {
diff --git a/iocore/net/quic/QUICConfig.h b/iocore/net/quic/QUICConfig.h
index 36ff7af..67106e6 100644
--- a/iocore/net/quic/QUICConfig.h
+++ b/iocore/net/quic/QUICConfig.h
@@ -37,7 +37,6 @@
   void initialize();
 
   uint32_t instance_id() const;
-  uint32_t num_alt_connection_ids() const;
   uint32_t stateless_retry() const;
   uint32_t vn_exercise_enabled() const;
   uint32_t cm_exercise_enabled() const;
@@ -70,6 +69,8 @@
   uint8_t ack_delay_exponent_out() const;
   uint8_t max_ack_delay_in() const;
   uint8_t max_ack_delay_out() const;
+  uint8_t active_cid_limit_in() const;
+  uint8_t active_cid_limit_out() const;
 
   // Loss Detection
   uint32_t ld_packet_threshold() const;
@@ -92,11 +93,10 @@
   // TODO: make configurable
   static const uint8_t _scid_len = 18; //< Length of Source Connection ID
 
-  uint32_t _instance_id            = 0;
-  uint32_t _num_alt_connection_ids = 0;
-  uint32_t _stateless_retry        = 0;
-  uint32_t _vn_exercise_enabled    = 0;
-  uint32_t _cm_exercise_enabled    = 0;
+  uint32_t _instance_id         = 0;
+  uint32_t _stateless_retry     = 0;
+  uint32_t _vn_exercise_enabled = 0;
+  uint32_t _cm_exercise_enabled = 0;
 
   char *_server_supported_groups = nullptr;
   char *_client_supported_groups = nullptr;
@@ -128,19 +128,21 @@
   uint32_t _ack_delay_exponent_out                  = 0;
   uint32_t _max_ack_delay_in                        = 0;
   uint32_t _max_ack_delay_out                       = 0;
+  uint32_t _active_cid_limit_in                     = 0;
+  uint32_t _active_cid_limit_out                    = 0;
 
   // [draft-17 recovery] 6.4.1.  Constants of interest
   uint32_t _ld_packet_threshold = 3;
   float _ld_time_threshold      = 1.25;
   ink_hrtime _ld_granularity    = HRTIME_MSECONDS(1);
-  ink_hrtime _ld_initial_rtt    = HRTIME_MSECONDS(100);
+  ink_hrtime _ld_initial_rtt    = HRTIME_MSECONDS(500);
 
   // [draft-11 recovery] 4.7.1.  Constants of interest
   uint32_t _cc_max_datagram_size               = 1200;
   uint32_t _cc_initial_window_scale            = 10; // Actual initial window size is this value multiplied by the _cc_default_mss
   uint32_t _cc_minimum_window_scale            = 2;  // Actual minimum window size is this value multiplied by the _cc_default_mss
   float _cc_loss_reduction_factor              = 0.5;
-  uint32_t _cc_persistent_congestion_threshold = 2;
+  uint32_t _cc_persistent_congestion_threshold = 3;
 };
 
 class QUICConfig
diff --git a/iocore/net/quic/QUICCongestionController.h b/iocore/net/quic/QUICCongestionController.h
new file mode 100644
index 0000000..46341dc
--- /dev/null
+++ b/iocore/net/quic/QUICCongestionController.h
@@ -0,0 +1,53 @@
+/** @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
+
+struct QUICPacketInfo {
+  // 6.3.1.  Sent Packet Fields
+  QUICPacketNumber packet_number;
+  ink_hrtime time_sent;
+  bool ack_eliciting;
+  bool is_crypto_packet;
+  bool in_flight;
+  size_t sent_bytes;
+
+  // addition
+  QUICPacketType type;
+  std::vector<QUICFrameInfo> frames;
+  QUICPacketNumberSpace pn_space;
+  // end
+};
+
+class QUICCongestionController
+{
+public:
+  virtual ~QUICCongestionController() {}
+  virtual void on_packet_sent(size_t bytes_sent)                                                                    = 0;
+  virtual void on_packet_acked(const QUICPacketInfo &acked_packet)                                                  = 0;
+  virtual void process_ecn(const QUICPacketInfo &acked_largest_packet, const QUICAckFrame::EcnSection *ecn_section) = 0;
+  virtual void on_packets_lost(const std::map<QUICPacketNumber, QUICPacketInfo *> &packets)                         = 0;
+  virtual void add_extra_credit()                                                                                   = 0;
+  virtual void reset()                                                                                              = 0;
+  virtual uint32_t credit() const                                                                                   = 0;
+};
diff --git a/iocore/net/quic/QUICConnection.h b/iocore/net/quic/QUICConnection.h
index d2c3d56..28c5a83 100644
--- a/iocore/net/quic/QUICConnection.h
+++ b/iocore/net/quic/QUICConnection.h
@@ -34,6 +34,7 @@
 class QUICConnectionInfoProvider
 {
 public:
+  virtual ~QUICConnectionInfoProvider() {}
   virtual QUICConnectionId peer_connection_id() const     = 0;
   virtual QUICConnectionId original_connection_id() const = 0;
   virtual QUICConnectionId first_connection_id() const    = 0;
diff --git a/iocore/net/quic/QUICContext.cc b/iocore/net/quic/QUICContext.cc
new file mode 100644
index 0000000..7ccb9e4
--- /dev/null
+++ b/iocore/net/quic/QUICContext.cc
@@ -0,0 +1,147 @@
+/** @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 "QUICContext.h"
+#include "QUICConnection.h"
+#include "QUICLossDetector.h"
+#include "QUICPacketProtectionKeyInfo.h"
+
+class QUICCCConfigQCP : public QUICCCConfig
+{
+public:
+  virtual ~QUICCCConfigQCP() {}
+  QUICCCConfigQCP(const QUICConfigParams *params) : _params(params) {}
+
+  uint32_t
+  max_datagram_size() const override
+  {
+    return this->_params->cc_max_datagram_size();
+  }
+
+  uint32_t
+  initial_window() const override
+  {
+    return this->_params->cc_initial_window();
+  }
+
+  uint32_t
+  minimum_window() const override
+  {
+    return this->_params->cc_minimum_window();
+  }
+
+  float
+  loss_reduction_factor() const override
+  {
+    return this->_params->cc_loss_reduction_factor();
+  }
+
+  uint32_t
+  persistent_congestion_threshold() const override
+  {
+    return this->_params->cc_persistent_congestion_threshold();
+  }
+
+private:
+  const QUICConfigParams *_params;
+};
+
+class QUICLDConfigQCP : public QUICLDConfig
+{
+public:
+  virtual ~QUICLDConfigQCP() {}
+  QUICLDConfigQCP(const QUICConfigParams *params) : _params(params) {}
+
+  uint32_t
+  packet_threshold() const override
+  {
+    return this->_params->ld_packet_threshold();
+  }
+
+  float
+  time_threshold() const override
+  {
+    return this->_params->ld_time_threshold();
+  }
+
+  ink_hrtime
+  granularity() const override
+  {
+    return this->_params->ld_granularity();
+  }
+
+  ink_hrtime
+  initial_rtt() const override
+  {
+    return this->_params->ld_initial_rtt();
+  }
+
+private:
+  const QUICConfigParams *_params;
+};
+
+QUICContextImpl::QUICContextImpl(QUICRTTProvider *rtt, QUICConnectionInfoProvider *info,
+                                 QUICPacketProtectionKeyInfoProvider *key_info)
+  : _key_info(key_info),
+    _connection_info(info),
+    _rtt_provider(rtt),
+    _ld_config(std::make_unique<QUICLDConfigQCP>(_config)),
+    _cc_config(std::make_unique<QUICCCConfigQCP>(_config))
+{
+}
+
+QUICConnectionInfoProvider *
+QUICContextImpl::connection_info() const
+{
+  return _connection_info;
+}
+
+QUICConfig::scoped_config
+QUICContextImpl::config() const
+{
+  return _config;
+}
+
+QUICPacketProtectionKeyInfoProvider *
+QUICContextImpl::key_info() const
+{
+  return _key_info;
+}
+
+QUICRTTProvider *
+QUICContextImpl::rtt_provider() const
+{
+  return _rtt_provider;
+}
+
+QUICLDConfig &
+QUICContextImpl::ld_config() const
+{
+  return *_ld_config;
+}
+
+QUICCCConfig &
+QUICContextImpl::cc_config() const
+{
+  return *_cc_config;
+}
diff --git a/iocore/net/quic/QUICContext.h b/iocore/net/quic/QUICContext.h
new file mode 100644
index 0000000..57e6c0b
--- /dev/null
+++ b/iocore/net/quic/QUICContext.h
@@ -0,0 +1,84 @@
+/** @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 "QUICConnection.h"
+#include "QUICConfig.h"
+
+class QUICRTTProvider;
+class QUICCongestionController;
+class QUICPacketProtectionKeyInfoProvider;
+
+class QUICNetVConnection;
+
+class QUICContext
+{
+public:
+  virtual ~QUICContext(){};
+  virtual QUICConnectionInfoProvider *connection_info() const = 0;
+  virtual QUICConfig::scoped_config config() const            = 0;
+};
+
+class QUICLDContext
+{
+public:
+  virtual ~QUICLDContext() {}
+  virtual QUICConnectionInfoProvider *connection_info() const   = 0;
+  virtual QUICLDConfig &ld_config() const                       = 0;
+  virtual QUICPacketProtectionKeyInfoProvider *key_info() const = 0;
+};
+
+class QUICCCContext
+{
+public:
+  virtual ~QUICCCContext() {}
+  virtual QUICConnectionInfoProvider *connection_info() const = 0;
+  virtual QUICCCConfig &cc_config() const                     = 0;
+  virtual QUICRTTProvider *rtt_provider() const               = 0;
+};
+
+class QUICContextImpl : public QUICContext, public QUICCCContext, public QUICLDContext
+{
+public:
+  QUICContextImpl(QUICRTTProvider *rtt, QUICConnectionInfoProvider *info, QUICPacketProtectionKeyInfoProvider *key_info);
+
+  virtual QUICConnectionInfoProvider *connection_info() const override;
+  virtual QUICConfig::scoped_config config() const override;
+  virtual QUICRTTProvider *rtt_provider() const override;
+
+  // TODO should be more abstract
+  virtual QUICPacketProtectionKeyInfoProvider *key_info() const override;
+
+  virtual QUICLDConfig &ld_config() const override;
+  virtual QUICCCConfig &cc_config() const override;
+
+private:
+  QUICConfig::scoped_config _config;
+  QUICPacketProtectionKeyInfoProvider *_key_info = nullptr;
+  QUICConnectionInfoProvider *_connection_info   = nullptr;
+  QUICRTTProvider *_rtt_provider                 = nullptr;
+
+  std::unique_ptr<QUICLDConfig> _ld_config = nullptr;
+  std::unique_ptr<QUICCCConfig> _cc_config = nullptr;
+};
diff --git a/iocore/net/quic/QUICCryptoStream.cc b/iocore/net/quic/QUICCryptoStream.cc
index f3b702c..ba3afa2 100644
--- a/iocore/net/quic/QUICCryptoStream.cc
+++ b/iocore/net/quic/QUICCryptoStream.cc
@@ -102,7 +102,7 @@
 }
 
 bool
-QUICCryptoStream::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp)
+QUICCryptoStream::will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num)
 {
   return this->_write_buffer_reader->is_read_avail_more_than(0) || !this->is_retransmited_frame_queue_empty();
 }
@@ -112,7 +112,7 @@
  */
 QUICFrame *
 QUICCryptoStream::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /* connection_credit */,
-                                 uint16_t maximum_frame_size, ink_hrtime timestamp)
+                                 uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num)
 {
   QUICConnectionErrorUPtr error = nullptr;
 
diff --git a/iocore/net/quic/QUICCryptoStream.h b/iocore/net/quic/QUICCryptoStream.h
index 6da3b2f..42f99db 100644
--- a/iocore/net/quic/QUICCryptoStream.h
+++ b/iocore/net/quic/QUICCryptoStream.h
@@ -53,9 +53,9 @@
   int64_t write(const uint8_t *buf, int64_t len);
 
   // QUICFrameGenerator
-  bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override;
+  bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) override;
   QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            ink_hrtime timestamp) override;
+                            size_t current_packet_size, uint32_t seq_num) override;
 
 private:
   void _on_frame_acked(QUICFrameInformationUPtr &info) override;
diff --git a/iocore/net/quic/QUICDebugNames.cc b/iocore/net/quic/QUICDebugNames.cc
index b70d959..bf9d2d8 100644
--- a/iocore/net/quic/QUICDebugNames.cc
+++ b/iocore/net/quic/QUICDebugNames.cc
@@ -121,22 +121,20 @@
     return "INTERNAL_ERROR";
   case static_cast<uint16_t>(QUICTransErrorCode::FLOW_CONTROL_ERROR):
     return "FLOW_CONTROL_ERROR";
-  case static_cast<uint16_t>(QUICTransErrorCode::STREAM_ID_ERROR):
-    return "STREAM_ID_ERROR";
+  case static_cast<uint16_t>(QUICTransErrorCode::STREAM_LIMIT_ERROR):
+    return "STREAM_LIMIT_ERROR";
   case static_cast<uint16_t>(QUICTransErrorCode::STREAM_STATE_ERROR):
     return "STREAM_STATE_ERROR";
-  case static_cast<uint16_t>(QUICTransErrorCode::FINAL_OFFSET_ERROR):
-    return "FINAL_OFFSET_ERROR";
+  case static_cast<uint16_t>(QUICTransErrorCode::FINAL_SIZE_ERROR):
+    return "FINAL_SIZE_ERROR";
   case static_cast<uint16_t>(QUICTransErrorCode::FRAME_ENCODING_ERROR):
     return "FRAME_ENCODING_ERROR";
   case static_cast<uint16_t>(QUICTransErrorCode::TRANSPORT_PARAMETER_ERROR):
     return "TRANSPORT_PARAMETER_ERROR";
-  case static_cast<uint16_t>(QUICTransErrorCode::VERSION_NEGOTIATION_ERROR):
-    return "VERSION_NEGOTIATION_ERROR";
   case static_cast<uint16_t>(QUICTransErrorCode::PROTOCOL_VIOLATION):
     return "PROTOCOL_VIOLATION";
-  case static_cast<uint16_t>(QUICTransErrorCode::INVALID_MIGRATION):
-    return "INVALID_MIGRATION";
+  case static_cast<uint16_t>(QUICTransErrorCode::CRYPTO_BUFFER_EXCEEDED):
+    return "CRYPTO_BUFFER_EXCEEDED";
   default:
     if (0x0100 <= code && code <= 0x01FF) {
       return "CRYPTO_ERROR";
@@ -193,8 +191,8 @@
     return "ACK_DELAY_EXPONENT";
   case QUICTransportParameterId::INITIAL_MAX_STREAMS_UNI:
     return "INITIAL_MAX_STREAMS_UNI";
-  case QUICTransportParameterId::DISABLE_MIGRATION:
-    return "DISABLE_MIGRATION";
+  case QUICTransportParameterId::DISABLE_ACTIVE_MIGRATION:
+    return "DISABLE_ACTIVE_MIGRATION";
   case QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_BIDI_REMOTE:
     return "INITIAL_MAX_STREAM_DATA_BIDI_REMOTE";
   case QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI:
@@ -203,6 +201,8 @@
     return "INITIAL_MAX_ACK_DELAY";
   case QUICTransportParameterId::ORIGINAL_CONNECTION_ID:
     return "INITIAL_ORIGINAL_CONNECTION_ID";
+  case QUICTransportParameterId::ACTIVE_CONNECTION_ID_LIMIT:
+    return "ACTIVE_CONNECTION_ID_LIMIT";
   default:
     return "UNKNOWN";
   }
diff --git a/iocore/net/quic/QUICFlowController.cc b/iocore/net/quic/QUICFlowController.cc
index 7c2e3db..b5275d0 100644
--- a/iocore/net/quic/QUICFlowController.cc
+++ b/iocore/net/quic/QUICFlowController.cc
@@ -96,7 +96,7 @@
 
 // For RemoteFlowController, caller of this function should also check QUICStreamManager::will_generate_frame()
 bool
-QUICFlowController::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp)
+QUICFlowController::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;
@@ -110,7 +110,7 @@
  */
 QUICFrame *
 QUICFlowController::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /* connection_credit */,
-                                   uint16_t maximum_frame_size, ink_hrtime timestamp)
+                                   uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num)
 {
   QUICFrame *frame = nullptr;
 
diff --git a/iocore/net/quic/QUICFlowController.h b/iocore/net/quic/QUICFlowController.h
index c3cb637..62adf9f 100644
--- a/iocore/net/quic/QUICFlowController.h
+++ b/iocore/net/quic/QUICFlowController.h
@@ -60,9 +60,9 @@
   virtual void set_limit(QUICOffset limit);
 
   // QUICFrameGenerator
-  bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override;
+  bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) override;
   QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            ink_hrtime timestamp) override;
+                            size_t current_packet_size, uint32_t seq_num) override;
 
 protected:
   QUICFlowController(uint64_t initial_limit) : _limit(initial_limit) {}
diff --git a/iocore/net/quic/QUICFrame.cc b/iocore/net/quic/QUICFrame.cc
index 903c4e9..0c4e0ba 100644
--- a/iocore/net/quic/QUICFrame.cc
+++ b/iocore/net/quic/QUICFrame.cc
@@ -59,6 +59,20 @@
 }
 
 bool
+QUICFrame::ack_eliciting() const
+{
+  auto type = this->type();
+
+  return type != QUICFrameType::PADDING && type != QUICFrameType::ACK;
+}
+
+const QUICPacket *
+QUICFrame::packet() const
+{
+  return this->_packet;
+}
+
+bool
 QUICFrame::is_probing_frame() const
 {
   return false;
@@ -105,22 +119,6 @@
   }
 }
 
-Ptr<IOBufferBlock>
-QUICFrame::to_io_buffer_block(size_t limit) const
-{
-  // FIXME Each classes should override this and drop store().
-  // This just wraps store() for now.
-
-  Ptr<IOBufferBlock> block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
-  block->alloc(iobuffer_size_to_index(limit));
-
-  size_t written_len = 0;
-  this->store(reinterpret_cast<uint8_t *>(block->start()), &written_len, limit);
-  block->fill(written_len);
-
-  return block;
-}
-
 int
 QUICFrame::debug_msg(char *msg, size_t msg_len) const
 {
@@ -149,9 +147,9 @@
 {
 }
 
-QUICStreamFrame::QUICStreamFrame(const uint8_t *buf, size_t len)
+QUICStreamFrame::QUICStreamFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
 {
-  this->parse(buf, len);
+  this->parse(buf, len, packet);
 }
 
 QUICStreamFrame::QUICStreamFrame(const QUICStreamFrame &o)
@@ -166,10 +164,11 @@
 }
 
 void
-QUICStreamFrame::parse(const uint8_t *buf, size_t len)
+QUICStreamFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
+  this->_packet = packet;
 
   uint8_t *pos            = const_cast<uint8_t *>(buf);
   this->_has_offset_field = (buf[0] & 0x04) != 0; // "O" of "0b00010OLF"
@@ -261,12 +260,6 @@
   return true;
 }
 
-size_t
-QUICStreamFrame::store(uint8_t *buf, size_t *len, size_t limit) const
-{
-  return this->store(buf, len, limit, true);
-}
-
 int
 QUICStreamFrame::debug_msg(char *msg, size_t msg_len) const
 {
@@ -333,13 +326,6 @@
   return *len;
 }
 
-size_t
-QUICStreamFrame::store(uint8_t *buf, size_t *len, size_t limit, bool include_length_field) const
-{
-  ink_assert(!"Call to_io_buffer_block() instead");
-  return 0;
-}
-
 QUICStreamId
 QUICStreamFrame::stream_id() const
 {
@@ -406,9 +392,9 @@
 {
 }
 
-QUICCryptoFrame::QUICCryptoFrame(const uint8_t *buf, size_t len)
+QUICCryptoFrame::QUICCryptoFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
 {
-  this->parse(buf, len);
+  this->parse(buf, len, packet);
 }
 
 QUICCryptoFrame::QUICCryptoFrame(const QUICCryptoFrame &o)
@@ -417,11 +403,12 @@
 }
 
 void
-QUICCryptoFrame::parse(const uint8_t *buf, size_t len)
+QUICCryptoFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
-  uint8_t *pos = const_cast<uint8_t *>(buf) + 1;
+  this->_packet = packet;
+  uint8_t *pos  = const_cast<uint8_t *>(buf) + 1;
 
   size_t field_len = 0;
   if (!read_varint(pos, LEFT_SPACE(pos), this->_offset, field_len)) {
@@ -488,14 +475,34 @@
                   this->data_length());
 }
 
-size_t
-QUICCryptoFrame::store(uint8_t *buf, size_t *len, size_t limit) const
+Ptr<IOBufferBlock>
+QUICCryptoFrame::to_io_buffer_block(size_t limit) const
 {
+  Ptr<IOBufferBlock> header;
+
   if (limit < this->size()) {
-    return 0;
+    return header;
   }
 
-  // Frame Type
+  // Create header block
+  size_t written_len = 0;
+  header             = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  header->alloc(iobuffer_size_to_index(MAX_HEADER_SIZE));
+  this->_store_header(reinterpret_cast<uint8_t *>(header->start()), &written_len);
+  header->fill(written_len);
+
+  // Append payload block to a chain
+  ink_assert(written_len + this->data_length() <= limit);
+  header->next = this->data();
+
+  // Return the chain
+  return header;
+}
+
+size_t
+QUICCryptoFrame::_store_header(uint8_t *buf, size_t *len) const
+{
+  // Type
   buf[0] = static_cast<uint8_t>(QUICFrameType::CRYPTO);
   *len   = 1;
 
@@ -509,10 +516,6 @@
   QUICIntUtil::write_QUICVariableInt(this->data_length(), buf + *len, &n);
   *len += n;
 
-  // Crypto Data (*)
-  memcpy(buf + *len, this->data()->start(), this->data_length());
-  *len += this->data_length();
-
   return *len;
 }
 
@@ -538,18 +541,19 @@
 // ACK frame
 //
 
-QUICAckFrame::QUICAckFrame(const uint8_t *buf, size_t len)
+QUICAckFrame::QUICAckFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
 {
-  this->parse(buf, len);
+  this->parse(buf, len, packet);
 }
 
 void
-QUICAckFrame::parse(const uint8_t *buf, size_t len)
+QUICAckFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
-  uint8_t *pos = const_cast<uint8_t *>(buf) + 1;
-  bool has_ecn = (buf[0] == static_cast<uint8_t>(QUICFrameType::ACK_WITH_ECN));
+  this->_packet = packet;
+  uint8_t *pos  = const_cast<uint8_t *>(buf) + 1;
+  bool has_ecn  = (buf[0] == static_cast<uint8_t>(QUICFrameType::ACK_WITH_ECN));
 
   size_t field_len = 0;
   if (!read_varint(pos, LEFT_SPACE(pos), this->_largest_acknowledged, field_len)) {
@@ -666,33 +670,43 @@
   return pre_len;
 }
 
-size_t
-QUICAckFrame::store(uint8_t *buf, size_t *len, size_t limit) const
+Ptr<IOBufferBlock>
+QUICAckFrame::to_io_buffer_block(size_t limit) const
 {
+  Ptr<IOBufferBlock> block;
+  size_t n = 0;
+
   if (limit < this->size()) {
-    return 0;
+    return block;
   }
 
-  uint8_t *p = buf;
-  size_t n;
-  *p = static_cast<uint8_t>(QUICFrameType::ACK);
-  ++p;
+  size_t written_len = 0;
+  block              = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(1 + 24));
+  uint8_t *block_start = reinterpret_cast<uint8_t *>(block->start());
 
-  QUICIntUtil::write_QUICVariableInt(this->_largest_acknowledged, p, &n);
-  p += n;
-  QUICIntUtil::write_QUICVariableInt(this->_ack_delay, p, &n);
-  p += n;
-  QUICIntUtil::write_QUICVariableInt(this->ack_block_count(), p, &n);
-  p += n;
+  // Type
+  block_start[0] = static_cast<uint8_t>(QUICFrameType::ACK);
+  n += 1;
 
-  ink_assert(limit >= static_cast<size_t>(p - buf));
-  limit -= (p - buf);
-  this->_ack_block_section->store(p, &n, limit);
-  p += n;
+  // Largest Acknowledged (i)
+  QUICIntUtil::write_QUICVariableInt(this->_largest_acknowledged, block_start + n, &written_len);
+  n += written_len;
 
-  *len = p - buf;
+  // Ack Delay (i)
+  QUICIntUtil::write_QUICVariableInt(this->_ack_delay, block_start + n, &written_len);
+  n += written_len;
 
-  return *len;
+  // Ack Range Count (i)
+  QUICIntUtil::write_QUICVariableInt(this->ack_block_count(), block_start + n, &written_len);
+  n += written_len;
+
+  block->fill(n);
+
+  // First Ack Range (i) + Ack Ranges (*)
+  block->next = this->_ack_block_section->to_io_buffer_block(limit - n);
+
+  return block;
 }
 
 int
@@ -829,29 +843,33 @@
   return n;
 }
 
-size_t
-QUICAckFrame::AckBlockSection::store(uint8_t *buf, size_t *len, size_t limit) const
+Ptr<IOBufferBlock>
+QUICAckFrame::AckBlockSection::to_io_buffer_block(size_t limit) const
 {
+  Ptr<IOBufferBlock> block;
+  size_t n = 0;
+
   if (limit < this->size()) {
-    return 0;
+    return block;
   }
 
-  size_t n;
-  uint8_t *p = buf;
+  size_t written_len = 0;
+  block              = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(limit));
+  uint8_t *block_start = reinterpret_cast<uint8_t *>(block->start());
 
-  QUICIntUtil::write_QUICVariableInt(this->_first_ack_block, p, &n);
-  p += n;
+  QUICIntUtil::write_QUICVariableInt(this->_first_ack_block, block_start + n, &written_len);
+  n += written_len;
 
   for (auto &&block : *this) {
-    QUICIntUtil::write_QUICVariableInt(block.gap(), p, &n);
-    p += n;
-    QUICIntUtil::write_QUICVariableInt(block.length(), p, &n);
-    p += n;
+    QUICIntUtil::write_QUICVariableInt(block.gap(), block_start + n, &written_len);
+    n += written_len;
+    QUICIntUtil::write_QUICVariableInt(block.length(), block_start + n, &written_len);
+    n += written_len;
   }
 
-  *len = p - buf;
-
-  return *len;
+  block->fill(n);
+  return block;
 }
 
 uint64_t
@@ -979,30 +997,35 @@
 {
 }
 
-QUICRstStreamFrame::QUICRstStreamFrame(const uint8_t *buf, size_t len)
+QUICRstStreamFrame::QUICRstStreamFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
 {
-  this->parse(buf, len);
+  this->parse(buf, len, packet);
 }
 
 void
-QUICRstStreamFrame::parse(const uint8_t *buf, size_t len)
+QUICRstStreamFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
-  uint8_t *pos = 1 + const_cast<uint8_t *>(buf);
+  this->_packet = packet;
+  uint8_t *pos  = 1 + const_cast<uint8_t *>(buf);
 
   size_t field_len = 0;
+
+  // Stream ID (i)
   if (!read_varint(pos, LEFT_SPACE(pos), this->_stream_id, field_len)) {
     return;
   }
 
-  if (LEFT_SPACE(pos) < 2) {
+  // Error Code (i)
+  if (LEFT_SPACE(pos) < 1) {
+    return;
+  }
+  if (!read_varint(pos, LEFT_SPACE(pos), this->_error_code, field_len)) {
     return;
   }
 
-  this->_error_code = QUICIntUtil::read_nbytes_as_uint(pos, 2);
-  pos += 2;
-
+  // Final Offset (i)
   if (!read_varint(pos, LEFT_SPACE(pos), this->_final_offset, field_len)) {
     return;
   }
@@ -1037,36 +1060,49 @@
     return this->_size;
   }
 
-  return 1 + QUICVariableInt::size(this->_stream_id) + sizeof(QUICAppErrorCode) + QUICVariableInt::size(this->_final_offset);
+  return 1 + QUICVariableInt::size(this->_stream_id) + QUICVariableInt::size(this->_error_code) +
+         QUICVariableInt::size(this->_final_offset);
 }
 
-size_t
-QUICRstStreamFrame::store(uint8_t *buf, size_t *len, size_t limit) const
+Ptr<IOBufferBlock>
+QUICRstStreamFrame::to_io_buffer_block(size_t limit) const
 {
+  Ptr<IOBufferBlock> block;
+  size_t n = 0;
+
   if (limit < this->size()) {
-    return 0;
+    return block;
   }
 
-  size_t n;
-  uint8_t *p = buf;
-  *p         = static_cast<uint8_t>(QUICFrameType::RESET_STREAM);
-  ++p;
-  QUICTypeUtil::write_QUICStreamId(this->_stream_id, p, &n);
-  p += n;
-  QUICTypeUtil::write_QUICAppErrorCode(this->_error_code, p, &n);
-  p += n;
-  QUICTypeUtil::write_QUICOffset(this->_final_offset, p, &n);
-  p += n;
+  size_t written_len = 0;
+  block              = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(1 + 24));
+  uint8_t *block_start = reinterpret_cast<uint8_t *>(block->start());
 
-  *len = p - buf;
+  // Type
+  block_start[0] = static_cast<uint8_t>(QUICFrameType::RESET_STREAM);
+  n += 1;
 
-  return *len;
+  // Stream ID (i)
+  QUICTypeUtil::write_QUICStreamId(this->_stream_id, block_start + n, &written_len);
+  n += written_len;
+
+  // Application Error Code (i)
+  QUICTypeUtil::write_QUICAppErrorCode(this->_error_code, block_start + n, &written_len);
+  n += written_len;
+
+  // Final Size (i)
+  QUICTypeUtil::write_QUICOffset(this->_final_offset, block_start + n, &written_len);
+  n += written_len;
+
+  block->fill(n);
+  return block;
 }
 
 int
 QUICRstStreamFrame::debug_msg(char *msg, size_t msg_len) const
 {
-  return snprintf(msg, msg_len, "RESET_STREAM size=%zu stream_id=%" PRIu64 " code=0x%" PRIx16, this->size(), this->stream_id(),
+  return snprintf(msg, msg_len, "RESET_STREAM size=%zu stream_id=%" PRIu64 " code=0x%" PRIx64, this->size(), this->stream_id(),
                   this->error_code());
 }
 
@@ -1092,17 +1128,18 @@
 // PING frame
 //
 
-QUICPingFrame::QUICPingFrame(const uint8_t *buf, size_t len)
+QUICPingFrame::QUICPingFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
 {
-  this->parse(buf, len);
+  this->parse(buf, len, packet);
 }
 
 void
-QUICPingFrame::parse(const uint8_t *buf, size_t len)
+QUICPingFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
   this->_reset();
-  this->_valid = true;
-  this->_size  = 1;
+  this->_packet = packet;
+  this->_valid  = true;
+  this->_size   = 1;
 }
 
 QUICFrameType
@@ -1117,33 +1154,52 @@
   return 1;
 }
 
-size_t
-QUICPingFrame::store(uint8_t *buf, size_t *len, size_t limit) const
+Ptr<IOBufferBlock>
+QUICPingFrame::to_io_buffer_block(size_t limit) const
 {
+  Ptr<IOBufferBlock> block;
+  size_t n = 0;
+
   if (limit < this->size()) {
-    return 0;
+    return block;
   }
 
-  *len   = this->size();
-  buf[0] = static_cast<uint8_t>(QUICFrameType::PING);
-  return *len;
+  block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(this->size()));
+  uint8_t *block_start = reinterpret_cast<uint8_t *>(block->start());
+
+  // Type
+  block_start[0] = static_cast<uint8_t>(QUICFrameType::PING);
+  n += 1;
+
+  block->fill(n);
+  return block;
 }
 
 //
 // PADDING frame
 //
-QUICPaddingFrame::QUICPaddingFrame(const uint8_t *buf, size_t len)
+QUICPaddingFrame::QUICPaddingFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
 {
-  this->parse(buf, len);
+  this->parse(buf, len, packet);
 }
 
 void
-QUICPaddingFrame::parse(const uint8_t *buf, size_t len)
+QUICPaddingFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
-  this->_valid = true;
-  this->_size  = 1;
+  this->_packet = packet;
+  this->_size   = 0;
+  this->_valid  = true;
+  // find out how many padding frames in this buf
+  for (size_t i = 0; i < len; i++) {
+    if (*(buf + i) == static_cast<uint8_t>(QUICFrameType::PADDING)) {
+      ++this->_size;
+    } else {
+      break;
+    }
+  }
 }
 
 QUICFrameType
@@ -1155,7 +1211,7 @@
 size_t
 QUICPaddingFrame::size() const
 {
-  return 1;
+  return this->_size;
 }
 
 bool
@@ -1164,22 +1220,31 @@
   return true;
 }
 
-size_t
-QUICPaddingFrame::store(uint8_t *buf, size_t *len, size_t limit) const
+Ptr<IOBufferBlock>
+QUICPaddingFrame::to_io_buffer_block(size_t limit) const
 {
+  Ptr<IOBufferBlock> block;
+  size_t n = 0;
+
   if (limit < this->size()) {
-    return 0;
+    return block;
   }
 
-  buf[0] = static_cast<uint8_t>(QUICFrameType::PADDING);
-  *len   = 1;
-  return *len;
+  block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(this->_size));
+  uint8_t *block_start = reinterpret_cast<uint8_t *>(block->start());
+
+  memset(block_start, 0, this->_size);
+  n = this->_size;
+
+  block->fill(n);
+  return block;
 }
 
 //
 // CONNECTION_CLOSE frame
 //
-QUICConnectionCloseFrame::QUICConnectionCloseFrame(uint16_t error_code, QUICFrameType frame_type, uint64_t reason_phrase_length,
+QUICConnectionCloseFrame::QUICConnectionCloseFrame(uint64_t error_code, QUICFrameType frame_type, uint64_t reason_phrase_length,
                                                    const char *reason_phrase, QUICFrameId id, QUICFrameGenerator *owner)
   : QUICFrame(id, owner),
     _type(0x1c),
@@ -1190,7 +1255,7 @@
 {
 }
 
-QUICConnectionCloseFrame::QUICConnectionCloseFrame(uint16_t error_code, uint64_t reason_phrase_length, const char *reason_phrase,
+QUICConnectionCloseFrame::QUICConnectionCloseFrame(uint64_t error_code, uint64_t reason_phrase_length, const char *reason_phrase,
                                                    QUICFrameId id, QUICFrameGenerator *owner)
   : QUICFrame(id, owner),
     _type(0x1d),
@@ -1200,9 +1265,10 @@
 {
 }
 
-QUICConnectionCloseFrame::QUICConnectionCloseFrame(const uint8_t *buf, size_t len)
+QUICConnectionCloseFrame::QUICConnectionCloseFrame(const uint8_t *buf, size_t len, const QUICPacket *packet)
+  : QUICFrame(0, nullptr, packet)
 {
-  this->parse(buf, len);
+  this->parse(buf, len, packet);
 }
 
 void
@@ -1220,28 +1286,29 @@
 }
 
 void
-QUICConnectionCloseFrame::parse(const uint8_t *buf, size_t len)
+QUICConnectionCloseFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
-  this->_type  = buf[0];
-  uint8_t *pos = const_cast<uint8_t *>(buf) + 1;
-
-  if (LEFT_SPACE(pos) < 2) {
-    return;
-  }
-
-  this->_error_code = QUICIntUtil::read_nbytes_as_uint(pos, 2);
-  pos += 2;
+  this->_packet = packet;
+  this->_type   = buf[0];
+  uint8_t *pos  = const_cast<uint8_t *>(buf) + 1;
 
   size_t field_len = 0;
   uint64_t field   = 0;
 
+  // Error Code (i)
+  if (LEFT_SPACE(pos) < 1) {
+    return;
+  }
+  read_varint(pos, LEFT_SPACE(pos), field, field_len);
+  this->_error_code = field;
+
   if (this->_type == 0x1c) {
+    // Frame Type (i)
     if (!read_varint(pos, LEFT_SPACE(pos), field, field_len)) {
       return;
     }
-
     this->_frame_type = static_cast<QUICFrameType>(field);
 
     /**
@@ -1255,16 +1322,21 @@
     }
   }
 
+  // Reason Phrase Length (i)
+  if (LEFT_SPACE(pos) < 1) {
+    return;
+  }
   if (!read_varint(pos, LEFT_SPACE(pos), this->_reason_phrase_length, field_len)) {
     return;
   }
 
+  // Reason Phrase
   if (LEFT_SPACE(pos) < this->_reason_phrase_length) {
     return;
   }
-
-  this->_valid         = true;
   this->_reason_phrase = reinterpret_cast<const char *>(pos);
+
+  this->_valid = true;
   pos += this->_reason_phrase_length;
   this->_size = FRAME_SIZE(pos);
 }
@@ -1282,7 +1354,7 @@
     return this->_size;
   }
 
-  return 1 + sizeof(QUICTransErrorCode) + QUICVariableInt::size(sizeof(QUICFrameType)) +
+  return 1 + QUICVariableInt::size(sizeof(QUICTransErrorCode)) + QUICVariableInt::size(sizeof(QUICFrameType)) +
          QUICVariableInt::size(this->_reason_phrase_length) + this->_reason_phrase_length;
 }
 
@@ -1292,42 +1364,58 @@
    PADDING frame in Frame Type field means frame type that triggered the error is unknown.
    When `_frame_type` is QUICFrameType::UNKNOWN, it's converted to QUICFrameType::PADDING (0x0).
  */
-size_t
-QUICConnectionCloseFrame::store(uint8_t *buf, size_t *len, size_t limit) const
+Ptr<IOBufferBlock>
+QUICConnectionCloseFrame::to_io_buffer_block(size_t limit) const
 {
+  Ptr<IOBufferBlock> first_block;
+  size_t n = 0;
+
   if (limit < this->size()) {
-    return 0;
+    return first_block;
   }
 
-  size_t n;
-  uint8_t *p = buf;
-  *p         = this->_type;
-  ++p;
+  // Create a block for Error Code(i) and Frame Type(i)
+  size_t written_len = 0;
+  first_block        = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  first_block->alloc(iobuffer_size_to_index(1 + 24));
+  uint8_t *block_start = reinterpret_cast<uint8_t *>(first_block->start());
 
-  // Error Code (16)
-  QUICTypeUtil::write_QUICTransErrorCode(this->_error_code, p, &n);
-  p += n;
+  // Type
+  block_start[0] = this->_type;
+  n += 1;
+
+  // Error Code (i)
+  QUICIntUtil::write_QUICVariableInt(this->_error_code, block_start + n, &written_len);
+  n += written_len;
 
   // Frame Type (i)
   QUICFrameType frame_type = this->_frame_type;
   if (frame_type == QUICFrameType::UNKNOWN) {
     frame_type = QUICFrameType::PADDING;
   }
-  *p = static_cast<uint8_t>(frame_type);
-  ++p;
+  QUICIntUtil::write_QUICVariableInt(static_cast<uint64_t>(frame_type), block_start + n, &written_len);
+  n += written_len;
 
   // Reason Phrase Length (i)
-  QUICIntUtil::write_QUICVariableInt(this->_reason_phrase_length, p, &n);
-  p += n;
+  QUICIntUtil::write_QUICVariableInt(this->_reason_phrase_length, block_start + n, &written_len);
+  n += written_len;
 
-  // Reason Phrase (*)
-  if (this->_reason_phrase_length > 0) {
-    memcpy(p, this->_reason_phrase, this->_reason_phrase_length);
-    p += this->_reason_phrase_length;
+  first_block->fill(n);
+
+  // Create a block for reason if necessary
+  if (this->_reason_phrase_length != 0) {
+    // Reason Phrase (*)
+    Ptr<IOBufferBlock> reason_block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+    reason_block->alloc(iobuffer_size_to_index(this->_reason_phrase_length));
+    memcpy(reinterpret_cast<uint8_t *>(reason_block->start()), this->_reason_phrase, this->_reason_phrase_length);
+    reason_block->fill(this->_reason_phrase_length);
+
+    // Append reason block to the first block
+    first_block->next = reason_block;
   }
 
-  *len = p - buf;
-  return *len;
+  // Return the chain
+  return first_block;
 }
 
 int
@@ -1400,17 +1488,18 @@
   this->_size  = 0;
 }
 
-QUICMaxDataFrame::QUICMaxDataFrame(const uint8_t *buf, size_t len)
+QUICMaxDataFrame::QUICMaxDataFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
 {
-  this->parse(buf, len);
+  this->parse(buf, len, packet);
 }
 
 void
-QUICMaxDataFrame::parse(const uint8_t *buf, size_t len)
+QUICMaxDataFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
-  uint8_t *pos = 1 + const_cast<uint8_t *>(buf);
+  this->_packet = packet;
+  uint8_t *pos  = 1 + const_cast<uint8_t *>(buf);
 
   size_t field_len = 0;
   if (!read_varint(pos, LEFT_SPACE(pos), this->_maximum_data, field_len)) {
@@ -1437,22 +1526,31 @@
   return sizeof(QUICFrameType) + QUICVariableInt::size(this->_maximum_data);
 }
 
-size_t
-QUICMaxDataFrame::store(uint8_t *buf, size_t *len, size_t limit) const
+Ptr<IOBufferBlock>
+QUICMaxDataFrame::to_io_buffer_block(size_t limit) const
 {
+  Ptr<IOBufferBlock> block;
+  size_t n = 0;
+
   if (limit < this->size()) {
-    return 0;
+    return block;
   }
 
-  size_t n;
-  uint8_t *p = buf;
-  *p         = static_cast<uint8_t>(QUICFrameType::MAX_DATA);
-  ++p;
-  QUICTypeUtil::write_QUICMaxData(this->_maximum_data, p, &n);
-  p += n;
+  size_t written_len = 0;
+  block              = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(1 + sizeof(size_t)));
+  uint8_t *block_start = reinterpret_cast<uint8_t *>(block->start());
 
-  *len = p - buf;
-  return *len;
+  // Type
+  block_start[0] = static_cast<uint8_t>(QUICFrameType::MAX_DATA);
+  n += 1;
+
+  // Maximum Data (i)
+  QUICTypeUtil::write_QUICMaxData(this->_maximum_data, block_start + n, &written_len);
+  n += written_len;
+
+  block->fill(n);
+  return block;
 }
 
 int
@@ -1490,17 +1588,19 @@
   this->_size  = 0;
 }
 
-QUICMaxStreamDataFrame::QUICMaxStreamDataFrame(const uint8_t *buf, size_t len)
+QUICMaxStreamDataFrame::QUICMaxStreamDataFrame(const uint8_t *buf, size_t len, const QUICPacket *packet)
+  : QUICFrame(0, nullptr, packet)
 {
-  this->parse(buf, len);
+  this->parse(buf, len, packet);
 }
 
 void
-QUICMaxStreamDataFrame::parse(const uint8_t *buf, size_t len)
+QUICMaxStreamDataFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
-  uint8_t *pos = const_cast<uint8_t *>(buf) + 1;
+  this->_packet = packet;
+  uint8_t *pos  = const_cast<uint8_t *>(buf) + 1;
 
   size_t field_len = 0;
   if (!read_varint(pos, LEFT_SPACE(pos), this->_stream_id, field_len)) {
@@ -1531,23 +1631,35 @@
   return sizeof(QUICFrameType) + QUICVariableInt::size(this->_maximum_stream_data) + QUICVariableInt::size(this->_stream_id);
 }
 
-size_t
-QUICMaxStreamDataFrame::store(uint8_t *buf, size_t *len, size_t limit) const
+Ptr<IOBufferBlock>
+QUICMaxStreamDataFrame::to_io_buffer_block(size_t limit) const
 {
-  if (limit < this->size()) {
-    return 0;
-  }
-  size_t n;
-  uint8_t *p = buf;
-  *p         = static_cast<uint8_t>(QUICFrameType::MAX_STREAM_DATA);
-  ++p;
-  QUICTypeUtil::write_QUICStreamId(this->_stream_id, p, &n);
-  p += n;
-  QUICTypeUtil::write_QUICMaxData(this->_maximum_stream_data, p, &n);
-  p += n;
+  Ptr<IOBufferBlock> block;
+  size_t n = 0;
 
-  *len = p - buf;
-  return *len;
+  if (limit < this->size()) {
+    return block;
+  }
+
+  size_t written_len = 0;
+  block              = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(1 + sizeof(uint64_t) + sizeof(size_t)));
+  uint8_t *block_start = reinterpret_cast<uint8_t *>(block->start());
+
+  // Type
+  block_start[0] = static_cast<uint8_t>(QUICFrameType::MAX_STREAM_DATA);
+  n += 1;
+
+  // Stream ID (i)
+  QUICTypeUtil::write_QUICStreamId(this->_stream_id, block_start + n, &written_len);
+  n += written_len;
+
+  // Maximum Stream Data (i)
+  QUICTypeUtil::write_QUICMaxData(this->_maximum_stream_data, block_start + n, &written_len);
+  n += written_len;
+
+  block->fill(n);
+  return block;
 }
 
 int
@@ -1589,17 +1701,18 @@
   this->_size  = 0;
 }
 
-QUICMaxStreamsFrame::QUICMaxStreamsFrame(const uint8_t *buf, size_t len)
+QUICMaxStreamsFrame::QUICMaxStreamsFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
 {
-  this->parse(buf, len);
+  this->parse(buf, len, packet);
 }
 
 void
-QUICMaxStreamsFrame::parse(const uint8_t *buf, size_t len)
+QUICMaxStreamsFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
-  uint8_t *pos = const_cast<uint8_t *>(buf) + 1;
+  this->_packet = packet;
+  uint8_t *pos  = const_cast<uint8_t *>(buf) + 1;
 
   size_t field_len = 0;
   if (!read_varint(pos, LEFT_SPACE(pos), this->_maximum_streams, field_len)) {
@@ -1626,22 +1739,31 @@
   return sizeof(QUICFrameType) + QUICVariableInt::size(this->_maximum_streams);
 }
 
-size_t
-QUICMaxStreamsFrame::store(uint8_t *buf, size_t *len, size_t limit) const
+Ptr<IOBufferBlock>
+QUICMaxStreamsFrame::to_io_buffer_block(size_t limit) const
 {
+  Ptr<IOBufferBlock> block;
+  size_t n = 0;
+
   if (limit < this->size()) {
-    return 0;
+    return block;
   }
 
-  size_t n;
-  uint8_t *p = buf;
-  *p         = static_cast<uint8_t>(QUICFrameType::MAX_STREAMS);
-  ++p;
-  QUICTypeUtil::write_QUICStreamId(this->_maximum_streams, p, &n);
-  p += n;
+  size_t written_len = 0;
+  block              = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(1 + sizeof(size_t)));
+  uint8_t *block_start = reinterpret_cast<uint8_t *>(block->start());
 
-  *len = p - buf;
-  return *len;
+  // Type
+  block_start[0] = static_cast<uint8_t>(QUICFrameType::MAX_STREAMS);
+  n += 1;
+
+  // Maximum Streams (i)
+  QUICTypeUtil::write_QUICStreamId(this->_maximum_streams, block_start + n, &written_len);
+  n += written_len;
+
+  block->fill(n);
+  return block;
 }
 
 uint64_t
@@ -1653,9 +1775,9 @@
 //
 // DATA_BLOCKED frame
 //
-QUICDataBlockedFrame::QUICDataBlockedFrame(const uint8_t *buf, size_t len)
+QUICDataBlockedFrame::QUICDataBlockedFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
 {
-  this->parse(buf, len);
+  this->parse(buf, len, packet);
 }
 
 void
@@ -1670,11 +1792,12 @@
 }
 
 void
-QUICDataBlockedFrame::parse(const uint8_t *buf, size_t len)
+QUICDataBlockedFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
-  uint8_t *pos = const_cast<uint8_t *>(buf) + 1;
+  this->_packet = packet;
+  uint8_t *pos  = const_cast<uint8_t *>(buf) + 1;
 
   size_t field_len = 0;
   if (!read_varint(pos, LEFT_SPACE(pos), this->_offset, field_len)) {
@@ -1707,24 +1830,31 @@
   return sizeof(QUICFrameType) + QUICVariableInt::size(this->offset());
 }
 
-size_t
-QUICDataBlockedFrame::store(uint8_t *buf, size_t *len, size_t limit) const
+Ptr<IOBufferBlock>
+QUICDataBlockedFrame::to_io_buffer_block(size_t limit) const
 {
+  Ptr<IOBufferBlock> block;
+  size_t n = 0;
+
   if (limit < this->size()) {
-    return 0;
+    return block;
   }
 
-  size_t n;
-  uint8_t *p = buf;
+  size_t written_len = 0;
+  block              = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(1 + sizeof(size_t)));
+  uint8_t *block_start = reinterpret_cast<uint8_t *>(block->start());
 
-  *p = static_cast<uint8_t>(QUICFrameType::DATA_BLOCKED);
-  ++p;
-  QUICTypeUtil::write_QUICOffset(this->_offset, p, &n);
-  p += n;
+  // Type
+  block_start[0] = static_cast<uint8_t>(QUICFrameType::DATA_BLOCKED);
+  n += 1;
 
-  *len = p - buf;
+  // Data Limit (i)
+  QUICTypeUtil::write_QUICOffset(this->_offset, block_start + n, &written_len);
+  n += written_len;
 
-  return *len;
+  block->fill(n);
+  return block;
 }
 
 QUICOffset
@@ -1736,9 +1866,10 @@
 //
 // STREAM_DATA_BLOCKED frame
 //
-QUICStreamDataBlockedFrame::QUICStreamDataBlockedFrame(const uint8_t *buf, size_t len)
+QUICStreamDataBlockedFrame::QUICStreamDataBlockedFrame(const uint8_t *buf, size_t len, const QUICPacket *packet)
+  : QUICFrame(0, nullptr, packet)
 {
-  this->parse(buf, len);
+  this->parse(buf, len, packet);
 }
 
 void
@@ -1754,11 +1885,12 @@
 }
 
 void
-QUICStreamDataBlockedFrame::parse(const uint8_t *buf, size_t len)
+QUICStreamDataBlockedFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
-  uint8_t *pos = const_cast<uint8_t *>(buf) + 1;
+  this->_packet = packet;
+  uint8_t *pos  = const_cast<uint8_t *>(buf) + 1;
 
   size_t field_len = 0;
   if (!read_varint(pos, LEFT_SPACE(pos), this->_stream_id, field_len)) {
@@ -1796,25 +1928,35 @@
   return sizeof(QUICFrameType) + QUICVariableInt::size(this->_offset) + QUICVariableInt::size(this->_stream_id);
 }
 
-size_t
-QUICStreamDataBlockedFrame::store(uint8_t *buf, size_t *len, size_t limit) const
+Ptr<IOBufferBlock>
+QUICStreamDataBlockedFrame::to_io_buffer_block(size_t limit) const
 {
+  Ptr<IOBufferBlock> block;
+  size_t n = 0;
+
   if (limit < this->size()) {
-    return 0;
+    return block;
   }
 
-  size_t n;
-  uint8_t *p = buf;
-  *p         = static_cast<uint8_t>(QUICFrameType::STREAM_DATA_BLOCKED);
-  ++p;
-  QUICTypeUtil::write_QUICStreamId(this->_stream_id, p, &n);
-  p += n;
-  QUICTypeUtil::write_QUICOffset(this->_offset, p, &n);
-  p += n;
+  size_t written_len = 0;
+  block              = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(1 + sizeof(size_t)));
+  uint8_t *block_start = reinterpret_cast<uint8_t *>(block->start());
 
-  *len = p - buf;
+  // Type
+  block_start[0] = static_cast<uint8_t>(QUICFrameType::STREAM_DATA_BLOCKED);
+  n += 1;
 
-  return *len;
+  // Stream ID (i)
+  QUICTypeUtil::write_QUICStreamId(this->_stream_id, block_start + n, &written_len);
+  n += written_len;
+
+  // Data Limit (i)
+  QUICTypeUtil::write_QUICOffset(this->_offset, block_start + n, &written_len);
+  n += written_len;
+
+  block->fill(n);
+  return block;
 }
 
 QUICStreamId
@@ -1832,9 +1974,10 @@
 //
 // STREAMS_BLOCKED frame
 //
-QUICStreamIdBlockedFrame::QUICStreamIdBlockedFrame(const uint8_t *buf, size_t len)
+QUICStreamIdBlockedFrame::QUICStreamIdBlockedFrame(const uint8_t *buf, size_t len, const QUICPacket *packet)
+  : QUICFrame(0, nullptr, packet)
 {
-  this->parse(buf, len);
+  this->parse(buf, len, packet);
 }
 
 void
@@ -1849,11 +1992,12 @@
 }
 
 void
-QUICStreamIdBlockedFrame::parse(const uint8_t *buf, size_t len)
+QUICStreamIdBlockedFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
-  uint8_t *pos = const_cast<uint8_t *>(buf) + 1;
+  this->_packet = packet;
+  uint8_t *pos  = const_cast<uint8_t *>(buf) + 1;
 
   size_t field_len = 0;
   if (!read_varint(pos, LEFT_SPACE(pos), this->_stream_id, field_len)) {
@@ -1880,23 +2024,31 @@
   return sizeof(QUICFrameType) + QUICVariableInt::size(this->_stream_id);
 }
 
-size_t
-QUICStreamIdBlockedFrame::store(uint8_t *buf, size_t *len, size_t limit) const
+Ptr<IOBufferBlock>
+QUICStreamIdBlockedFrame::to_io_buffer_block(size_t limit) const
 {
+  Ptr<IOBufferBlock> block;
+  size_t n = 0;
+
   if (limit < this->size()) {
-    return 0;
+    return block;
   }
 
-  size_t n;
-  uint8_t *p = buf;
+  size_t written_len = 0;
+  block              = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(1 + sizeof(size_t)));
+  uint8_t *block_start = reinterpret_cast<uint8_t *>(block->start());
 
-  *p = static_cast<uint8_t>(QUICFrameType::STREAMS_BLOCKED);
-  ++p;
-  QUICTypeUtil::write_QUICStreamId(this->_stream_id, p, &n);
-  p += n;
+  // Type
+  block_start[0] = static_cast<uint8_t>(QUICFrameType::STREAMS_BLOCKED);
+  n += 1;
 
-  *len = p - buf;
-  return *len;
+  // Stream Limit (i)
+  QUICTypeUtil::write_QUICStreamId(this->_stream_id, block_start + n, &written_len);
+  n += written_len;
+
+  block->fill(n);
+  return block;
 }
 
 QUICStreamId
@@ -1908,16 +2060,18 @@
 //
 // NEW_CONNECTION_ID frame
 //
-QUICNewConnectionIdFrame::QUICNewConnectionIdFrame(const uint8_t *buf, size_t len)
+QUICNewConnectionIdFrame::QUICNewConnectionIdFrame(const uint8_t *buf, size_t len, const QUICPacket *packet)
+  : QUICFrame(0, nullptr, packet)
 {
-  this->parse(buf, len);
+  this->parse(buf, len, packet);
 }
 
 void
 QUICNewConnectionIdFrame::_reset()
 {
-  this->_sequence      = 0;
-  this->_connection_id = QUICConnectionId::ZERO();
+  this->_sequence        = 0;
+  this->_retire_prior_to = 0;
+  this->_connection_id   = QUICConnectionId::ZERO();
 
   this->_owner = nullptr;
   this->_id    = 0;
@@ -1926,38 +2080,49 @@
 }
 
 void
-QUICNewConnectionIdFrame::parse(const uint8_t *buf, size_t len)
+QUICNewConnectionIdFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
-  uint8_t *pos = const_cast<uint8_t *>(buf) + 1;
+  this->_packet = packet;
+  uint8_t *pos  = const_cast<uint8_t *>(buf) + 1;
 
+  // Sequence Number (i)
   size_t field_len = 0;
   if (!read_varint(pos, LEFT_SPACE(pos), this->_sequence, field_len)) {
     return;
   }
 
+  // Retire Prior To (i)
   if (LEFT_SPACE(pos) < 1) {
     return;
   }
-
-  size_t cid_len = *pos;
-  pos += 1;
-
-  if (LEFT_SPACE(pos) < cid_len) {
+  if (!read_varint(pos, LEFT_SPACE(pos), this->_retire_prior_to, field_len)) {
     return;
   }
 
+  // Length (8)
+  if (LEFT_SPACE(pos) < 1) {
+    return;
+  }
+  size_t cid_len = *pos;
+  pos += 1;
+
+  // Connection ID (8..160)
+  if (LEFT_SPACE(pos) < cid_len) {
+    return;
+  }
   this->_connection_id = QUICTypeUtil::read_QUICConnectionId(pos, cid_len);
   pos += cid_len;
 
-  if (LEFT_SPACE(pos) < 16) {
+  // Stateless Reset Token (128)
+  if (LEFT_SPACE(pos) < QUICStatelessResetToken::LEN) {
     return;
   }
 
   this->_stateless_reset_token = QUICStatelessResetToken(pos);
   this->_valid                 = true;
-  this->_size                  = FRAME_SIZE(pos) + 16;
+  this->_size                  = FRAME_SIZE(pos) + QUICStatelessResetToken::LEN;
 }
 
 QUICFrameType
@@ -1973,31 +2138,52 @@
     return this->_size;
   }
 
-  return sizeof(QUICFrameType) + QUICVariableInt::size(this->_sequence) + 1 + this->_connection_id.length() + 16;
+  return sizeof(QUICFrameType) + QUICVariableInt::size(this->_sequence) + QUICVariableInt::size(this->_retire_prior_to) + 1 +
+         this->_connection_id.length() + QUICStatelessResetToken::LEN;
 }
 
-size_t
-QUICNewConnectionIdFrame::store(uint8_t *buf, size_t *len, size_t limit) const
+Ptr<IOBufferBlock>
+QUICNewConnectionIdFrame::to_io_buffer_block(size_t limit) const
 {
+  Ptr<IOBufferBlock> block;
+  size_t n = 0;
+
   if (limit < this->size()) {
-    return 0;
+    return block;
   }
 
-  size_t n;
-  uint8_t *p = buf;
-  *p         = static_cast<uint8_t>(QUICFrameType::NEW_CONNECTION_ID);
-  ++p;
-  QUICIntUtil::write_QUICVariableInt(this->_sequence, p, &n);
-  p += n;
-  *p = this->_connection_id.length();
-  p += 1;
-  QUICTypeUtil::write_QUICConnectionId(this->_connection_id, p, &n);
-  p += n;
-  memcpy(p, this->_stateless_reset_token.buf(), QUICStatelessResetToken::LEN);
-  p += QUICStatelessResetToken::LEN;
+  size_t written_len = 0;
+  block              = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(1 + sizeof(uint64_t) + sizeof(uint64_t) + 1 + QUICConnectionId::MAX_LENGTH +
+                                      QUICStatelessResetToken::LEN));
+  uint8_t *block_start = reinterpret_cast<uint8_t *>(block->start());
 
-  *len = p - buf;
-  return *len;
+  // Type
+  block_start[0] = static_cast<uint8_t>(QUICFrameType::NEW_CONNECTION_ID);
+  n += 1;
+
+  // Sequence Number (i)
+  QUICIntUtil::write_QUICVariableInt(this->_sequence, block_start + n, &written_len);
+  n += written_len;
+
+  // Retire Prior To (i)
+  QUICIntUtil::write_QUICVariableInt(this->_retire_prior_to, block_start + n, &written_len);
+  n += written_len;
+
+  // Length (8)
+  *(block_start + n) = this->_connection_id.length();
+  n += 1;
+
+  // Connection ID (8..160)
+  QUICTypeUtil::write_QUICConnectionId(this->_connection_id, block_start + n, &written_len);
+  n += written_len;
+
+  // Stateless Reset Token (128)
+  memcpy(block_start + n, this->_stateless_reset_token.buf(), QUICStatelessResetToken::LEN);
+  n += QUICStatelessResetToken::LEN;
+
+  block->fill(n);
+  return block;
 }
 
 int
@@ -2006,7 +2192,8 @@
   char cid_str[QUICConnectionId::MAX_HEX_STR_LENGTH];
   this->connection_id().hex(cid_str, QUICConnectionId::MAX_HEX_STR_LENGTH);
 
-  return snprintf(msg, msg_len, "NEW_CONNECTION_ID size=%zu seq=%" PRIu64 " cid=0x%s", this->size(), this->sequence(), cid_str);
+  return snprintf(msg, msg_len, "NEW_CONNECTION_ID size=%zu seq=%" PRIu64 " rpt=%" PRIu64 " cid=0x%s", this->size(),
+                  this->sequence(), this->retire_prior_to(), cid_str);
 }
 
 uint64_t
@@ -2015,6 +2202,12 @@
   return this->_sequence;
 }
 
+uint64_t
+QUICNewConnectionIdFrame::retire_prior_to() const
+{
+  return this->_retire_prior_to;
+}
+
 QUICConnectionId
 QUICNewConnectionIdFrame::connection_id() const
 {
@@ -2049,30 +2242,35 @@
   this->_size  = 0;
 }
 
-QUICStopSendingFrame::QUICStopSendingFrame(const uint8_t *buf, size_t len)
+QUICStopSendingFrame::QUICStopSendingFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
 {
-  this->parse(buf, len);
+  this->parse(buf, len, packet);
 }
 
 void
-QUICStopSendingFrame::parse(const uint8_t *buf, size_t len)
+QUICStopSendingFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
-  uint8_t *pos = const_cast<uint8_t *>(buf) + 1;
+  this->_packet = packet;
+  uint8_t *pos  = const_cast<uint8_t *>(buf) + 1;
 
+  // Stream ID (i)
   size_t field_len = 0;
   if (!read_varint(pos, LEFT_SPACE(pos), this->_stream_id, field_len)) {
     return;
   }
 
-  if (LEFT_SPACE(pos) < 2) {
+  // Error Code (i)
+  if (LEFT_SPACE(pos) < 1) {
+    return;
+  }
+  if (!read_varint(pos, LEFT_SPACE(pos), this->_error_code, field_len)) {
     return;
   }
 
-  this->_error_code = static_cast<QUICAppErrorCode>(QUICIntUtil::read_nbytes_as_uint(pos, 2));
-  this->_valid      = true;
-  this->_size       = FRAME_SIZE(pos) + 2;
+  this->_valid = true;
+  this->_size  = FRAME_SIZE(pos);
 }
 
 QUICFrameType
@@ -2088,27 +2286,38 @@
     return this->_size;
   }
 
-  return sizeof(QUICFrameType) + QUICVariableInt::size(this->_stream_id) + sizeof(QUICAppErrorCode);
+  return sizeof(QUICFrameType) + QUICVariableInt::size(this->_stream_id) + QUICVariableInt::size(sizeof(QUICAppErrorCode));
 }
 
-size_t
-QUICStopSendingFrame::store(uint8_t *buf, size_t *len, size_t limit) const
+Ptr<IOBufferBlock>
+QUICStopSendingFrame::to_io_buffer_block(size_t limit) const
 {
+  Ptr<IOBufferBlock> block;
+  size_t n = 0;
+
   if (limit < this->size()) {
-    return 0;
+    return block;
   }
 
-  size_t n;
-  uint8_t *p = buf;
-  *p         = static_cast<uint8_t>(QUICFrameType::STOP_SENDING);
-  ++p;
-  QUICTypeUtil::write_QUICStreamId(this->_stream_id, p, &n);
-  p += n;
-  QUICTypeUtil::write_QUICAppErrorCode(this->_error_code, p, &n);
-  p += n;
+  size_t written_len = 0;
+  block              = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(1 + 24));
+  uint8_t *block_start = reinterpret_cast<uint8_t *>(block->start());
 
-  *len = p - buf;
-  return *len;
+  // Type
+  block_start[0] = static_cast<uint8_t>(QUICFrameType::STOP_SENDING);
+  n += 1;
+
+  // Stream ID (i)
+  QUICTypeUtil::write_QUICStreamId(this->_stream_id, block_start + n, &written_len);
+  n += written_len;
+
+  // Application Error Code (i)
+  QUICTypeUtil::write_QUICAppErrorCode(this->_error_code, block_start + n, &written_len);
+  n += written_len;
+
+  block->fill(n);
+  return block;
 }
 
 QUICAppErrorCode
@@ -2126,9 +2335,10 @@
 //
 // PATH_CHALLENGE frame
 //
-QUICPathChallengeFrame::QUICPathChallengeFrame(const uint8_t *buf, size_t len)
+QUICPathChallengeFrame::QUICPathChallengeFrame(const uint8_t *buf, size_t len, const QUICPacket *packet)
+  : QUICFrame(0, nullptr, packet)
 {
-  this->parse(buf, len);
+  this->parse(buf, len, packet);
 }
 
 void
@@ -2142,11 +2352,12 @@
 }
 
 void
-QUICPathChallengeFrame::parse(const uint8_t *buf, size_t len)
+QUICPathChallengeFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
-  uint8_t *pos = const_cast<uint8_t *>(buf) + 1;
+  this->_packet = packet;
+  uint8_t *pos  = const_cast<uint8_t *>(buf) + 1;
 
   if (LEFT_SPACE(pos) < QUICPathChallengeFrame::DATA_LEN) {
     return;
@@ -2180,19 +2391,38 @@
   return true;
 }
 
-size_t
-QUICPathChallengeFrame::store(uint8_t *buf, size_t *len, size_t limit) const
+Ptr<IOBufferBlock>
+QUICPathChallengeFrame::to_io_buffer_block(size_t limit) const
 {
+  Ptr<IOBufferBlock> block;
+  size_t n = 0;
+
   if (limit < this->size()) {
-    return 0;
+    return block;
   }
 
-  *len = this->size();
+  block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(1 + QUICPathChallengeFrame::DATA_LEN));
+  uint8_t *block_start = reinterpret_cast<uint8_t *>(block->start());
 
-  buf[0] = static_cast<uint8_t>(QUICFrameType::PATH_CHALLENGE);
-  memcpy(buf + 1, this->data(), QUICPathChallengeFrame::DATA_LEN);
+  // Type
+  block_start[0] = static_cast<uint8_t>(QUICFrameType::PATH_CHALLENGE);
+  n += 1;
 
-  return *len;
+  // Data (64)
+  memcpy(block_start + n, this->data(), QUICPathChallengeFrame::DATA_LEN);
+  n += QUICPathChallengeFrame::DATA_LEN;
+
+  block->fill(n);
+  return block;
+}
+
+int
+QUICPathChallengeFrame::debug_msg(char *msg, size_t msg_len) const
+{
+  auto data = this->data();
+  return snprintf(msg, msg_len, "PATH_CHALLENGE size=%zu data=0x%02x%02x%02x%02x%02x%02x%02x%02x", this->size(), data[0], data[1],
+                  data[2], data[3], data[4], data[5], data[6], data[7]);
 }
 
 const uint8_t *
@@ -2204,9 +2434,10 @@
 //
 // PATH_RESPONSE frame
 //
-QUICPathResponseFrame::QUICPathResponseFrame(const uint8_t *buf, size_t len)
+QUICPathResponseFrame::QUICPathResponseFrame(const uint8_t *buf, size_t len, const QUICPacket *packet)
+  : QUICFrame(0, nullptr, packet)
 {
-  this->parse(buf, len);
+  this->parse(buf, len, packet);
 }
 
 void
@@ -2219,12 +2450,39 @@
   this->_size  = 0;
 }
 
+Ptr<IOBufferBlock>
+QUICPathResponseFrame::to_io_buffer_block(size_t limit) const
+{
+  Ptr<IOBufferBlock> block;
+  size_t n = 0;
+
+  if (limit < this->size()) {
+    return block;
+  }
+
+  block = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(1 + QUICPathResponseFrame::DATA_LEN));
+  uint8_t *block_start = reinterpret_cast<uint8_t *>(block->start());
+
+  // Type
+  block_start[0] = static_cast<uint8_t>(QUICFrameType::PATH_RESPONSE);
+  n += 1;
+
+  // Data (64)
+  memcpy(block_start + n, this->data(), QUICPathChallengeFrame::DATA_LEN);
+  n += QUICPathChallengeFrame::DATA_LEN;
+
+  block->fill(n);
+  return block;
+}
+
 void
-QUICPathResponseFrame::parse(const uint8_t *buf, size_t len)
+QUICPathResponseFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
-  uint8_t *pos = const_cast<uint8_t *>(buf) + 1;
+  this->_packet = packet;
+  uint8_t *pos  = const_cast<uint8_t *>(buf) + 1;
 
   if (LEFT_SPACE(pos) < QUICPathChallengeFrame::DATA_LEN) {
     return;
@@ -2254,19 +2512,12 @@
   return true;
 }
 
-size_t
-QUICPathResponseFrame::store(uint8_t *buf, size_t *len, size_t limit) const
+int
+QUICPathResponseFrame::debug_msg(char *msg, size_t msg_len) const
 {
-  if (limit < this->size()) {
-    return 0;
-  }
-
-  *len = this->size();
-
-  buf[0] = static_cast<uint8_t>(QUICFrameType::PATH_RESPONSE);
-  memcpy(buf + 1, this->data(), QUICPathResponseFrame::DATA_LEN);
-
-  return *len;
+  auto data = this->data();
+  return snprintf(msg, msg_len, "PATH_RESPONSE size=%zu data=0x%02x%02x%02x%02x%02x%02x%02x%02x", this->size(), data[0], data[1],
+                  data[2], data[3], data[4], data[5], data[6], data[7]);
 }
 
 const uint8_t *
@@ -2278,9 +2529,9 @@
 //
 // QUICNewTokenFrame
 //
-QUICNewTokenFrame::QUICNewTokenFrame(const uint8_t *buf, size_t len)
+QUICNewTokenFrame::QUICNewTokenFrame(const uint8_t *buf, size_t len, const QUICPacket *packet) : QUICFrame(0, nullptr, packet)
 {
-  this->parse(buf, len);
+  this->parse(buf, len, packet);
 }
 
 void
@@ -2296,11 +2547,12 @@
 }
 
 void
-QUICNewTokenFrame::parse(const uint8_t *buf, size_t len)
+QUICNewTokenFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
-  uint8_t *pos = const_cast<uint8_t *>(buf) + 1;
+  this->_packet = packet;
+  uint8_t *pos  = const_cast<uint8_t *>(buf) + 1;
 
   size_t field_len = 0;
   if (!read_varint(pos, LEFT_SPACE(pos), this->_token_length, field_len)) {
@@ -2333,30 +2585,35 @@
   return 1 + QUICVariableInt::size(this->_token_length) + this->token_length();
 }
 
-size_t
-QUICNewTokenFrame::store(uint8_t *buf, size_t *len, size_t limit) const
+Ptr<IOBufferBlock>
+QUICNewTokenFrame::to_io_buffer_block(size_t limit) const
 {
+  Ptr<IOBufferBlock> block;
+  size_t n = 0;
+
   if (limit < this->size()) {
-    return 0;
+    return block;
   }
 
-  uint8_t *p = buf;
+  size_t written_len = 0;
+  block              = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(1 + 24));
+  uint8_t *block_start = reinterpret_cast<uint8_t *>(block->start());
 
-  // Type (i)
-  *p = static_cast<uint8_t>(QUICFrameType::NEW_TOKEN);
-  ++p;
+  // Type
+  block_start[0] = static_cast<uint8_t>(QUICFrameType::NEW_TOKEN);
+  n += 1;
 
   // Token Length (i)
-  size_t n;
-  QUICIntUtil::write_QUICVariableInt(this->_token_length, p, &n);
-  p += n;
+  QUICIntUtil::write_QUICVariableInt(this->_token_length, block_start + n, &written_len);
+  n += written_len;
 
   // Token (*)
-  memcpy(p, this->token(), this->token_length());
-  p += this->token_length();
+  memcpy(block_start + n, this->token(), this->token_length());
+  n += this->token_length();
 
-  *len = p - buf;
-  return *len;
+  block->fill(n);
+  return block;
 }
 
 uint64_t
@@ -2374,9 +2631,10 @@
 //
 // RETIRE_CONNECTION_ID frame
 //
-QUICRetireConnectionIdFrame::QUICRetireConnectionIdFrame(const uint8_t *buf, size_t len)
+QUICRetireConnectionIdFrame::QUICRetireConnectionIdFrame(const uint8_t *buf, size_t len, const QUICPacket *packet)
+  : QUICFrame(0, nullptr, packet)
 {
-  this->parse(buf, len);
+  this->parse(buf, len, packet);
 }
 
 void
@@ -2392,11 +2650,12 @@
 }
 
 void
-QUICRetireConnectionIdFrame::parse(const uint8_t *buf, size_t len)
+QUICRetireConnectionIdFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
   ink_assert(len >= 1);
   this->_reset();
-  uint8_t *pos = const_cast<uint8_t *>(buf) + 1;
+  this->_packet = packet;
+  uint8_t *pos  = const_cast<uint8_t *>(buf) + 1;
 
   size_t field_len = 0;
   if (!read_varint(pos, LEFT_SPACE(pos), this->_seq_num, field_len)) {
@@ -2423,23 +2682,31 @@
   return sizeof(QUICFrameType) + QUICVariableInt::size(this->_seq_num);
 }
 
-size_t
-QUICRetireConnectionIdFrame::store(uint8_t *buf, size_t *len, size_t limit) const
+Ptr<IOBufferBlock>
+QUICRetireConnectionIdFrame::to_io_buffer_block(size_t limit) const
 {
+  Ptr<IOBufferBlock> block;
+  size_t n = 0;
+
   if (limit < this->size()) {
-    return 0;
+    return block;
   }
 
-  size_t n;
-  uint8_t *p = buf;
-  *p         = static_cast<uint8_t>(QUICFrameType::RETIRE_CONNECTION_ID);
-  ++p;
-  QUICIntUtil::write_QUICVariableInt(this->_seq_num, p, &n);
-  p += n;
+  size_t written_len = 0;
+  block              = make_ptr<IOBufferBlock>(new_IOBufferBlock());
+  block->alloc(iobuffer_size_to_index(1 + sizeof(uint64_t)));
+  uint8_t *block_start = reinterpret_cast<uint8_t *>(block->start());
 
-  *len = p - buf;
+  // Type
+  block_start[0] = static_cast<uint8_t>(QUICFrameType::RETIRE_CONNECTION_ID);
+  n += 1;
 
-  return *len;
+  // Sequence Number (i)
+  QUICIntUtil::write_QUICVariableInt(this->_seq_num, block_start + n, &written_len);
+  n += written_len;
+
+  block->fill(n);
+  return block;
 }
 
 int
@@ -2470,15 +2737,17 @@
   return 0;
 }
 
-size_t
-QUICUnknownFrame::store(uint8_t *buf, size_t *len, size_t limit) const
+Ptr<IOBufferBlock>
+QUICUnknownFrame::to_io_buffer_block(size_t limit) const
 {
-  return 0;
+  Ptr<IOBufferBlock> block;
+  return block;
 }
 
 void
-QUICUnknownFrame::parse(const uint8_t *buf, size_t len)
+QUICUnknownFrame::parse(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
+  this->_packet = packet;
 }
 
 int
@@ -2492,65 +2761,65 @@
 //
 
 QUICFrame *
-QUICFrameFactory::create(uint8_t *buf, const uint8_t *src, size_t len)
+QUICFrameFactory::create(uint8_t *buf, const uint8_t *src, size_t len, const QUICPacket *packet)
 {
   switch (QUICFrame::type(src)) {
   case QUICFrameType::STREAM:
-    new (buf) QUICStreamFrame(src, len);
+    new (buf) QUICStreamFrame(src, len, packet);
     return reinterpret_cast<QUICFrame *>(buf);
   case QUICFrameType::CRYPTO:
-    new (buf) QUICCryptoFrame(src, len);
+    new (buf) QUICCryptoFrame(src, len, packet);
     return reinterpret_cast<QUICFrame *>(buf);
   case QUICFrameType::ACK:
-    new (buf) QUICAckFrame(src, len);
+    new (buf) QUICAckFrame(src, len, packet);
     return reinterpret_cast<QUICFrame *>(buf);
   case QUICFrameType::PADDING:
-    new (buf) QUICPaddingFrame(src, len);
+    new (buf) QUICPaddingFrame(src, len, packet);
     return reinterpret_cast<QUICFrame *>(buf);
   case QUICFrameType::RESET_STREAM:
-    new (buf) QUICRstStreamFrame(src, len);
+    new (buf) QUICRstStreamFrame(src, len, packet);
     return reinterpret_cast<QUICFrame *>(buf);
   case QUICFrameType::CONNECTION_CLOSE:
-    new (buf) QUICConnectionCloseFrame(src, len);
+    new (buf) QUICConnectionCloseFrame(src, len, packet);
     return reinterpret_cast<QUICFrame *>(buf);
   case QUICFrameType::MAX_DATA:
-    new (buf) QUICMaxDataFrame(src, len);
+    new (buf) QUICMaxDataFrame(src, len, packet);
     return reinterpret_cast<QUICFrame *>(buf);
   case QUICFrameType::MAX_STREAM_DATA:
-    new (buf) QUICMaxStreamDataFrame(src, len);
+    new (buf) QUICMaxStreamDataFrame(src, len, packet);
     return reinterpret_cast<QUICFrame *>(buf);
   case QUICFrameType::MAX_STREAMS:
-    new (buf) QUICMaxStreamsFrame(src, len);
+    new (buf) QUICMaxStreamsFrame(src, len, packet);
     return reinterpret_cast<QUICFrame *>(buf);
   case QUICFrameType::PING:
-    new (buf) QUICPingFrame(src, len);
+    new (buf) QUICPingFrame(src, len, packet);
     return reinterpret_cast<QUICFrame *>(buf);
   case QUICFrameType::DATA_BLOCKED:
-    new (buf) QUICDataBlockedFrame(src, len);
+    new (buf) QUICDataBlockedFrame(src, len, packet);
     return reinterpret_cast<QUICFrame *>(buf);
   case QUICFrameType::STREAM_DATA_BLOCKED:
-    new (buf) QUICStreamDataBlockedFrame(src, len);
+    new (buf) QUICStreamDataBlockedFrame(src, len, packet);
     return reinterpret_cast<QUICFrame *>(buf);
   case QUICFrameType::STREAMS_BLOCKED:
-    new (buf) QUICStreamIdBlockedFrame(src, len);
+    new (buf) QUICStreamIdBlockedFrame(src, len, packet);
     return reinterpret_cast<QUICFrame *>(buf);
   case QUICFrameType::NEW_CONNECTION_ID:
-    new (buf) QUICNewConnectionIdFrame(src, len);
+    new (buf) QUICNewConnectionIdFrame(src, len, packet);
     return reinterpret_cast<QUICFrame *>(buf);
   case QUICFrameType::STOP_SENDING:
-    new (buf) QUICStopSendingFrame(src, len);
+    new (buf) QUICStopSendingFrame(src, len, packet);
     return reinterpret_cast<QUICFrame *>(buf);
   case QUICFrameType::PATH_CHALLENGE:
-    new (buf) QUICPathChallengeFrame(src, len);
+    new (buf) QUICPathChallengeFrame(src, len, packet);
     return reinterpret_cast<QUICFrame *>(buf);
   case QUICFrameType::PATH_RESPONSE:
-    new (buf) QUICPathResponseFrame(src, len);
+    new (buf) QUICPathResponseFrame(src, len, packet);
     return reinterpret_cast<QUICFrame *>(buf);
   case QUICFrameType::NEW_TOKEN:
-    new (buf) QUICNewTokenFrame(src, len);
+    new (buf) QUICNewTokenFrame(src, len, packet);
     return reinterpret_cast<QUICFrame *>(buf);
   case QUICFrameType::RETIRE_CONNECTION_ID:
-    new (buf) QUICRetireConnectionIdFrame(src, len);
+    new (buf) QUICRetireConnectionIdFrame(src, len, packet);
     return reinterpret_cast<QUICFrame *>(buf);
   default:
     // Unknown frame
@@ -2560,7 +2829,7 @@
 }
 
 const QUICFrame &
-QUICFrameFactory::fast_create(const uint8_t *buf, size_t len)
+QUICFrameFactory::fast_create(const uint8_t *buf, size_t len, const QUICPacket *packet)
 {
   if (QUICFrame::type(buf) == QUICFrameType::UNKNOWN) {
     return this->_unknown_frame;
@@ -2570,12 +2839,12 @@
   QUICFrame *frame     = this->_reusable_frames[type_index];
 
   if (frame == nullptr) {
-    frame = QUICFrameFactory::create(this->_buf_for_fast_create + (type_index * QUICFrame::MAX_INSTANCE_SIZE), buf, len);
+    frame = QUICFrameFactory::create(this->_buf_for_fast_create + (type_index * QUICFrame::MAX_INSTANCE_SIZE), buf, len, packet);
     if (frame != nullptr) {
       this->_reusable_frames[static_cast<ptrdiff_t>(QUICFrame::type(buf))] = frame;
     }
   } else {
-    frame->parse(buf, len);
+    frame->parse(buf, len, packet);
   }
 
   return *frame;
@@ -2656,6 +2925,13 @@
   return reinterpret_cast<QUICPingFrame *>(buf);
 }
 
+QUICPaddingFrame *
+QUICFrameFactory::create_padding_frame(uint8_t *buf, size_t size, QUICFrameId id, QUICFrameGenerator *owner)
+{
+  new (buf) QUICPaddingFrame(size);
+  return reinterpret_cast<QUICPaddingFrame *>(buf);
+}
+
 QUICPathChallengeFrame *
 QUICFrameFactory::create_path_challenge_frame(uint8_t *buf, const uint8_t *data, QUICFrameId id, QUICFrameGenerator *owner)
 {
@@ -2721,11 +2997,11 @@
 }
 
 QUICNewConnectionIdFrame *
-QUICFrameFactory::create_new_connection_id_frame(uint8_t *buf, uint32_t sequence, QUICConnectionId connectoin_id,
-                                                 QUICStatelessResetToken stateless_reset_token, QUICFrameId id,
-                                                 QUICFrameGenerator *owner)
+QUICFrameFactory::create_new_connection_id_frame(uint8_t *buf, uint64_t sequence, uint64_t retire_prior_to,
+                                                 QUICConnectionId connectoin_id, QUICStatelessResetToken stateless_reset_token,
+                                                 QUICFrameId id, QUICFrameGenerator *owner)
 {
-  new (buf) QUICNewConnectionIdFrame(sequence, connectoin_id, stateless_reset_token, id, owner);
+  new (buf) QUICNewConnectionIdFrame(sequence, retire_prior_to, connectoin_id, stateless_reset_token, id, owner);
   return reinterpret_cast<QUICNewConnectionIdFrame *>(buf);
 }
 
diff --git a/iocore/net/quic/QUICFrame.h b/iocore/net/quic/QUICFrame.h
index 20f1bfe..ab21b17 100644
--- a/iocore/net/quic/QUICFrame.h
+++ b/iocore/net/quic/QUICFrame.h
@@ -55,21 +55,26 @@
   virtual size_t size() const = 0;
   virtual bool is_probing_frame() const;
   virtual bool is_flow_controlled() const;
-  virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const = 0;
-  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const = 0;
   virtual int debug_msg(char *msg, size_t msg_len) const;
-  virtual void parse(const uint8_t *buf, size_t len){};
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet){};
   virtual QUICFrameGenerator *generated_by();
   bool valid() const;
+  bool ack_eliciting() const;
+  const QUICPacket *packet() const;
   LINK(QUICFrame, link);
 
 protected:
   virtual void _reset(){};
-  QUICFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : _id(id), _owner(owner) {}
+  QUICFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr, const QUICPacket *packet = nullptr)
+    : _id(id), _owner(owner), _packet(packet)
+  {
+  }
   size_t _size               = 0;
   bool _valid                = false;
   QUICFrameId _id            = 0;
   QUICFrameGenerator *_owner = nullptr;
+  const QUICPacket *_packet  = nullptr;
 };
 
 //
@@ -80,7 +85,7 @@
 {
 public:
   QUICStreamFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICStreamFrame(const uint8_t *buf, size_t len);
+  QUICStreamFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
   QUICStreamFrame(Ptr<IOBufferBlock> &block, QUICStreamId streamid, QUICOffset offset, bool last = false,
                   bool has_offset_field = true, bool has_length_field = true, QUICFrameId id = 0,
                   QUICFrameGenerator *owner = nullptr);
@@ -89,12 +94,10 @@
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
   virtual bool is_flow_controlled() const override;
-  virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override;
   virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
-  virtual void parse(const uint8_t *buf, size_t len) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
 
-  size_t store(uint8_t *buf, size_t *len, size_t limit, bool include_length_field) const;
   QUICStreamId stream_id() const;
   QUICOffset offset() const;
   IOBufferBlock *data() const;
@@ -128,15 +131,15 @@
 {
 public:
   QUICCryptoFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICCryptoFrame(const uint8_t *buf, size_t len);
+  QUICCryptoFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
   QUICCryptoFrame(Ptr<IOBufferBlock> &block, QUICOffset offset, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr);
   QUICCryptoFrame(const QUICCryptoFrame &o);
 
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
-  virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
-  virtual void parse(const uint8_t *buf, size_t len) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
 
   QUICOffset offset() const;
   uint64_t data_length() const;
@@ -145,8 +148,12 @@
   LINK(QUICCryptoFrame, link);
 
 private:
+  static constexpr uint8_t MAX_HEADER_SIZE = 16;
+
   virtual void _reset() override;
 
+  size_t _store_header(uint8_t *buf, size_t *len) const;
+
   QUICOffset _offset = 0;
   Ptr<IOBufferBlock> _block;
 };
@@ -218,7 +225,7 @@
     AckBlockSection(uint64_t first_ack_block) : _first_ack_block(first_ack_block) {}
     uint8_t count() const;
     size_t size() const;
-    size_t store(uint8_t *buf, size_t *len, size_t limit) const;
+    Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const;
     uint64_t first_ack_block() const;
     void add_ack_block(const AckBlock block);
     const_iterator begin() const;
@@ -250,7 +257,7 @@
   };
 
   QUICAckFrame(QUICFrameId id = 0) : QUICFrame(id) {}
-  QUICAckFrame(const uint8_t *buf, size_t len);
+  QUICAckFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
   QUICAckFrame(QUICPacketNumber largest_acknowledged, uint64_t ack_delay, uint64_t first_ack_block, QUICFrameId id = 0,
                QUICFrameGenerator *owner = nullptr);
 
@@ -260,8 +267,8 @@
   virtual ~QUICAckFrame();
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
-  virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override;
-  virtual void parse(const uint8_t *buf, size_t len) override;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
 
   QUICPacketNumber largest_acknowledged() const;
@@ -289,15 +296,15 @@
 {
 public:
   QUICRstStreamFrame(QUICFrameId id = 0) : QUICFrame(id) {}
-  QUICRstStreamFrame(const uint8_t *buf, size_t len);
+  QUICRstStreamFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
   QUICRstStreamFrame(QUICStreamId stream_id, QUICAppErrorCode error_code, QUICOffset final_offset, QUICFrameId id = 0,
                      QUICFrameGenerator *owner = nullptr);
 
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
-  virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
-  virtual void parse(const uint8_t *buf, size_t len) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
 
   QUICStreamId stream_id() const;
   QUICAppErrorCode error_code() const;
@@ -319,11 +326,11 @@
 {
 public:
   QUICPingFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICPingFrame(const uint8_t *buf, size_t len);
+  QUICPingFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
-  virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override;
-  virtual void parse(const uint8_t *buf, size_t len) override;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
 
 private:
 };
@@ -335,13 +342,18 @@
 class QUICPaddingFrame : public QUICFrame
 {
 public:
-  QUICPaddingFrame() {}
-  QUICPaddingFrame(const uint8_t *buf, size_t len);
+  QUICPaddingFrame(size_t size) : _size(size) {}
+  QUICPaddingFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
   virtual bool is_probing_frame() const override;
-  virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override;
-  virtual void parse(const uint8_t *buf, size_t len) override;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+
+private:
+  // padding frame is a resident of padding frames
+  // size indicate how many padding frames in this QUICPaddingFrame
+  size_t _size = 0;
 };
 
 //
@@ -352,18 +364,18 @@
 {
 public:
   QUICConnectionCloseFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICConnectionCloseFrame(const uint8_t *buf, size_t len);
+  QUICConnectionCloseFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
   // Constructor for transport error codes
-  QUICConnectionCloseFrame(uint16_t error_code, QUICFrameType frame_type, uint64_t reason_phrase_length, const char *reason_phrase,
+  QUICConnectionCloseFrame(uint64_t error_code, QUICFrameType frame_type, uint64_t reason_phrase_length, const char *reason_phrase,
                            QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr);
   // Constructor for application protocol error codes
-  QUICConnectionCloseFrame(uint16_t error_code, uint64_t reason_phrase_length, const char *reason_phrase, QUICFrameId id = 0,
+  QUICConnectionCloseFrame(uint64_t error_code, uint64_t reason_phrase_length, const char *reason_phrase, QUICFrameId id = 0,
                            QUICFrameGenerator *owner = nullptr);
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
-  virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
-  virtual void parse(const uint8_t *buf, size_t len) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
 
   uint16_t error_code() const;
   QUICFrameType frame_type() const;
@@ -374,7 +386,7 @@
   virtual void _reset() override;
 
   uint8_t _type = 0;
-  uint16_t _error_code;
+  uint64_t _error_code;
   QUICFrameType _frame_type      = QUICFrameType::UNKNOWN;
   uint64_t _reason_phrase_length = 0;
   const char *_reason_phrase     = nullptr;
@@ -388,13 +400,13 @@
 {
 public:
   QUICMaxDataFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICMaxDataFrame(const uint8_t *buf, size_t len);
+  QUICMaxDataFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
   QUICMaxDataFrame(uint64_t maximum_data, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr);
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
-  virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
-  virtual void parse(const uint8_t *buf, size_t len) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
 
   uint64_t maximum_data() const;
 
@@ -412,13 +424,13 @@
 {
 public:
   QUICMaxStreamDataFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICMaxStreamDataFrame(const uint8_t *buf, size_t len);
+  QUICMaxStreamDataFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
   QUICMaxStreamDataFrame(QUICStreamId stream_id, uint64_t maximum_stream_data, QUICFrameId id = 0,
                          QUICFrameGenerator *owner = nullptr);
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
-  virtual void parse(const uint8_t *buf, size_t len) override;
-  virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
 
   QUICStreamId stream_id() const;
@@ -439,12 +451,12 @@
 {
 public:
   QUICMaxStreamsFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICMaxStreamsFrame(const uint8_t *buf, size_t len);
+  QUICMaxStreamsFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
   QUICMaxStreamsFrame(QUICStreamId maximum_streams, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr);
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
-  virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override;
-  virtual void parse(const uint8_t *buf, size_t len) override;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
   uint64_t maximum_streams() const;
 
 private:
@@ -460,15 +472,15 @@
 {
 public:
   QUICDataBlockedFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICDataBlockedFrame(const uint8_t *buf, size_t len);
+  QUICDataBlockedFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
   QUICDataBlockedFrame(QUICOffset offset, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr)
     : QUICFrame(id, owner), _offset(offset){};
 
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
-  virtual void parse(const uint8_t *buf, size_t len) override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
-  virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
 
   QUICOffset offset() const;
 
@@ -486,14 +498,14 @@
 {
 public:
   QUICStreamDataBlockedFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICStreamDataBlockedFrame(const uint8_t *buf, size_t len);
+  QUICStreamDataBlockedFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
   QUICStreamDataBlockedFrame(QUICStreamId s, QUICOffset o, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr)
     : QUICFrame(id, owner), _stream_id(s), _offset(o){};
 
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
-  virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override;
-  virtual void parse(const uint8_t *buf, size_t len) override;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
 
   QUICStreamId stream_id() const;
@@ -513,15 +525,15 @@
 {
 public:
   QUICStreamIdBlockedFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICStreamIdBlockedFrame(const uint8_t *buf, size_t len);
+  QUICStreamIdBlockedFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
   QUICStreamIdBlockedFrame(QUICStreamId s, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr)
     : QUICFrame(id, owner), _stream_id(s)
   {
   }
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
-  virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override;
-  virtual void parse(const uint8_t *buf, size_t len) override;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
 
   QUICStreamId stream_id() const;
 
@@ -539,18 +551,19 @@
 {
 public:
   QUICNewConnectionIdFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICNewConnectionIdFrame(const uint8_t *buf, size_t len);
-  QUICNewConnectionIdFrame(uint64_t seq, const QUICConnectionId &cid, QUICStatelessResetToken token, QUICFrameId id = 0,
-                           QUICFrameGenerator *owner = nullptr)
-    : QUICFrame(id, owner), _sequence(seq), _connection_id(cid), _stateless_reset_token(token){};
+  QUICNewConnectionIdFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
+  QUICNewConnectionIdFrame(uint64_t seq, uint64_t ret, const QUICConnectionId &cid, QUICStatelessResetToken token,
+                           QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr)
+    : QUICFrame(id, owner), _sequence(seq), _retire_prior_to(ret), _connection_id(cid), _stateless_reset_token(token){};
 
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
-  virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override;
-  virtual void parse(const uint8_t *buf, size_t len) override;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
 
   uint64_t sequence() const;
+  uint64_t retire_prior_to() const;
   QUICConnectionId connection_id() const;
   QUICStatelessResetToken stateless_reset_token() const;
 
@@ -558,6 +571,7 @@
   virtual void _reset() override;
 
   uint64_t _sequence              = 0;
+  uint64_t _retire_prior_to       = 0;
   QUICConnectionId _connection_id = QUICConnectionId::ZERO();
   QUICStatelessResetToken _stateless_reset_token;
 };
@@ -570,14 +584,14 @@
 {
 public:
   QUICStopSendingFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICStopSendingFrame(const uint8_t *buf, size_t len);
+  QUICStopSendingFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
   QUICStopSendingFrame(QUICStreamId stream_id, QUICAppErrorCode error_code, QUICFrameId id = 0,
                        QUICFrameGenerator *owner = nullptr);
 
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
-  virtual void parse(const uint8_t *buf, size_t len) override;
-  virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
 
   QUICStreamId stream_id() const;
   QUICAppErrorCode error_code() const;
@@ -598,7 +612,7 @@
 public:
   static constexpr uint8_t DATA_LEN = 8;
   QUICPathChallengeFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICPathChallengeFrame(const uint8_t *buf, size_t len);
+  QUICPathChallengeFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
   QUICPathChallengeFrame(ats_unique_buf data, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr)
     : QUICFrame(id, owner), _data(std::move(data))
   {
@@ -606,8 +620,9 @@
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
   virtual bool is_probing_frame() const override;
-  virtual void parse(const uint8_t *buf, size_t len) override;
-  virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
+  virtual int debug_msg(char *msg, size_t msg_len) const override;
 
   const uint8_t *data() const;
 
@@ -626,7 +641,7 @@
 public:
   static constexpr uint8_t DATA_LEN = 8;
   QUICPathResponseFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICPathResponseFrame(const uint8_t *buf, size_t len);
+  QUICPathResponseFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
   QUICPathResponseFrame(ats_unique_buf data, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr)
     : QUICFrame(id, owner), _data(std::move(data))
   {
@@ -634,8 +649,9 @@
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
   virtual bool is_probing_frame() const override;
-  virtual void parse(const uint8_t *buf, size_t len) override;
-  virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
+  virtual int debug_msg(char *msg, size_t msg_len) const override;
 
   const uint8_t *data() const;
 
@@ -653,15 +669,15 @@
 {
 public:
   QUICNewTokenFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICNewTokenFrame(const uint8_t *buf, size_t len);
+  QUICNewTokenFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
   QUICNewTokenFrame(ats_unique_buf token, size_t token_length, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr)
     : QUICFrame(id, owner), _token_length(token_length), _token(std::move(token))
   {
   }
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
-  virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override;
-  virtual void parse(const uint8_t *buf, size_t len) override;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
 
   uint64_t token_length() const;
   const uint8_t *token() const;
@@ -681,15 +697,15 @@
 {
 public:
   QUICRetireConnectionIdFrame(QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr) : QUICFrame(id, owner) {}
-  QUICRetireConnectionIdFrame(const uint8_t *buf, size_t len);
+  QUICRetireConnectionIdFrame(const uint8_t *buf, size_t len, const QUICPacket *packet = nullptr);
   QUICRetireConnectionIdFrame(uint64_t seq_num, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr)
     : QUICFrame(id, owner), _seq_num(seq_num)
   {
   }
   virtual QUICFrameType type() const override;
   virtual size_t size() const override;
-  virtual size_t store(uint8_t *buf, size_t *len, size_t limit) const override;
-  virtual void parse(const uint8_t *buf, size_t len) override;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
+  virtual void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
   virtual int debug_msg(char *msg, size_t msg_len) const override;
 
   uint64_t seq_num() const;
@@ -708,8 +724,8 @@
 {
   QUICFrameType type() const override;
   size_t size() const override;
-  size_t store(uint8_t *buf, size_t *len, size_t limit) const override;
-  void parse(const uint8_t *buf, size_t len) override;
+  virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
+  void parse(const uint8_t *buf, size_t len, const QUICPacket *packet) override;
   int debug_msg(char *msg, size_t msg_len) const override;
 };
 
@@ -722,13 +738,13 @@
   /*
    * This is used for creating a QUICFrame object based on received data.
    */
-  static QUICFrame *create(uint8_t *buf, const uint8_t *src, size_t len);
+  static QUICFrame *create(uint8_t *buf, const uint8_t *src, size_t len, const QUICPacket *packet);
 
   /*
    * This works almost the same as create() but it reuses created objects for performance.
    * If you create a frame object which has the same frame type that you created before, the object will be reset by new data.
    */
-  const QUICFrame &fast_create(const uint8_t *buf, size_t len);
+  const QUICFrame &fast_create(const uint8_t *buf, size_t len, const QUICPacket *packet);
 
   /*
    * Creates a STREAM frame.
@@ -833,7 +849,8 @@
   /*
    * Creates a NEW_CONNECTION_ID frame.
    */
-  static QUICNewConnectionIdFrame *create_new_connection_id_frame(uint8_t *buf, uint32_t sequence, QUICConnectionId connectoin_id,
+  static QUICNewConnectionIdFrame *create_new_connection_id_frame(uint8_t *buf, uint64_t sequence, uint64_t retire_prior_to,
+                                                                  QUICConnectionId connectoin_id,
                                                                   QUICStatelessResetToken stateless_reset_token, QUICFrameId id = 0,
                                                                   QUICFrameGenerator *owner = nullptr);
 
@@ -849,6 +866,11 @@
   static QUICRetireConnectionIdFrame *create_retire_connection_id_frame(uint8_t *buf, uint64_t seq_num, QUICFrameId id = 0,
                                                                         QUICFrameGenerator *owner = nullptr);
 
+  /*
+   * Creates a PADDING frame
+   */
+  static QUICPaddingFrame *create_padding_frame(uint8_t *buf, size_t size, QUICFrameId id = 0, QUICFrameGenerator *owner = nullptr);
+
 private:
   // FIXME Actual number of frame types is several but some of the values are not sequential.
   QUICFrame *_reusable_frames[256] = {nullptr};
diff --git a/iocore/net/quic/QUICFrameDispatcher.cc b/iocore/net/quic/QUICFrameDispatcher.cc
index c2f3d1f..b807ff5 100644
--- a/iocore/net/quic/QUICFrameDispatcher.cc
+++ b/iocore/net/quic/QUICFrameDispatcher.cc
@@ -43,7 +43,7 @@
 
 QUICConnectionErrorUPtr
 QUICFrameDispatcher::receive_frames(QUICEncryptionLevel level, const uint8_t *payload, uint16_t size, bool &ack_only,
-                                    bool &is_flow_controlled, bool *has_non_probing_frame)
+                                    bool &is_flow_controlled, bool *has_non_probing_frame, const QUICPacket *packet)
 {
   uint16_t cursor               = 0;
   ack_only                      = true;
@@ -51,7 +51,7 @@
   QUICConnectionErrorUPtr error = nullptr;
 
   while (cursor < size) {
-    const QUICFrame &frame = this->_frame_factory.fast_create(payload + cursor, size - cursor);
+    const QUICFrame &frame = this->_frame_factory.fast_create(payload + cursor, size - cursor, packet);
     if (frame.type() == QUICFrameType::UNKNOWN) {
       QUICDebug("Failed to create a frame (%u bytes skipped)", size - cursor);
       break;
diff --git a/iocore/net/quic/QUICFrameDispatcher.h b/iocore/net/quic/QUICFrameDispatcher.h
index 79d7131..e7ce785 100644
--- a/iocore/net/quic/QUICFrameDispatcher.h
+++ b/iocore/net/quic/QUICFrameDispatcher.h
@@ -35,7 +35,8 @@
   QUICFrameDispatcher(QUICConnectionInfoProvider *info);
 
   QUICConnectionErrorUPtr receive_frames(QUICEncryptionLevel level, const uint8_t *payload, uint16_t size,
-                                         bool &should_send_ackbool, bool &is_flow_controlled, bool *has_non_probing_frame);
+                                         bool &should_send_ackbool, bool &is_flow_controlled, bool *has_non_probing_frame,
+                                         const QUICPacket *packet);
 
   void add_handler(QUICFrameHandler *handler);
 
diff --git a/iocore/net/quic/QUICFrameGenerator.cc b/iocore/net/quic/QUICFrameGenerator.cc
index 3dce966..908374e 100644
--- a/iocore/net/quic/QUICFrameGenerator.cc
+++ b/iocore/net/quic/QUICFrameGenerator.cc
@@ -23,6 +23,7 @@
 
 #include "QUICFrameGenerator.h"
 
+// QUICFrameGenerator
 void
 QUICFrameGenerator::_records_frame(QUICFrameId id, QUICFrameInformationUPtr info)
 {
@@ -58,3 +59,31 @@
     this->_info.erase(it);
   }
 }
+
+void
+QUICFrameGeneratorManager::add_generator(QUICFrameGenerator &generator, int weight)
+{
+  auto it = this->_inline_vector.begin();
+  for (; it != this->_inline_vector.end(); ++it) {
+    if (it->first >= weight) {
+      break;
+    }
+  }
+  this->_inline_vector.emplace(it, weight, &generator);
+}
+
+const std::vector<QUICFrameGenerator *> &
+QUICFrameGeneratorManager::generators()
+{
+  // Because we don't remove generators. So The size changed means new generators is coming
+  if (!this->_generators.empty() && this->_generators.size() == this->_inline_vector.size()) {
+    return this->_generators;
+  }
+
+  this->_generators.clear();
+  for (auto it : this->_inline_vector) {
+    this->_generators.emplace_back(it.second);
+  }
+
+  return this->_generators;
+}
diff --git a/iocore/net/quic/QUICFrameGenerator.h b/iocore/net/quic/QUICFrameGenerator.h
index 2298de5..f415ff7 100644
--- a/iocore/net/quic/QUICFrameGenerator.h
+++ b/iocore/net/quic/QUICFrameGenerator.h
@@ -30,14 +30,14 @@
 {
 public:
   virtual ~QUICFrameGenerator(){};
-  virtual bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) = 0;
+  virtual bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) = 0;
 
   /*
    * This function constructs an instance of QUICFrame on buf.
    * It returns a pointer for the frame if it succeeded, and returns nullptr if it failed.
    */
   virtual QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit,
-                                    uint16_t maximum_frame_size, ink_hrtime timestamp) = 0;
+                                    uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num) = 0;
 
   void on_frame_acked(QUICFrameId id);
   void on_frame_lost(QUICFrameId id);
@@ -67,3 +67,55 @@
   QUICEncryptionLevel _encryption_level_filter = QUICEncryptionLevel::ONE_RTT;
   std::map<QUICFrameId, QUICFrameInformationUPtr> _info;
 };
+
+// only generate one frame per loop
+class QUICFrameOnceGenerator : public QUICFrameGenerator
+{
+public:
+  bool
+  will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) override
+  {
+    if (this->_seq_num == seq_num) {
+      return false;
+    }
+
+    this->_seq_num = seq_num;
+    return this->_will_generate_frame(level, current_packet_size, ack_eliciting);
+  }
+
+  QUICFrame *
+  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) override
+  {
+    this->_seq_num = seq_num;
+    return this->_generate_frame(buf, level, connection_credit, maximum_frame_size, current_packet_size);
+  }
+
+protected:
+  virtual bool _will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting) = 0;
+  virtual QUICFrame *_generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit,
+                                     uint16_t maximum_frame_size, size_t current_packet_size)                  = 0;
+
+private:
+  uint32_t _seq_num = UINT32_MAX;
+};
+
+enum QUICFrameGeneratorWeight {
+  EARLY       = 100,
+  BEFORE_DATA = 200,
+  AFTER_DATA  = 300,
+  LATE        = 400,
+};
+
+class QUICFrameGeneratorManager
+{
+public:
+  void add_generator(QUICFrameGenerator &generator, int weight);
+  const std::vector<QUICFrameGenerator *> &generators();
+
+private:
+  using QUICActiveFrameGenerator = std::pair<int, QUICFrameGenerator *>;
+
+  std::vector<QUICFrameGenerator *> _generators;
+  std::vector<QUICActiveFrameGenerator> _inline_vector;
+};
diff --git a/iocore/net/quic/QUICHandshake.cc b/iocore/net/quic/QUICHandshake.cc
index 6042d26..c88377f 100644
--- a/iocore/net/quic/QUICHandshake.cc
+++ b/iocore/net/quic/QUICHandshake.cc
@@ -166,7 +166,7 @@
     packet_factory->set_version(version);
   } else {
     QUICHSDebug("Version negotiation failed");
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::VERSION_NEGOTIATION_ERROR);
+    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::PROTOCOL_VIOLATION);
   }
 
   return nullptr;
@@ -322,24 +322,24 @@
 }
 
 bool
-QUICHandshake::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp)
+QUICHandshake::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;
   }
 
-  return this->_crypto_streams[static_cast<int>(level)].will_generate_frame(level, timestamp);
+  return this->_crypto_streams[static_cast<int>(level)].will_generate_frame(level, current_packet_size, ack_eliciting, seq_num);
 }
 
 QUICFrame *
 QUICHandshake::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                              ink_hrtime timestamp)
+                              size_t current_packet_size, uint32_t seq_num)
 {
   QUICFrame *frame = nullptr;
 
   if (this->_is_level_matched(level)) {
-    frame =
-      this->_crypto_streams[static_cast<int>(level)].generate_frame(buf, level, connection_credit, maximum_frame_size, timestamp);
+    frame = this->_crypto_streams[static_cast<int>(level)].generate_frame(buf, level, connection_credit, maximum_frame_size,
+                                                                          current_packet_size, seq_num);
   }
 
   return frame;
@@ -382,6 +382,9 @@
     pref_addr->store(pref_addr_buf, len);
     tp->set(QUICTransportParameterId::PREFERRED_ADDRESS, pref_addr_buf, len);
   }
+  if (tp_config.active_cid_limit() != 0) {
+    tp->set(QUICTransportParameterId::ACTIVE_CONNECTION_ID_LIMIT, tp_config.active_cid_limit());
+  }
 
   // MAYs (server)
   tp->set(QUICTransportParameterId::STATELESS_RESET_TOKEN, this->_reset_token.buf(), QUICStatelessResetToken::LEN);
@@ -421,6 +424,9 @@
     tp->set(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI, tp_config.initial_max_stream_data_uni());
   }
   tp->set(QUICTransportParameterId::ACK_DELAY_EXPONENT, tp_config.ack_delay_exponent());
+  if (tp_config.active_cid_limit() != 0) {
+    tp->set(QUICTransportParameterId::ACTIVE_CONNECTION_ID_LIMIT, tp_config.active_cid_limit());
+  }
 
   this->_local_transport_parameters = std::shared_ptr<QUICTransportParameters>(tp);
   this->_hs_protocol->set_local_transport_parameters(std::unique_ptr<QUICTransportParameters>(tp));
diff --git a/iocore/net/quic/QUICHandshake.h b/iocore/net/quic/QUICHandshake.h
index 4dda103..49ef437 100644
--- a/iocore/net/quic/QUICHandshake.h
+++ b/iocore/net/quic/QUICHandshake.h
@@ -51,9 +51,9 @@
   virtual QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override;
 
   // QUICFrameGenerator
-  bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override;
+  bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) override;
   QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            ink_hrtime timestamp) override;
+                            size_t current_packet_size, uint32_t seq_num) override;
 
   // for client side
   QUICConnectionErrorUPtr start(const QUICTPConfig &tp_config, QUICPacketFactory *packet_factory, bool vn_exercise_enabled);
diff --git a/iocore/net/quic/QUICIncomingFrameBuffer.cc b/iocore/net/quic/QUICIncomingFrameBuffer.cc
index 154563f..373d009 100644
--- a/iocore/net/quic/QUICIncomingFrameBuffer.cc
+++ b/iocore/net/quic/QUICIncomingFrameBuffer.cc
@@ -113,7 +113,11 @@
     this->_recv_offset = offset + len;
     this->_recv_buffer.push(stream_frame);
   } else {
-    this->_out_of_order_queue.insert(std::make_pair(offset, stream_frame));
+    auto result = this->_out_of_order_queue.insert(std::make_pair(offset, stream_frame));
+    if (!result.second) {
+      // Duplicate frame doesn't need to be inserted
+      delete stream_frame;
+    }
   }
 
   return nullptr;
@@ -134,30 +138,30 @@
   // stream with fin flag {11.3. Stream Final Offset}
   // Once a final offset for a stream is known, it cannot change.
   // If a RESET_STREAM or STREAM frame causes the final offset to change for a stream,
-  // an endpoint SHOULD respond with a FINAL_OFFSET_ERROR error (see Section 12).
+  // an endpoint SHOULD respond with a FINAL_SIZE_ERROR error (see Section 12).
   // A receiver SHOULD treat receipt of data at or beyond the final offset as a
-  // FINAL_OFFSET_ERROR error, even after a stream is closed.
+  // FINAL_SIZE_ERROR error, even after a stream is closed.
 
   // {11.3. Stream Final Offset}
   // A receiver SHOULD treat receipt of data at or beyond the final offset as a
-  // FINAL_OFFSET_ERROR error, even after a stream is closed.
+  // FINAL_SIZE_ERROR error, even after a stream is closed.
   if (fin_flag) {
     if (this->_fin_offset != UINT64_MAX) {
       if (this->_fin_offset == offset + len) {
         // dup fin frame
         return nullptr;
       }
-      return std::make_unique<QUICConnectionError>(QUICTransErrorCode::FINAL_OFFSET_ERROR);
+      return std::make_unique<QUICConnectionError>(QUICTransErrorCode::FINAL_SIZE_ERROR);
     }
 
     this->_fin_offset = offset + len;
 
     if (this->_max_offset > this->_fin_offset) {
-      return std::make_unique<QUICConnectionError>(QUICTransErrorCode::FINAL_OFFSET_ERROR);
+      return std::make_unique<QUICConnectionError>(QUICTransErrorCode::FINAL_SIZE_ERROR);
     }
 
   } else if (this->_fin_offset != UINT64_MAX && this->_fin_offset <= offset) {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::FINAL_OFFSET_ERROR);
+    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::FINAL_SIZE_ERROR);
   }
   this->_max_offset = std::max(offset + len, this->_max_offset);
 
@@ -242,7 +246,11 @@
     this->_recv_offset = offset + len;
     this->_recv_buffer.push(crypto_frame);
   } else {
-    this->_out_of_order_queue.insert(std::make_pair(offset, crypto_frame));
+    auto result = this->_out_of_order_queue.insert(std::make_pair(offset, crypto_frame));
+    if (!result.second) {
+      // Duplicate frame doesn't need to be inserted
+      delete crypto_frame;
+    }
   }
 
   return nullptr;
diff --git a/iocore/net/quic/QUICKeyGenerator.cc b/iocore/net/quic/QUICKeyGenerator.cc
index 05c1796..98d091d 100644
--- a/iocore/net/quic/QUICKeyGenerator.cc
+++ b/iocore/net/quic/QUICKeyGenerator.cc
@@ -34,7 +34,7 @@
 using namespace std::literals;
 
 constexpr static uint8_t QUIC_VERSION_1_SALT[] = {
-  0xef, 0x4f, 0xb0, 0xab, 0xb4, 0x74, 0x70, 0xc4, 0x1b, 0xef, 0xcf, 0x80, 0x31, 0x33, 0x4f, 0xae, 0x48, 0x5e, 0x09, 0xa0,
+  0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, 0xd2, 0x43, 0x2b, 0xb4, 0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02,
 };
 constexpr static std::string_view LABEL_FOR_CLIENT_INITIAL_SECRET("client in"sv);
 constexpr static std::string_view LABEL_FOR_SERVER_INITIAL_SECRET("server in"sv);
diff --git a/iocore/net/quic/QUICLossDetector.cc b/iocore/net/quic/QUICLossDetector.cc
index 7cd94c0..4d6397b 100644
--- a/iocore/net/quic/QUICLossDetector.cc
+++ b/iocore/net/quic/QUICLossDetector.cc
@@ -29,14 +29,20 @@
 #include "QUICEvents.h"
 #include "QUICDebugNames.h"
 #include "QUICFrameGenerator.h"
+#include "QUICPinger.h"
+#include "QUICPadder.h"
+#include "QUICPacketProtectionKeyInfo.h"
 
-#define QUICLDDebug(fmt, ...) Debug("quic_loss_detector", "[%s] " fmt, this->_info->cids().data(), ##__VA_ARGS__)
-#define QUICLDVDebug(fmt, ...) Debug("v_quic_loss_detector", "[%s] " fmt, this->_info->cids().data(), ##__VA_ARGS__)
+#define QUICLDDebug(fmt, ...) \
+  Debug("quic_loss_detector", "[%s] " fmt, this->_context.connection_info()->cids().data(), ##__VA_ARGS__)
+#define QUICLDVDebug(fmt, ...) \
+  Debug("v_quic_loss_detector", "[%s] " fmt, this->_context.connection_info()->cids().data(), ##__VA_ARGS__)
 
-QUICLossDetector::QUICLossDetector(QUICConnectionInfoProvider *info, QUICCongestionController *cc, QUICRTTMeasure *rtt_measure,
-                                   const QUICLDConfig &ld_config)
-  : _info(info), _rtt_measure(rtt_measure), _cc(cc)
+QUICLossDetector::QUICLossDetector(QUICLDContext &context, QUICCongestionController *cc, QUICRTTMeasure *rtt_measure,
+                                   QUICPinger *pinger, QUICPadder *padder)
+  : _rtt_measure(rtt_measure), _pinger(pinger), _padder(padder), _cc(cc), _context(context)
 {
+  auto &ld_config             = _context.ld_config();
   this->mutex                 = new_ProxyMutex();
   this->_loss_detection_mutex = new_ProxyMutex();
 
@@ -133,6 +139,9 @@
   ink_hrtime now                 = packet_info->time_sent;
   size_t sent_bytes              = packet_info->sent_bytes;
 
+  QUICLDDebug("%s packet sent : %" PRIu64 " bytes: %lu ack_eliciting: %d", QUICDebugNames::pn_space(packet_info->pn_space),
+              packet_number, sent_bytes, ack_eliciting);
+
   this->_add_to_sent_packet_list(packet_number, std::move(packet_info));
 
   if (in_flight) {
@@ -178,6 +187,21 @@
   this->_ack_delay_exponent = ack_delay_exponent;
 }
 
+bool
+QUICLossDetector::_include_ack_eliciting(const std::vector<QUICPacketInfo *> &acked_packets, int index) const
+{
+  // Find out ack_elicting packet.
+  // FIXME: this loop is the same as _on_ack_received's loop it would better
+  // to combine it.
+  for (auto packet : acked_packets) {
+    if (packet->ack_eliciting) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 void
 QUICLossDetector::_on_ack_received(const QUICAckFrame &ack_frame, QUICPacketNumberSpace pn_space)
 {
@@ -185,10 +209,17 @@
 
   int index                          = static_cast<int>(pn_space);
   this->_largest_acked_packet[index] = std::max(this->_largest_acked_packet[index], ack_frame.largest_acknowledged());
+
+  auto newly_acked_packets = this->_determine_newly_acked_packets(ack_frame, index);
+  if (newly_acked_packets.empty()) {
+    return;
+  }
+
   // If the largest acknowledged is newly acked and
   //  ack-eliciting, update the RTT.
   auto pi = this->_sent_packets[index].find(ack_frame.largest_acknowledged());
-  if (pi != this->_sent_packets[index].end() && pi->second->ack_eliciting) {
+  if (pi != this->_sent_packets[index].end() &&
+      (pi->second->ack_eliciting || this->_include_ack_eliciting(newly_acked_packets, index))) {
     ink_hrtime latest_rtt = Thread::get_hrtime() - pi->second->time_sent;
     // _latest_rtt is nanosecond but ack_frame.ack_delay is microsecond and scaled
     ink_hrtime delay = HRTIME_USECONDS(ack_frame.ack_delay() << this->_ack_delay_exponent);
@@ -205,21 +236,8 @@
   }
 
   // Find all newly acked packets.
-  bool newly_acked_packets = false;
-  for (auto &&range : this->_determine_newly_acked_packets(ack_frame)) {
-    for (auto ite = this->_sent_packets[index].begin(); ite != this->_sent_packets[index].end(); /* no increment here*/) {
-      auto tmp_ite = ite;
-      tmp_ite++;
-      if (range.contains(ite->first)) {
-        newly_acked_packets = true;
-        this->_on_packet_acked(*(ite->second));
-      }
-      ite = tmp_ite;
-    }
-  }
-
-  if (!newly_acked_packets) {
-    return;
+  for (auto info : newly_acked_packets) {
+    this->_on_packet_acked(*info);
   }
 
   QUICLDVDebug("[%s] Unacked packets %lu (retransmittable %u, includes %u handshake packets)", QUICDebugNames::pn_space(pn_space),
@@ -240,9 +258,10 @@
 QUICLossDetector::_on_packet_acked(const QUICPacketInfo &acked_packet)
 {
   SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread());
-  // QUICLDDebug("Packet number %" PRIu64 " has been acked", acked_packet_number);
+  QUICLDDebug("[%s] Packet number %" PRIu64 " has been acked", QUICDebugNames::pn_space(acked_packet.pn_space),
+              acked_packet.packet_number);
 
-  if (acked_packet.ack_eliciting) {
+  if (acked_packet.in_flight) {
     this->_cc->on_packet_acked(acked_packet);
   }
 
@@ -276,6 +295,31 @@
 void
 QUICLossDetector::_set_loss_detection_timer()
 {
+  std::function<void(ink_hrtime)> update_timer = [this](ink_hrtime time) {
+    this->_loss_detection_alarm_at = time;
+    if (!this->_loss_detection_timer) {
+      this->_loss_detection_timer = eventProcessor.schedule_every(this, HRTIME_MSECONDS(25));
+    }
+  };
+
+  QUICPacketNumberSpace pn_space;
+  ink_hrtime alarm = this->_get_earliest_loss_time(pn_space);
+  if (alarm != 0) {
+    update_timer(alarm);
+    QUICLDDebug("[%s] time threshold loss detection timer: %" PRId64 "ms", QUICDebugNames::pn_space(pn_space),
+                (this->_loss_detection_alarm_at - Thread::get_hrtime()) / HRTIME_MSECOND);
+    return;
+  }
+
+  if (this->_crypto_outstanding > 0 || this->_is_client_without_one_rtt_key()) {
+    // Crypto retransmission timer.
+    alarm = this->_time_of_last_sent_crypto_packet + this->_rtt_measure->handshake_retransmit_timeout();
+    update_timer(alarm);
+    QUICLDDebug("%s crypto packet alarm will be set: %" PRId64 "ms", QUICDebugNames::pn_space(pn_space),
+                (alarm - this->_time_of_last_sent_crypto_packet) / HRTIME_MSECOND);
+    return;
+  }
+
   // Don't arm the alarm if there are no packets with retransmittable data in flight.
   // -- MODIFIED CODE --
   // In psuedocode, `bytes_in_flight` is used, but we're tracking "retransmittable data in flight" by `_ack_eliciting_outstanding`
@@ -291,29 +335,11 @@
   }
   // -- END OF MODIFIED CODE --
 
-  QUICPacketNumberSpace pn_space;
-  ink_hrtime loss_time = this->_get_earliest_loss_time(pn_space);
-  if (loss_time != 0) {
-    // Time threshold loss detection.
-    this->_loss_detection_alarm_at = loss_time;
-    QUICLDDebug("[%s] time threshold loss detection timer: %" PRId64, QUICDebugNames::pn_space(pn_space),
-                this->_loss_detection_alarm_at);
-  } else if (this->_crypto_outstanding) {
-    // Handshake retransmission alarm.
-    this->_loss_detection_alarm_at = this->_time_of_last_sent_crypto_packet + this->_rtt_measure->handshake_retransmit_timeout();
-    QUICLDDebug("%s crypto packet alarm will be set: %" PRId64, QUICDebugNames::pn_space(pn_space), this->_loss_detection_alarm_at);
-    // -- ADDITIONAL CODE --
-    // In psudocode returning here, but we don't do for scheduling _loss_detection_alarm event.
-    // -- END OF ADDITIONAL CODE --
-  } else {
-    // PTO Duration
-    this->_loss_detection_alarm_at = this->_time_of_last_sent_ack_eliciting_packet + this->_rtt_measure->current_pto_period();
-    QUICLDDebug("[%s] PTO timeout will be set: %" PRId64, QUICDebugNames::pn_space(pn_space), this->_loss_detection_alarm_at);
-  }
-
-  if (!this->_loss_detection_timer) {
-    this->_loss_detection_timer = eventProcessor.schedule_every(this, HRTIME_MSECONDS(25));
-  }
+  // PTO Duration
+  alarm = this->_time_of_last_sent_ack_eliciting_packet + this->_rtt_measure->current_pto_period();
+  update_timer(alarm);
+  QUICLDDebug("[%s] PTO timeout will be set: %" PRId64 "ms", QUICDebugNames::pn_space(pn_space),
+              (alarm - this->_time_of_last_sent_ack_eliciting_packet) / HRTIME_MSECOND);
 }
 
 void
@@ -326,11 +352,23 @@
     this->_detect_lost_packets(pn_space);
   } else if (this->_crypto_outstanding) {
     // Handshake retransmission alarm.
+    QUICLDVDebug("Crypto Retranmission");
     this->_retransmit_all_unacked_crypto_data();
     this->_rtt_measure->set_crypto_count(this->_rtt_measure->crypto_count() + 1);
+  } else if (this->_is_client_without_one_rtt_key()) {
+    // Client sends an anti-deadlock packet: Initial is padded
+    // to earn more anti-amplification credit,
+    // a Handshake packet proves address ownership.
+    if (this->_context.key_info()->is_encryption_key_available(QUICKeyPhase::HANDSHAKE)) {
+      this->_send_one_handshake_packets();
+    } else {
+      this->_send_one_padded_packets();
+    }
+
+    this->_rtt_measure->set_crypto_count(this->_rtt_measure->crypto_count() + 1);
   } else {
     QUICLDVDebug("PTO");
-    this->_send_two_packets();
+    this->_send_one_or_two_packet();
     this->_rtt_measure->set_pto_count(this->_rtt_measure->pto_count() + 1);
   }
 
@@ -358,6 +396,8 @@
   SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread());
   this->_loss_time[static_cast<int>(pn_space)] = 0;
   ink_hrtime loss_delay = this->_k_time_threshold * std::max(this->_rtt_measure->latest_rtt(), this->_rtt_measure->smoothed_rtt());
+  loss_delay            = std::min(loss_delay, this->_rtt_measure->k_granularity());
+
   std::map<QUICPacketNumber, QUICPacketInfo *> lost_packets;
 
   // Packets sent before this time are deemed lost.
@@ -390,12 +430,12 @@
 
       if (unacked->in_flight) {
         lost_packets.insert({it->first, it->second.get()});
-      } else if (this->_loss_time[static_cast<int>(pn_space)] == 0) {
-        this->_loss_time[static_cast<int>(pn_space)] = unacked->time_sent + loss_delay;
-      } else {
-        this->_loss_time[static_cast<int>(pn_space)] =
-          std::min(this->_loss_time[static_cast<int>(pn_space)], unacked->time_sent + loss_delay);
       }
+    } else if (this->_loss_time[static_cast<int>(pn_space)] == 0) {
+      this->_loss_time[static_cast<int>(pn_space)] = unacked->time_sent + loss_delay;
+    } else {
+      this->_loss_time[static_cast<int>(pn_space)] =
+        std::min(this->_loss_time[static_cast<int>(pn_space)], unacked->time_sent + loss_delay);
     }
   }
 
@@ -442,10 +482,38 @@
 }
 
 void
-QUICLossDetector::_send_two_packets()
+QUICLossDetector::_send_packet(QUICEncryptionLevel level, bool padded)
 {
-  SCOPED_MUTEX_LOCK(lock, this->_loss_detection_mutex, this_ethread());
-  // TODO sent ping
+  if (padded) {
+    this->_padder->request(level);
+  } else {
+    this->_pinger->request();
+  }
+  this->_cc->add_extra_credit();
+}
+
+void
+QUICLossDetector::_send_one_or_two_packet()
+{
+  this->_send_packet(QUICEncryptionLevel::ONE_RTT);
+  this->_send_packet(QUICEncryptionLevel::ONE_RTT);
+  ink_assert(this->_pinger->count() >= 2);
+  QUICLDDebug("[%s] send ping frame %" PRIu64, QUICDebugNames::encryption_level(QUICEncryptionLevel::ONE_RTT),
+              this->_pinger->count());
+}
+
+void
+QUICLossDetector::_send_one_handshake_packets()
+{
+  this->_send_packet(QUICEncryptionLevel::HANDSHAKE);
+  QUICLDDebug("[%s] send handshake packet", QUICDebugNames::encryption_level(QUICEncryptionLevel::HANDSHAKE));
+}
+
+void
+QUICLossDetector::_send_one_padded_packets()
+{
+  this->_send_packet(QUICEncryptionLevel::INITIAL, true);
+  QUICLDDebug("[%s] send PADDING frame", QUICDebugNames::encryption_level(QUICEncryptionLevel::INITIAL));
 }
 
 // ===== Functions below are helper functions =====
@@ -466,9 +534,10 @@
   }
 }
 
-std::set<QUICAckFrame::PacketNumberRange>
-QUICLossDetector::_determine_newly_acked_packets(const QUICAckFrame &ack_frame)
+std::vector<QUICPacketInfo *>
+QUICLossDetector::_determine_newly_acked_packets(const QUICAckFrame &ack_frame, int pn_space)
 {
+  std::vector<QUICPacketInfo *> packets;
   std::set<QUICAckFrame::PacketNumberRange> numbers;
   QUICPacketNumber x = ack_frame.largest_acknowledged();
   numbers.insert({x, static_cast<uint64_t>(x) - ack_frame.ack_block_section()->first_ack_block()});
@@ -479,7 +548,15 @@
     x -= block.length() + 1;
   }
 
-  return numbers;
+  for (auto &&range : numbers) {
+    for (auto ite = this->_sent_packets[pn_space].rbegin(); ite != this->_sent_packets[pn_space].rend(); ite++) {
+      if (range.contains(ite->first)) {
+        packets.push_back(ite->second.get());
+      }
+    }
+  }
+
+  return packets;
 }
 
 void
@@ -542,6 +619,16 @@
   }
 }
 
+bool
+QUICLossDetector::_is_client_without_one_rtt_key() const
+{
+  return this->_context.connection_info()->direction() == NET_VCONNECTION_OUT &&
+         !((this->_context.key_info()->is_encryption_key_available(QUICKeyPhase::PHASE_1) &&
+            this->_context.key_info()->is_decryption_key_available(QUICKeyPhase::PHASE_1)) ||
+           (this->_context.key_info()->is_encryption_key_available(QUICKeyPhase::PHASE_0) &&
+            this->_context.key_info()->is_decryption_key_available(QUICKeyPhase::PHASE_0)));
+}
+
 //
 // QUICRTTMeasure
 //
@@ -566,30 +653,28 @@
 void
 QUICRTTMeasure::update_rtt(ink_hrtime latest_rtt, ink_hrtime ack_delay)
 {
-  // additional code
   this->_latest_rtt = latest_rtt;
 
+  if (this->_smoothed_rtt == 0) {
+    this->_min_rtt      = 0;
+    this->_smoothed_rtt = this->_latest_rtt;
+    this->_rttvar       = this->_latest_rtt / 2;
+    return;
+  }
+
   // min_rtt ignores ack delay.
   this->_min_rtt = std::min(this->_min_rtt, latest_rtt);
   // Limit ack_delay by max_ack_delay
   ack_delay = std::min(ack_delay, this->_max_ack_delay);
   // Adjust for ack delay if it's plausible.
-  if (this->_latest_rtt - this->_min_rtt > ack_delay) {
-    this->_latest_rtt -= ack_delay;
+  auto adjusted_rtt = this->_latest_rtt;
+  if (adjusted_rtt > this->_min_rtt + ack_delay) {
+    adjusted_rtt -= ack_delay;
+  }
 
-    // the newest spec has removed the max_ack_delay assignment. but we need to assign it in somewhere
-    // this code is from draft-19
-    this->_max_ack_delay = std::max(ack_delay, this->_max_ack_delay);
-  }
   // Based on {{RFC6298}}.
-  if (this->_smoothed_rtt == 0) {
-    this->_smoothed_rtt = latest_rtt;
-    this->_rttvar       = latest_rtt / 2.0;
-  } else {
-    double rttvar_sample = ABS(this->_smoothed_rtt - latest_rtt);
-    this->_rttvar        = 3.0 / 4.0 * this->_rttvar + 1.0 / 4.0 * rttvar_sample;
-    this->_smoothed_rtt  = 7.0 / 8.0 * this->_smoothed_rtt + 1.0 / 8.0 * latest_rtt;
-  }
+  this->_rttvar       = 3.0 / 4.0 * this->_rttvar + 1.0 / 4.0 * ABS(this->_smoothed_rtt - adjusted_rtt);
+  this->_smoothed_rtt = 7.0 / 8.0 * this->_smoothed_rtt + 1.0 / 8.0 * adjusted_rtt;
 }
 
 ink_hrtime
@@ -607,7 +692,7 @@
 QUICRTTMeasure::congestion_period(uint32_t threshold) const
 {
   ink_hrtime pto = this->_smoothed_rtt + std::max(this->_rttvar * 4, this->_k_granularity);
-  return pto * (1 << (threshold - 1));
+  return pto * threshold;
 }
 
 ink_hrtime
@@ -662,6 +747,12 @@
   return this->_pto_count;
 }
 
+ink_hrtime
+QUICRTTMeasure::k_granularity() const
+{
+  return this->_k_granularity;
+}
+
 void
 QUICRTTMeasure::reset()
 {
@@ -669,5 +760,6 @@
   this->_pto_count    = 0;
   this->_smoothed_rtt = 0;
   this->_rttvar       = 0;
-  this->_min_rtt      = INT64_MAX;
+  this->_min_rtt      = 0;
+  this->_latest_rtt   = 0;
 }
diff --git a/iocore/net/quic/QUICLossDetector.h b/iocore/net/quic/QUICLossDetector.h
index 1b7a536..5e4005b 100644
--- a/iocore/net/quic/QUICLossDetector.h
+++ b/iocore/net/quic/QUICLossDetector.h
@@ -36,26 +36,14 @@
 #include "QUICFrame.h"
 #include "QUICFrameHandler.h"
 #include "QUICConnection.h"
+#include "QUICContext.h"
+#include "QUICCongestionController.h"
 
+class QUICPadder;
+class QUICPinger;
 class QUICLossDetector;
 class QUICRTTMeasure;
 
-struct QUICPacketInfo {
-  // 6.3.1.  Sent Packet Fields
-  QUICPacketNumber packet_number;
-  ink_hrtime time_sent;
-  bool ack_eliciting;
-  bool is_crypto_packet;
-  bool in_flight;
-  size_t sent_bytes;
-
-  // addition
-  QUICPacketType type;
-  std::vector<QUICFrameInfo> frames;
-  QUICPacketNumberSpace pn_space;
-  // end
-};
-
 using QUICPacketInfoUPtr = std::unique_ptr<QUICPacketInfo>;
 
 class QUICRTTProvider
@@ -68,19 +56,18 @@
   virtual ink_hrtime congestion_period(uint32_t threshold) const = 0;
 };
 
-class QUICCongestionController
+class QUICNewRenoCongestionController : public QUICCongestionController
 {
 public:
-  QUICCongestionController(const QUICRTTProvider &rtt_provider, QUICConnectionInfoProvider *info, const QUICCCConfig &cc_config);
-  virtual ~QUICCongestionController() {}
-  void on_packet_sent(size_t bytes_sent);
-  void on_packet_acked(const QUICPacketInfo &acked_packet);
-  virtual void on_packets_lost(const std::map<QUICPacketNumber, QUICPacketInfo *> &packets);
-  void on_retransmission_timeout_verified();
-  void process_ecn(const QUICPacketInfo &acked_largest_packet, const QUICAckFrame::EcnSection *ecn_section);
+  QUICNewRenoCongestionController(QUICCCContext &context);
+  virtual ~QUICNewRenoCongestionController() {}
+  void on_packet_sent(size_t bytes_sent) override;
+  void on_packet_acked(const QUICPacketInfo &acked_packet) override;
+  virtual void on_packets_lost(const std::map<QUICPacketNumber, QUICPacketInfo *> &packets) override;
+  void process_ecn(const QUICPacketInfo &acked_largest_packet, const QUICAckFrame::EcnSection *ecn_section) override;
   bool check_credit() const;
-  uint32_t open_window() const;
-  void reset();
+  uint32_t credit() const override;
+  void reset() override;
   bool is_app_limited();
 
   // Debug
@@ -88,6 +75,8 @@
   uint32_t congestion_window() const;
   uint32_t current_ssthresh() const;
 
+  void add_extra_credit() override;
+
 private:
   Ptr<ProxyMutex> _cc_mutex;
 
@@ -97,32 +86,33 @@
   bool _in_window_lost(const std::map<QUICPacketNumber, QUICPacketInfo *> &lost_packets, QUICPacketInfo *largest_lost_packet,
                        ink_hrtime period) const;
 
+  uint32_t _extra_packets_count = 0;
+
   // [draft-17 recovery] 7.9.1. Constants of interest
   // Values will be loaded from records.config via QUICConfig at constructor
   uint32_t _k_max_datagram_size               = 0;
   uint32_t _k_initial_window                  = 0;
   uint32_t _k_minimum_window                  = 0;
   float _k_loss_reduction_factor              = 0.0;
-  uint32_t _k_persistent_congestion_threshold = 0;
+  uint32_t _k_persistent_congestion_threshold = 3;
 
   // [draft-17 recovery] 7.9.2. Variables of interest
-  uint32_t _ecn_ce_counter        = 0;
-  uint32_t _bytes_in_flight       = 0;
-  uint32_t _congestion_window     = 0;
-  ink_hrtime _recovery_start_time = 0;
-  uint32_t _ssthresh              = UINT32_MAX;
+  uint32_t _ecn_ce_counter                   = 0;
+  uint32_t _bytes_in_flight                  = 0;
+  uint32_t _congestion_window                = 0;
+  ink_hrtime _congestion_recovery_start_time = 0;
+  uint32_t _ssthresh                         = UINT32_MAX;
 
-  QUICConnectionInfoProvider *_info = nullptr;
-  const QUICRTTProvider &_rtt_provider;
+  bool _in_congestion_recovery(ink_hrtime sent_time);
 
-  bool _in_recovery(ink_hrtime sent_time);
+  QUICCCContext &_context;
 };
 
 class QUICLossDetector : public Continuation, public QUICFrameHandler
 {
 public:
-  QUICLossDetector(QUICConnectionInfoProvider *info, QUICCongestionController *cc, QUICRTTMeasure *rtt_measure,
-                   const QUICLDConfig &ld_config);
+  QUICLossDetector(QUICLDContext &context, QUICCongestionController *cc, QUICRTTMeasure *rtt_measure, QUICPinger *pinger,
+                   QUICPadder *padder);
   ~QUICLossDetector();
 
   int event_handler(int event, Event *edata);
@@ -178,15 +168,24 @@
 
   ink_hrtime _get_earliest_loss_time(QUICPacketNumberSpace &pn_space);
 
-  std::set<QUICAckFrame::PacketNumberRange> _determine_newly_acked_packets(const QUICAckFrame &ack_frame);
+  std::vector<QUICPacketInfo *> _determine_newly_acked_packets(const QUICAckFrame &ack_frame, int pn_space);
+  bool _include_ack_eliciting(const std::vector<QUICPacketInfo *> &acked_packets, int index) const;
 
   void _retransmit_all_unacked_crypto_data();
-  void _send_one_packet();
-  void _send_two_packets();
+  void _send_one_or_two_packet();
+  void _send_one_handshake_packets();
+  void _send_one_padded_packets();
 
-  QUICConnectionInfoProvider *_info = nullptr;
-  QUICRTTMeasure *_rtt_measure      = nullptr;
-  QUICCongestionController *_cc     = nullptr;
+  void _send_packet(QUICEncryptionLevel level, bool padded = false);
+
+  bool _is_client_without_one_rtt_key() const;
+
+  QUICRTTMeasure *_rtt_measure  = nullptr;
+  QUICPinger *_pinger           = nullptr;
+  QUICPadder *_padder           = nullptr;
+  QUICCongestionController *_cc = nullptr;
+
+  QUICLDContext &_context;
 };
 
 class QUICRTTMeasure : public QUICRTTProvider
@@ -219,11 +218,14 @@
   void update_rtt(ink_hrtime latest_rtt, ink_hrtime ack_delay);
   void reset();
 
+  ink_hrtime k_granularity() const;
+
 private:
   // related to rtt calculate
-  uint32_t _crypto_count    = 0;
-  uint32_t _pto_count       = 0;
-  ink_hrtime _max_ack_delay = 0;
+  uint32_t _crypto_count = 0;
+  uint32_t _pto_count    = 0;
+  // FIXME should be set by transport parameters
+  ink_hrtime _max_ack_delay = HRTIME_MSECONDS(25);
 
   // rtt vars
   ink_hrtime _latest_rtt   = 0;
@@ -233,5 +235,5 @@
 
   // config
   ink_hrtime _k_granularity = 0;
-  ink_hrtime _k_initial_rtt = 0;
+  ink_hrtime _k_initial_rtt = HRTIME_MSECONDS(500);
 };
diff --git a/iocore/net/quic/QUICCongestionController.cc b/iocore/net/quic/QUICNewRenoCongestionController.cc
similarity index 62%
rename from iocore/net/quic/QUICCongestionController.cc
rename to iocore/net/quic/QUICNewRenoCongestionController.cc
index 79d8fd8..080c910 100644
--- a/iocore/net/quic/QUICCongestionController.cc
+++ b/iocore/net/quic/QUICNewRenoCongestionController.cc
@@ -24,22 +24,24 @@
 #include <tscore/Diags.h>
 #include <QUICLossDetector.h>
 
-#define QUICCCDebug(fmt, ...)                                                \
-  Debug("quic_cc",                                                           \
-        "[%s] "                                                              \
-        "window: %" PRIu32 " bytes: %" PRIu32 " ssthresh: %" PRIu32 " " fmt, \
-        this->_info->cids().data(), this->_congestion_window, this->_bytes_in_flight, this->_ssthresh, ##__VA_ARGS__)
+#define QUICCCDebug(fmt, ...)                                                                                               \
+  Debug("quic_cc",                                                                                                          \
+        "[%s] "                                                                                                             \
+        "window: %" PRIu32 " bytes: %" PRIu32 " ssthresh: %" PRIu32 " extra: %" PRIu32 " " fmt,                             \
+        this->_context.connection_info()->cids().data(), this->_congestion_window, this->_bytes_in_flight, this->_ssthresh, \
+        this->_extra_packets_count, ##__VA_ARGS__)
 
-#define QUICCCError(fmt, ...)                                                \
-  Error("quic_cc",                                                           \
-        "[%s] "                                                              \
-        "window: %" PRIu32 " bytes: %" PRIu32 " ssthresh: %" PRIu32 " " fmt, \
-        this->_info->cids().data(), this->_congestion_window, this->_bytes_in_flight, this->_ssthresh, ##__VA_ARGS__)
+#define QUICCCError(fmt, ...)                                                                                               \
+  Error("quic_cc",                                                                                                          \
+        "[%s] "                                                                                                             \
+        "window: %" PRIu32 " bytes: %" PRIu32 " ssthresh: %" PRIu32 " extra %" PRIu32 " " fmt,                              \
+        this->_context.connection_info()->cids().data(), this->_congestion_window, this->_bytes_in_flight, this->_ssthresh, \
+        this->_extra_packets_count, ##__VA_ARGS__)
 
-QUICCongestionController::QUICCongestionController(const QUICRTTProvider &rtt_provider, QUICConnectionInfoProvider *info,
-                                                   const QUICCCConfig &cc_config)
-  : _cc_mutex(new_ProxyMutex()), _info(info), _rtt_provider(rtt_provider)
+QUICNewRenoCongestionController::QUICNewRenoCongestionController(QUICCCContext &context)
+  : _cc_mutex(new_ProxyMutex()), _context(context)
 {
+  auto &cc_config                          = context.cc_config();
   this->_k_max_datagram_size               = cc_config.max_datagram_size();
   this->_k_initial_window                  = cc_config.initial_window();
   this->_k_minimum_window                  = cc_config.minimum_window();
@@ -50,32 +52,36 @@
 }
 
 void
-QUICCongestionController::on_packet_sent(size_t bytes_sent)
+QUICNewRenoCongestionController::on_packet_sent(size_t bytes_sent)
 {
   SCOPED_MUTEX_LOCK(lock, this->_cc_mutex, this_ethread());
+  if (this->_extra_packets_count > 0) {
+    --this->_extra_packets_count;
+  }
+
   this->_bytes_in_flight += bytes_sent;
 }
 
 bool
-QUICCongestionController::_in_recovery(ink_hrtime sent_time)
+QUICNewRenoCongestionController::_in_congestion_recovery(ink_hrtime sent_time)
 {
-  return sent_time <= this->_recovery_start_time;
+  return sent_time <= this->_congestion_recovery_start_time;
 }
 
 bool
-QUICCongestionController::is_app_limited()
+QUICNewRenoCongestionController::is_app_limited()
 {
   // FIXME : don't known how does app worked here
   return false;
 }
 
 void
-QUICCongestionController::on_packet_acked(const QUICPacketInfo &acked_packet)
+QUICNewRenoCongestionController::on_packet_acked(const QUICPacketInfo &acked_packet)
 {
   // Remove from bytes_in_flight.
   SCOPED_MUTEX_LOCK(lock, this->_cc_mutex, this_ethread());
   this->_bytes_in_flight -= acked_packet.sent_bytes;
-  if (this->_in_recovery(acked_packet.time_sent)) {
+  if (this->_in_congestion_recovery(acked_packet.time_sent)) {
     // Do not increase congestion window in recovery period.
     return;
   }
@@ -101,12 +107,12 @@
 // the original one is:
 //   CongestionEvent(sent_time):
 void
-QUICCongestionController::_congestion_event(ink_hrtime sent_time)
+QUICNewRenoCongestionController::_congestion_event(ink_hrtime sent_time)
 {
-  // Start a new congestion event if the sent time is larger
-  // than the start time of the previous recovery epoch.
-  if (!this->_in_recovery(sent_time)) {
-    this->_recovery_start_time = Thread::get_hrtime();
+  // Start a new congestion event if packet was sent after the
+  // start of the previous congestion recovery period.
+  if (!this->_in_congestion_recovery(sent_time)) {
+    this->_congestion_recovery_start_time = Thread::get_hrtime();
     this->_congestion_window *= this->_k_loss_reduction_factor;
     this->_congestion_window = std::max(this->_congestion_window, this->_k_minimum_window);
     this->_ssthresh          = this->_congestion_window;
@@ -117,7 +123,8 @@
 // the original one is:
 //   ProcessECN(ack):
 void
-QUICCongestionController::process_ecn(const QUICPacketInfo &acked_largest_packet, const QUICAckFrame::EcnSection *ecn_section)
+QUICNewRenoCongestionController::process_ecn(const QUICPacketInfo &acked_largest_packet,
+                                             const QUICAckFrame::EcnSection *ecn_section)
 {
   // If the ECN-CE counter reported by the peer has increased,
   // this could be a new congestion event.
@@ -131,10 +138,10 @@
 }
 
 bool
-QUICCongestionController::_in_persistent_congestion(const std::map<QUICPacketNumber, QUICPacketInfo *> &lost_packets,
-                                                    QUICPacketInfo *largest_lost_packet)
+QUICNewRenoCongestionController::_in_persistent_congestion(const std::map<QUICPacketNumber, QUICPacketInfo *> &lost_packets,
+                                                           QUICPacketInfo *largest_lost_packet)
 {
-  ink_hrtime period = this->_rtt_provider.congestion_period(this->_k_persistent_congestion_threshold);
+  ink_hrtime period = this->_context.rtt_provider()->congestion_period(this->_k_persistent_congestion_threshold);
   // Determine if all packets in the window before the
   // newest lost packet, including the edges, are marked
   // lost
@@ -145,7 +152,7 @@
 // the original one is:
 //   OnPacketsLost(lost_packets):
 void
-QUICCongestionController::on_packets_lost(const std::map<QUICPacketNumber, QUICPacketInfo *> &lost_packets)
+QUICNewRenoCongestionController::on_packets_lost(const std::map<QUICPacketNumber, QUICPacketInfo *> &lost_packets)
 {
   if (lost_packets.empty()) {
     return;
@@ -157,8 +164,6 @@
     this->_bytes_in_flight -= lost_packet.second->sent_bytes;
   }
   QUICPacketInfo *largest_lost_packet = lost_packets.rbegin()->second;
-  // Start a new recovery epoch if the lost packet is larger
-  // than the end of the previous recovery epoch.
   this->_congestion_event(largest_lost_packet->time_sent);
 
   // Collapse congestion window if persistent congestion
@@ -168,7 +173,7 @@
 }
 
 bool
-QUICCongestionController::check_credit() const
+QUICNewRenoCongestionController::check_credit() const
 {
   if (this->_bytes_in_flight >= this->_congestion_window) {
     QUICCCDebug("Congestion control pending");
@@ -178,8 +183,12 @@
 }
 
 uint32_t
-QUICCongestionController::open_window() const
+QUICNewRenoCongestionController::credit() const
 {
+  if (this->_extra_packets_count) {
+    return UINT32_MAX;
+  }
+
   if (this->check_credit()) {
     return this->_congestion_window - this->_bytes_in_flight;
   } else {
@@ -188,38 +197,38 @@
 }
 
 uint32_t
-QUICCongestionController::bytes_in_flight() const
+QUICNewRenoCongestionController::bytes_in_flight() const
 {
   return this->_bytes_in_flight;
 }
 
 uint32_t
-QUICCongestionController::congestion_window() const
+QUICNewRenoCongestionController::congestion_window() const
 {
   return this->_congestion_window;
 }
 
 uint32_t
-QUICCongestionController::current_ssthresh() const
+QUICNewRenoCongestionController::current_ssthresh() const
 {
   return this->_ssthresh;
 }
 
 // [draft-17 recovery] 7.9.3.  Initialization
 void
-QUICCongestionController::reset()
+QUICNewRenoCongestionController::reset()
 {
   SCOPED_MUTEX_LOCK(lock, this->_cc_mutex, this_ethread());
 
-  this->_bytes_in_flight     = 0;
-  this->_congestion_window   = this->_k_initial_window;
-  this->_recovery_start_time = 0;
-  this->_ssthresh            = UINT32_MAX;
+  this->_bytes_in_flight                = 0;
+  this->_congestion_window              = this->_k_initial_window;
+  this->_congestion_recovery_start_time = 0;
+  this->_ssthresh                       = UINT32_MAX;
 }
 
 bool
-QUICCongestionController::_in_window_lost(const std::map<QUICPacketNumber, QUICPacketInfo *> &lost_packets,
-                                          QUICPacketInfo *largest_lost_packet, ink_hrtime period) const
+QUICNewRenoCongestionController::_in_window_lost(const std::map<QUICPacketNumber, QUICPacketInfo *> &lost_packets,
+                                                 QUICPacketInfo *largest_lost_packet, ink_hrtime period) const
 {
   // check whether packets are continuous. return true if all continuous packets are in period
   QUICPacketNumber next_expected = UINT64_MAX;
@@ -240,3 +249,9 @@
 
   return next_expected == UINT64_MAX ? false : true;
 }
+
+void
+QUICNewRenoCongestionController::add_extra_credit()
+{
+  ++this->_extra_packets_count;
+}
diff --git a/iocore/net/quic/QUICPacket.cc b/iocore/net/quic/QUICPacket.cc
index ec8ac37..534ed27 100644
--- a/iocore/net/quic/QUICPacket.cc
+++ b/iocore/net/quic/QUICPacket.cc
@@ -69,6 +69,12 @@
   return this->_from;
 }
 
+const IpEndpoint &
+QUICPacketHeader::to() const
+{
+  return this->_to;
+}
+
 bool
 QUICPacketHeader::is_crypto_packet() const
 {
@@ -82,16 +88,16 @@
 }
 
 QUICPacketHeaderUPtr
-QUICPacketHeader::load(const IpEndpoint from, ats_unique_buf buf, size_t len, QUICPacketNumber base)
+QUICPacketHeader::load(const IpEndpoint from, const IpEndpoint to, ats_unique_buf buf, size_t len, QUICPacketNumber base)
 {
   QUICPacketHeaderUPtr header = QUICPacketHeaderUPtr(nullptr, &QUICPacketHeaderDeleter::delete_null_header);
   if (QUICInvariants::is_long_header(buf.get())) {
     QUICPacketLongHeader *long_header = quicPacketLongHeaderAllocator.alloc();
-    new (long_header) QUICPacketLongHeader(from, std::move(buf), len, base);
+    new (long_header) QUICPacketLongHeader(from, to, std::move(buf), len, base);
     header = QUICPacketHeaderUPtr(long_header, &QUICPacketHeaderDeleter::delete_long_header);
   } else {
     QUICPacketShortHeader *short_header = quicPacketShortHeaderAllocator.alloc();
-    new (short_header) QUICPacketShortHeader(from, std::move(buf), len, base);
+    new (short_header) QUICPacketShortHeader(from, to, std::move(buf), len, base);
     header = QUICPacketHeaderUPtr(short_header, &QUICPacketHeaderDeleter::delete_short_header);
   }
   return header;
@@ -159,8 +165,9 @@
 // QUICPacketLongHeader
 //
 
-QUICPacketLongHeader::QUICPacketLongHeader(const IpEndpoint from, ats_unique_buf buf, size_t len, QUICPacketNumber base)
-  : QUICPacketHeader(from, std::move(buf), len, base)
+QUICPacketLongHeader::QUICPacketLongHeader(const IpEndpoint from, const IpEndpoint to, ats_unique_buf buf, size_t len,
+                                           QUICPacketNumber base)
+  : QUICPacketHeader(from, to, std::move(buf), len, base)
 {
   this->_key_phase = QUICTypeUtil::key_phase(this->type());
   uint8_t *raw_buf = this->_buf.get();
@@ -172,13 +179,15 @@
 
   size_t offset          = LONG_HDR_OFFSET_CONNECTION_ID;
   this->_destination_cid = {raw_buf + offset, dcil};
-  offset += dcil;
+  offset += dcil + 1;
   this->_source_cid = {raw_buf + offset, scil};
   offset += scil;
 
   if (this->type() != QUICPacketType::VERSION_NEGOTIATION) {
     if (this->type() == QUICPacketType::RETRY) {
-      uint8_t odcil        = (raw_buf[0] & 0x0f) + 3;
+      uint8_t odcil = raw_buf[offset];
+      offset += 1;
+
       this->_original_dcid = {raw_buf + offset, odcil};
       offset += odcil;
     } else {
@@ -219,7 +228,7 @@
 {
   if (this->_type == QUICPacketType::VERSION_NEGOTIATION) {
     this->_buf_len =
-      LONG_HDR_OFFSET_CONNECTION_ID + this->_destination_cid.length() + this->_source_cid.length() + this->_payload_length;
+      LONG_HDR_OFFSET_CONNECTION_ID + this->_destination_cid.length() + 1 + this->_source_cid.length() + this->_payload_length;
   } else {
     this->buf();
   }
@@ -289,9 +298,6 @@
 QUICPacketLongHeader::dcil(uint8_t &dcil, const uint8_t *packet, size_t packet_len)
 {
   if (QUICInvariants::dcil(dcil, packet, packet_len)) {
-    if (dcil != 0) {
-      dcil += 3;
-    }
     return true;
   } else {
     return false;
@@ -302,9 +308,6 @@
 QUICPacketLongHeader::scil(uint8_t &scil, const uint8_t *packet, size_t packet_len)
 {
   if (QUICInvariants::scil(scil, packet, packet_len)) {
-    if (scil != 0) {
-      scil += 3;
-    }
     return true;
   } else {
     return false;
@@ -312,16 +315,16 @@
 }
 
 bool
-QUICPacketLongHeader::token_length(size_t &token_length, uint8_t *field_len, const uint8_t *packet, size_t packet_len)
+QUICPacketLongHeader::token_length(size_t &token_length, uint8_t &field_len, size_t &token_length_filed_offset,
+                                   const uint8_t *packet, size_t packet_len)
 {
   QUICPacketType type = QUICPacketType::UNINITIALIZED;
   QUICPacketLongHeader::type(type, packet, packet_len);
 
   if (type != QUICPacketType::INITIAL) {
     token_length = 0;
-    if (field_len) {
-      *field_len = 0;
-    }
+    field_len    = 0;
+
     return true;
   }
 
@@ -329,66 +332,85 @@
   QUICPacketLongHeader::dcil(dcil, packet, packet_len);
   QUICPacketLongHeader::scil(scil, packet, packet_len);
 
-  size_t offset = LONG_HDR_OFFSET_CONNECTION_ID + dcil + scil;
-  if (offset >= packet_len) {
+  token_length_filed_offset = LONG_HDR_OFFSET_CONNECTION_ID + dcil + 1 + scil;
+  if (token_length_filed_offset >= packet_len) {
     return false;
   }
 
-  if (offset > packet_len) {
-    return false;
-  }
-
-  token_length = QUICIntUtil::read_QUICVariableInt(packet + offset);
-  if (field_len) {
-    *field_len = QUICVariableInt::size(packet + offset);
-  }
+  token_length = QUICIntUtil::read_QUICVariableInt(packet + token_length_filed_offset);
+  field_len    = QUICVariableInt::size(packet + token_length_filed_offset);
 
   return true;
 }
 
 bool
-QUICPacketLongHeader::length(size_t &length, uint8_t *field_len, const uint8_t *packet, size_t packet_len)
+QUICPacketLongHeader::length(size_t &length, uint8_t &length_field_len, size_t &length_field_offset, const uint8_t *packet,
+                             size_t packet_len)
 {
-  uint8_t dcil, scil;
-  QUICPacketLongHeader::dcil(dcil, packet, packet_len);
-  QUICPacketLongHeader::scil(scil, packet, packet_len);
+  uint8_t dcil;
+  if (!QUICPacketLongHeader::dcil(dcil, packet, packet_len)) {
+    return false;
+  }
+
+  uint8_t scil;
+  if (!QUICPacketLongHeader::scil(scil, packet, packet_len)) {
+    return false;
+  }
 
   // Token Length (i) + Token (*) (for INITIAL packet)
-  size_t token_length            = 0;
-  uint8_t token_length_field_len = 0;
-  if (!QUICPacketLongHeader::token_length(token_length, &token_length_field_len, packet, packet_len)) {
+  size_t token_length              = 0;
+  uint8_t token_length_field_len   = 0;
+  size_t token_length_field_offset = 0;
+  if (!QUICPacketLongHeader::token_length(token_length, token_length_field_len, token_length_field_offset, packet, packet_len)) {
     return false;
   }
 
   // Length (i)
-  size_t length_offset = LONG_HDR_OFFSET_CONNECTION_ID + dcil + scil + token_length_field_len + token_length;
-  if (length_offset >= packet_len) {
+  length_field_offset = LONG_HDR_OFFSET_CONNECTION_ID + dcil + 1 + scil + token_length_field_len + token_length;
+  if (length_field_offset >= packet_len) {
     return false;
   }
-  length = QUICIntUtil::read_QUICVariableInt(packet + length_offset);
-  if (field_len) {
-    *field_len = QUICVariableInt::size(packet + length_offset);
-  }
+
+  length_field_len = QUICVariableInt::size(packet + length_field_offset);
+  length           = QUICIntUtil::read_QUICVariableInt(packet + length_field_offset);
+
   return true;
 }
 
 bool
-QUICPacketLongHeader::packet_number_offset(uint8_t &pn_offset, const uint8_t *packet, size_t packet_len)
+QUICPacketLongHeader::packet_number_offset(size_t &pn_offset, const uint8_t *packet, size_t packet_len)
 {
-  QUICPacketType type;
-  QUICPacketLongHeader::type(type, packet, packet_len);
-
-  uint8_t dcil, scil;
-  size_t token_length;
-  uint8_t token_length_field_len;
   size_t length;
   uint8_t length_field_len;
-  if (!QUICPacketLongHeader::dcil(dcil, packet, packet_len) || !QUICPacketLongHeader::scil(scil, packet, packet_len) ||
-      !QUICPacketLongHeader::token_length(token_length, &token_length_field_len, packet, packet_len) ||
-      !QUICPacketLongHeader::length(length, &length_field_len, packet, packet_len)) {
+  size_t length_field_offset;
+
+  if (!QUICPacketLongHeader::length(length, length_field_len, length_field_offset, packet, packet_len)) {
     return false;
   }
-  pn_offset = 6 + dcil + scil + token_length_field_len + token_length + length_field_len;
+  pn_offset = length_field_offset + length_field_len;
+
+  if (pn_offset >= packet_len) {
+    return false;
+  }
+
+  return true;
+}
+
+bool
+QUICPacketLongHeader::packet_length(size_t &packet_len, const uint8_t *buf, size_t buf_len)
+{
+  size_t length;
+  uint8_t length_field_len;
+  size_t length_field_offset;
+
+  if (!QUICPacketLongHeader::length(length, length_field_len, length_field_offset, buf, buf_len)) {
+    return false;
+  }
+  packet_len = length + length_field_offset + length_field_len;
+
+  if (packet_len > buf_len) {
+    return false;
+  }
 
   return true;
 }
@@ -521,28 +543,49 @@
   QUICTypeUtil::write_QUICVersion(this->_version, buf + *len, &n);
   *len += n;
 
-  buf[*len] = this->_destination_cid == QUICConnectionId::ZERO() ? 0 : (this->_destination_cid.length() - 3) << 4;
-  buf[*len] += this->_source_cid == QUICConnectionId::ZERO() ? 0 : this->_source_cid.length() - 3;
-  *len += 1;
-
+  // DICD
   if (this->_destination_cid != QUICConnectionId::ZERO()) {
+    // Len
+    buf[*len] = this->_destination_cid.length();
+    *len += 1;
+
+    // ID
     QUICTypeUtil::write_QUICConnectionId(this->_destination_cid, buf + *len, &n);
     *len += n;
+  } else {
+    buf[*len] = 0;
+    *len += 1;
   }
+
+  // SCID
   if (this->_source_cid != QUICConnectionId::ZERO()) {
+    // Len
+    buf[*len] = this->_source_cid.length();
+    *len += 1;
+
+    // ID
     QUICTypeUtil::write_QUICConnectionId(this->_source_cid, buf + *len, &n);
     *len += n;
+  } else {
+    buf[*len] = 0;
+    *len += 1;
   }
 
   if (this->_type != QUICPacketType::VERSION_NEGOTIATION) {
     if (this->_type == QUICPacketType::RETRY) {
       // Original Destination Connection ID
       if (this->_original_dcid != QUICConnectionId::ZERO()) {
+        // Len
+        buf[*len] = this->_original_dcid.length();
+        *len += 1;
+
+        // ID
         QUICTypeUtil::write_QUICConnectionId(this->_original_dcid, buf + *len, &n);
         *len += n;
+      } else {
+        buf[*len] = 0;
+        *len += 1;
       }
-      // ODCIL
-      buf[0] |= this->_original_dcid.length() - 3;
     } else {
       if (this->_type == QUICPacketType::INITIAL) {
         // Token Length Field
@@ -590,8 +633,9 @@
 // QUICPacketShortHeader
 //
 
-QUICPacketShortHeader::QUICPacketShortHeader(const IpEndpoint from, ats_unique_buf buf, size_t len, QUICPacketNumber base)
-  : QUICPacketHeader(from, std::move(buf), len, base)
+QUICPacketShortHeader::QUICPacketShortHeader(const IpEndpoint from, const IpEndpoint to, ats_unique_buf buf, size_t len,
+                                             QUICPacketNumber base)
+  : QUICPacketHeader(from, to, std::move(buf), len, base)
 {
   QUICInvariants::dcid(this->_connection_id, this->_buf.get(), len);
 
@@ -718,7 +762,7 @@
 }
 
 bool
-QUICPacketShortHeader::packet_number_offset(uint8_t &pn_offset, const uint8_t *packet, size_t packet_len, int dcil)
+QUICPacketShortHeader::packet_number_offset(size_t &pn_offset, const uint8_t *packet, size_t packet_len, int dcil)
 {
   pn_offset = 1 + dcil;
   return true;
@@ -795,6 +839,12 @@
   return this->_header->from();
 }
 
+const IpEndpoint &
+QUICPacket::to() const
+{
+  return this->_header->to();
+}
+
 UDPConnection *
 QUICPacket::udp_con() const
 {
diff --git a/iocore/net/quic/QUICPacket.h b/iocore/net/quic/QUICPacket.h
index dd9c9bc..5fc834f 100644
--- a/iocore/net/quic/QUICPacket.h
+++ b/iocore/net/quic/QUICPacket.h
@@ -55,8 +55,8 @@
 class QUICPacketHeader
 {
 public:
-  QUICPacketHeader(const IpEndpoint from, ats_unique_buf buf, size_t len, QUICPacketNumber base)
-    : _from(from), _buf(std::move(buf)), _buf_len(len), _base_packet_number(base)
+  QUICPacketHeader(const IpEndpoint from, const IpEndpoint to, ats_unique_buf buf, size_t len, QUICPacketNumber base)
+    : _from(from), _to(to), _buf(std::move(buf)), _buf_len(len), _base_packet_number(base)
   {
   }
   ~QUICPacketHeader() {}
@@ -65,6 +65,7 @@
   virtual bool is_crypto_packet() const;
 
   const IpEndpoint &from() const;
+  const IpEndpoint &to() const;
 
   virtual QUICPacketType type() const = 0;
 
@@ -121,7 +122,8 @@
    *
    * This creates either a QUICPacketShortHeader or a QUICPacketLongHeader.
    */
-  static QUICPacketHeaderUPtr load(const IpEndpoint from, ats_unique_buf buf, size_t len, QUICPacketNumber base);
+  static QUICPacketHeaderUPtr load(const IpEndpoint from, const IpEndpoint to, ats_unique_buf buf, size_t len,
+                                   QUICPacketNumber base);
 
   /*
    * Build a QUICPacketHeader
@@ -185,6 +187,7 @@
   static constexpr size_t MAX_PACKET_HEADER_LEN = 256;
 
   const IpEndpoint _from = {};
+  const IpEndpoint _to   = {};
 
   // These two are used only if the instance was created with a buffer
   ats_unique_buf _buf = {nullptr};
@@ -208,7 +211,7 @@
 public:
   QUICPacketLongHeader() : QUICPacketHeader(){};
   virtual ~QUICPacketLongHeader(){};
-  QUICPacketLongHeader(const IpEndpoint from, ats_unique_buf buf, size_t len, QUICPacketNumber base);
+  QUICPacketLongHeader(const IpEndpoint from, const IpEndpoint to, ats_unique_buf buf, size_t len, QUICPacketNumber base);
   QUICPacketLongHeader(QUICPacketType type, QUICKeyPhase key_phase, const QUICConnectionId &destination_cid,
                        const QUICConnectionId &source_cid, QUICPacketNumber packet_number, QUICPacketNumber base_packet_number,
                        QUICVersion version, bool crypto, ats_unique_buf buf, size_t len,
@@ -235,18 +238,15 @@
 
   static bool type(QUICPacketType &type, const uint8_t *packet, size_t packet_len);
   static bool version(QUICVersion &version, const uint8_t *packet, size_t packet_len);
-  /**
-   * Unlike QUICInvariants::dcil(), this returns actual connection id length
-   */
   static bool dcil(uint8_t &dcil, const uint8_t *packet, size_t packet_len);
-  /**
-   * Unlike QUICInvariants::scil(), this returns actual connection id length
-   */
   static bool scil(uint8_t &scil, const uint8_t *packet, size_t packet_len);
-  static bool token_length(size_t &token_length, uint8_t *field_len, const uint8_t *packet, size_t packet_len);
-  static bool length(size_t &length, uint8_t *field_len, const uint8_t *packet, size_t packet_len);
+  static bool token_length(size_t &token_length, uint8_t &field_len, size_t &token_length_filed_offset, const uint8_t *packet,
+                           size_t packet_len);
+  static bool length(size_t &length, uint8_t &length_field_len, size_t &length_field_offset, const uint8_t *packet,
+                     size_t packet_len);
   static bool key_phase(QUICKeyPhase &key_phase, const uint8_t *packet, size_t packet_len);
-  static bool packet_number_offset(uint8_t &pn_offset, const uint8_t *packet, size_t packet_len);
+  static bool packet_number_offset(size_t &pn_offset, const uint8_t *packet, size_t packet_len);
+  static bool packet_length(size_t &length, const uint8_t *buf, size_t buf_len);
 
 private:
   QUICConnectionId _destination_cid = QUICConnectionId::ZERO();
@@ -264,7 +264,7 @@
 public:
   QUICPacketShortHeader() : QUICPacketHeader(){};
   virtual ~QUICPacketShortHeader(){};
-  QUICPacketShortHeader(const IpEndpoint from, ats_unique_buf buf, size_t len, QUICPacketNumber base);
+  QUICPacketShortHeader(const IpEndpoint from, const IpEndpoint to, ats_unique_buf buf, size_t len, QUICPacketNumber base);
   QUICPacketShortHeader(QUICPacketType type, QUICKeyPhase key_phase, QUICPacketNumber packet_number,
                         QUICPacketNumber base_packet_number, ats_unique_buf buf, size_t len);
   QUICPacketShortHeader(QUICPacketType type, QUICKeyPhase key_phase, const QUICConnectionId &connection_id,
@@ -286,7 +286,7 @@
   void store(uint8_t *buf, size_t *len) const override;
 
   static bool key_phase(QUICKeyPhase &key_phase, const uint8_t *packet, size_t packet_len);
-  static bool packet_number_offset(uint8_t &pn_offset, const uint8_t *packet, size_t packet_len, int dcil);
+  static bool packet_number_offset(size_t &pn_offset, const uint8_t *packet, size_t packet_len, int dcil);
 
 private:
   int _packet_number_len;
@@ -347,10 +347,11 @@
   QUICPacket(QUICPacketHeaderUPtr header, ats_unique_buf payload, size_t payload_len, bool ack_eliciting, bool probing,
              std::vector<QUICFrameInfo> &frames);
 
-  ~QUICPacket();
+  virtual ~QUICPacket();
 
   UDPConnection *udp_con() const;
-  const IpEndpoint &from() const;
+  virtual const IpEndpoint &from() const;
+  virtual const IpEndpoint &to() const;
   QUICPacketType type() const;
   QUICConnectionId destination_cid() const;
   QUICConnectionId source_cid() const;
diff --git a/iocore/net/quic/QUICPacketFactory.cc b/iocore/net/quic/QUICPacketFactory.cc
index 4c88c1b..ea82f44 100644
--- a/iocore/net/quic/QUICPacketFactory.cc
+++ b/iocore/net/quic/QUICPacketFactory.cc
@@ -62,14 +62,14 @@
 }
 
 QUICPacketUPtr
-QUICPacketFactory::create(UDPConnection *udp_con, IpEndpoint from, ats_unique_buf buf, size_t len,
+QUICPacketFactory::create(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to, ats_unique_buf buf, size_t len,
                           QUICPacketNumber base_packet_number, QUICPacketCreationResult &result)
 {
   size_t max_plain_txt_len = 2048;
   ats_unique_buf plain_txt = ats_unique_malloc(max_plain_txt_len);
   size_t plain_txt_len     = 0;
 
-  QUICPacketHeaderUPtr header = QUICPacketHeader::load(from, std::move(buf), len, base_packet_number);
+  QUICPacketHeaderUPtr header = QUICPacketHeader::load(from, to, std::move(buf), len, base_packet_number);
 
   QUICConnectionId dcid = header->destination_cid();
   QUICConnectionId scid = header->source_cid();
@@ -238,7 +238,7 @@
                                            QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len,
                                            bool retransmittable, bool probing, bool crypto)
 {
-  QUICPacketNumberSpace index = QUICTypeUtil::pn_space(QUICEncryptionLevel::INITIAL);
+  QUICPacketNumberSpace index = QUICTypeUtil::pn_space(QUICEncryptionLevel::HANDSHAKE);
   QUICPacketNumber pn         = this->_packet_number_generator[static_cast<int>(index)].next();
   QUICPacketHeaderUPtr header =
     QUICPacketHeader::build(QUICPacketType::HANDSHAKE, QUICKeyPhase::HANDSHAKE, destination_cid, source_cid, pn, base_packet_number,
@@ -251,7 +251,7 @@
                                           QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len,
                                           bool retransmittable, bool probing)
 {
-  QUICPacketNumberSpace index = QUICTypeUtil::pn_space(QUICEncryptionLevel::INITIAL);
+  QUICPacketNumberSpace index = QUICTypeUtil::pn_space(QUICEncryptionLevel::ZERO_RTT);
   QUICPacketNumber pn         = this->_packet_number_generator[static_cast<int>(index)].next();
   QUICPacketHeaderUPtr header =
     QUICPacketHeader::build(QUICPacketType::ZERO_RTT_PROTECTED, QUICKeyPhase::ZERO_RTT, destination_cid, source_cid, pn,
@@ -263,7 +263,7 @@
 QUICPacketFactory::create_protected_packet(QUICConnectionId connection_id, QUICPacketNumber base_packet_number,
                                            ats_unique_buf payload, size_t len, bool retransmittable, bool probing)
 {
-  QUICPacketNumberSpace index = QUICTypeUtil::pn_space(QUICEncryptionLevel::INITIAL);
+  QUICPacketNumberSpace index = QUICTypeUtil::pn_space(QUICEncryptionLevel::ONE_RTT);
   QUICPacketNumber pn         = this->_packet_number_generator[static_cast<int>(index)].next();
   // TODO Key phase should be picked up from QUICHandshakeProtocol, probably
   QUICPacketHeaderUPtr header = QUICPacketHeader::build(QUICPacketType::PROTECTED, QUICKeyPhase::PHASE_0, connection_id, pn,
@@ -274,19 +274,20 @@
 QUICPacketUPtr
 QUICPacketFactory::create_stateless_reset_packet(QUICConnectionId connection_id, QUICStatelessResetToken stateless_reset_token)
 {
+  constexpr uint8_t MIN_UNPREDICTABLE_FIELD_LEN = 5;
   std::random_device rnd;
 
   uint8_t random_packet_number = static_cast<uint8_t>(rnd() & 0xFF);
-  size_t payload_len           = static_cast<uint8_t>((rnd() & 0xFF) | 16); // Mimimum length has to be 16
-  ats_unique_buf payload       = ats_unique_malloc(payload_len + 16);
-  uint8_t *naked_payload       = payload.get();
+  size_t payload_len     = static_cast<uint8_t>((rnd() & 0xFF) | (MIN_UNPREDICTABLE_FIELD_LEN + QUICStatelessResetToken::LEN));
+  ats_unique_buf payload = ats_unique_malloc(payload_len);
+  uint8_t *naked_payload = payload.get();
 
   // Generate random octets
   for (int i = payload_len - 1; i >= 0; --i) {
     naked_payload[i] = static_cast<uint8_t>(rnd() & 0xFF);
   }
   // Copy stateless reset token into payload
-  memcpy(naked_payload + payload_len - 16, stateless_reset_token.buf(), 16);
+  memcpy(naked_payload + payload_len - QUICStatelessResetToken::LEN, stateless_reset_token.buf(), QUICStatelessResetToken::LEN);
 
   // KeyPhase won't be used
   QUICPacketHeaderUPtr header = QUICPacketHeader::build(QUICPacketType::STATELESS_RESET, QUICKeyPhase::INITIAL, connection_id,
diff --git a/iocore/net/quic/QUICPacketFactory.h b/iocore/net/quic/QUICPacketFactory.h
index a00639d..732ad35 100644
--- a/iocore/net/quic/QUICPacketFactory.h
+++ b/iocore/net/quic/QUICPacketFactory.h
@@ -52,7 +52,7 @@
 
   QUICPacketFactory(const QUICPacketProtectionKeyInfo &pp_key_info) : _pp_key_info(pp_key_info), _pp_protector(pp_key_info) {}
 
-  QUICPacketUPtr create(UDPConnection *udp_con, IpEndpoint from, ats_unique_buf buf, size_t len,
+  QUICPacketUPtr create(UDPConnection *udp_con, IpEndpoint from, IpEndpoint to, ats_unique_buf buf, size_t len,
                         QUICPacketNumber base_packet_number, QUICPacketCreationResult &result);
   QUICPacketUPtr create_initial_packet(QUICConnectionId destination_cid, QUICConnectionId source_cid,
                                        QUICPacketNumber base_packet_number, ats_unique_buf payload, size_t len, bool ack_eliciting,
diff --git a/iocore/net/quic/QUICPacketHeaderProtector.cc b/iocore/net/quic/QUICPacketHeaderProtector.cc
index 49a29ad..0dbfe8d 100644
--- a/iocore/net/quic/QUICPacketHeaderProtector.cc
+++ b/iocore/net/quic/QUICPacketHeaderProtector.cc
@@ -152,25 +152,16 @@
                                                int dcil) const
 {
   if (QUICInvariants::is_long_header(protected_packet)) {
-    uint8_t dcil;
-    uint8_t scil;
     size_t dummy;
     uint8_t length_len;
-    QUICPacketLongHeader::dcil(dcil, protected_packet, protected_packet_len);
-    QUICPacketLongHeader::scil(scil, protected_packet, protected_packet_len);
-    QUICPacketLongHeader::length(dummy, &length_len, protected_packet, protected_packet_len);
-    *sample_offset = 6 + dcil + scil + length_len + 4;
-
-    QUICPacketType type;
-    QUICPacketLongHeader::type(type, protected_packet, protected_packet_len);
-    if (type == QUICPacketType::INITIAL) {
-      size_t token_len;
-      uint8_t token_length_len;
-      QUICPacketLongHeader::token_length(token_len, &token_length_len, protected_packet, protected_packet_len);
-      *sample_offset += token_len + token_length_len;
+    size_t length_offset;
+    if (!QUICPacketLongHeader::length(dummy, length_len, length_offset, protected_packet, protected_packet_len)) {
+      return false;
     }
+
+    *sample_offset = length_offset + length_len + 4;
   } else {
-    *sample_offset = 1 + dcil + 4;
+    *sample_offset = QUICInvariants::SH_DCID_OFFSET + dcil + 4;
   }
 
   return static_cast<size_t>(*sample_offset + 16) <= protected_packet_len;
@@ -179,7 +170,7 @@
 bool
 QUICPacketHeaderProtector::_unprotect(uint8_t *protected_packet, size_t protected_packet_len, const uint8_t *mask) const
 {
-  uint8_t pn_offset;
+  size_t pn_offset;
 
   // Unprotect packet number
   if (QUICInvariants::is_long_header(protected_packet)) {
@@ -201,7 +192,7 @@
 bool
 QUICPacketHeaderProtector::_protect(uint8_t *protected_packet, size_t protected_packet_len, const uint8_t *mask, int dcil) const
 {
-  uint8_t pn_offset;
+  size_t pn_offset;
 
   uint8_t pn_length = QUICTypeUtil::read_QUICPacketNumberLen(protected_packet);
 
diff --git a/iocore/net/quic/QUICPacketProtectionKeyInfo.h b/iocore/net/quic/QUICPacketProtectionKeyInfo.h
index 32a38d4..71d077d 100644
--- a/iocore/net/quic/QUICPacketProtectionKeyInfo.h
+++ b/iocore/net/quic/QUICPacketProtectionKeyInfo.h
@@ -26,11 +26,43 @@
 #include "QUICTypes.h"
 #include "QUICKeyGenerator.h"
 
-class QUICPacketProtectionKeyInfo
+class QUICPacketProtectionKeyInfoProvider
+{
+public:
+  virtual ~QUICPacketProtectionKeyInfoProvider() {}
+  // Payload Protection (common)
+  virtual const EVP_CIPHER *get_cipher(QUICKeyPhase phase) const = 0;
+  virtual size_t get_tag_len(QUICKeyPhase phase) const           = 0;
+
+  // Payload Protection (encryption)
+  virtual bool is_encryption_key_available(QUICKeyPhase phase) const = 0;
+  virtual const uint8_t *encryption_key(QUICKeyPhase phase) const    = 0;
+  virtual size_t encryption_key_len(QUICKeyPhase phase) const        = 0;
+  virtual const uint8_t *encryption_iv(QUICKeyPhase phase) const     = 0;
+  virtual const size_t *encryption_iv_len(QUICKeyPhase phase) const  = 0;
+
+  // Payload Protection (decryption)
+  virtual bool is_decryption_key_available(QUICKeyPhase phase) const = 0;
+  virtual const uint8_t *decryption_key(QUICKeyPhase phase) const    = 0;
+  virtual size_t decryption_key_len(QUICKeyPhase phase) const        = 0;
+  virtual const uint8_t *decryption_iv(QUICKeyPhase phase) const     = 0;
+  virtual const size_t *decryption_iv_len(QUICKeyPhase phase) const  = 0;
+
+  // Header Protection
+  virtual const EVP_CIPHER *get_cipher_for_hp(QUICKeyPhase phase) const  = 0;
+  virtual const uint8_t *encryption_key_for_hp(QUICKeyPhase phase) const = 0;
+  virtual size_t encryption_key_for_hp_len(QUICKeyPhase phase) const     = 0;
+  virtual const uint8_t *decryption_key_for_hp(QUICKeyPhase phase) const = 0;
+  virtual size_t decryption_key_for_hp_len(QUICKeyPhase phase) const     = 0;
+};
+
+class QUICPacketProtectionKeyInfo : public QUICPacketProtectionKeyInfoProvider
 {
 public:
   enum class Context { SERVER, CLIENT };
 
+  virtual ~QUICPacketProtectionKeyInfo() {}
+
   // FIXME This should be passed to the constructor but NetVC cannot pass it because it has set_context too.
   void set_context(Context ctx);
 
@@ -38,58 +70,58 @@
 
   // Payload Protection (common)
 
-  virtual const EVP_CIPHER *get_cipher(QUICKeyPhase phase) const;
-  virtual size_t get_tag_len(QUICKeyPhase phase) const;
+  virtual const EVP_CIPHER *get_cipher(QUICKeyPhase phase) const override;
+  virtual size_t get_tag_len(QUICKeyPhase phase) const override;
   virtual void set_cipher_initial(const EVP_CIPHER *cipher);
   virtual void set_cipher(const EVP_CIPHER *cipher, size_t tag_len);
 
   // Payload Protection (encryption)
 
-  virtual bool is_encryption_key_available(QUICKeyPhase phase) const;
+  virtual bool is_encryption_key_available(QUICKeyPhase phase) const override;
   virtual void set_encryption_key_available(QUICKeyPhase phase);
 
-  virtual const uint8_t *encryption_key(QUICKeyPhase phase) const;
+  virtual const uint8_t *encryption_key(QUICKeyPhase phase) const override;
   virtual uint8_t *encryption_key(QUICKeyPhase phase);
 
-  virtual size_t encryption_key_len(QUICKeyPhase phase) const;
+  virtual size_t encryption_key_len(QUICKeyPhase phase) const override;
 
-  virtual const uint8_t *encryption_iv(QUICKeyPhase phase) const;
+  virtual const uint8_t *encryption_iv(QUICKeyPhase phase) const override;
   virtual uint8_t *encryption_iv(QUICKeyPhase phase);
 
-  virtual const size_t *encryption_iv_len(QUICKeyPhase phase) const;
+  virtual const size_t *encryption_iv_len(QUICKeyPhase phase) const override;
   virtual size_t *encryption_iv_len(QUICKeyPhase phase);
 
   // Payload Protection (decryption)
 
-  virtual bool is_decryption_key_available(QUICKeyPhase phase) const;
+  virtual bool is_decryption_key_available(QUICKeyPhase phase) const override;
   virtual void set_decryption_key_available(QUICKeyPhase phase);
 
-  virtual const uint8_t *decryption_key(QUICKeyPhase phase) const;
+  virtual const uint8_t *decryption_key(QUICKeyPhase phase) const override;
   virtual uint8_t *decryption_key(QUICKeyPhase phase);
 
-  virtual size_t decryption_key_len(QUICKeyPhase phase) const;
+  virtual size_t decryption_key_len(QUICKeyPhase phase) const override;
 
-  virtual const uint8_t *decryption_iv(QUICKeyPhase phase) const;
+  virtual const uint8_t *decryption_iv(QUICKeyPhase phase) const override;
   virtual uint8_t *decryption_iv(QUICKeyPhase phase);
 
-  virtual const size_t *decryption_iv_len(QUICKeyPhase phase) const;
+  virtual const size_t *decryption_iv_len(QUICKeyPhase phase) const override;
   virtual size_t *decryption_iv_len(QUICKeyPhase phase);
 
   // Header Protection
 
-  virtual const EVP_CIPHER *get_cipher_for_hp(QUICKeyPhase phase) const;
+  virtual const EVP_CIPHER *get_cipher_for_hp(QUICKeyPhase phase) const override;
   virtual void set_cipher_for_hp_initial(const EVP_CIPHER *cipher);
   virtual void set_cipher_for_hp(const EVP_CIPHER *cipher);
 
-  virtual const uint8_t *encryption_key_for_hp(QUICKeyPhase phase) const;
+  virtual const uint8_t *encryption_key_for_hp(QUICKeyPhase phase) const override;
   virtual uint8_t *encryption_key_for_hp(QUICKeyPhase phase);
 
-  virtual size_t encryption_key_for_hp_len(QUICKeyPhase phase) const;
+  virtual size_t encryption_key_for_hp_len(QUICKeyPhase phase) const override;
 
-  virtual const uint8_t *decryption_key_for_hp(QUICKeyPhase phase) const;
+  virtual const uint8_t *decryption_key_for_hp(QUICKeyPhase phase) const override;
   virtual uint8_t *decryption_key_for_hp(QUICKeyPhase phase);
 
-  virtual size_t decryption_key_for_hp_len(QUICKeyPhase phase) const;
+  virtual size_t decryption_key_for_hp_len(QUICKeyPhase phase) const override;
 
 private:
   Context _ctx = Context::SERVER;
diff --git a/iocore/net/quic/QUICPacketReceiveQueue.cc b/iocore/net/quic/QUICPacketReceiveQueue.cc
index 405c295..c90385b 100644
--- a/iocore/net/quic/QUICPacketReceiveQueue.cc
+++ b/iocore/net/quic/QUICPacketReceiveQueue.cc
@@ -26,42 +26,12 @@
 
 #include "QUICIntUtil.h"
 
-// FIXME: workaround for coalescing packets
-static constexpr int LONG_HDR_OFFSET_CONNECTION_ID = 6;
-
 static bool
 is_vn(QUICVersion v)
 {
   return v == 0x0;
 }
 
-static bool
-long_hdr_pkt_len(size_t &pkt_len, uint8_t *buf, size_t len)
-{
-  uint8_t dcil, scil;
-  QUICPacketLongHeader::dcil(dcil, buf, len);
-  QUICPacketLongHeader::scil(scil, buf, len);
-
-  size_t offset = LONG_HDR_OFFSET_CONNECTION_ID + dcil + scil;
-
-  // token_length and token_length_field_len should be 0 except INITIAL packet
-  size_t token_length            = 0;
-  uint8_t token_length_field_len = 0;
-  if (!QUICPacketLongHeader::token_length(token_length, &token_length_field_len, buf, len)) {
-    return false;
-  }
-
-  size_t length            = 0;
-  uint8_t length_field_len = 0;
-  if (!QUICPacketLongHeader::length(length, &length_field_len, buf, len)) {
-    return false;
-  }
-
-  pkt_len = offset + token_length + token_length_field_len + length_field_len + length;
-
-  return true;
-}
-
 QUICPacketReceiveQueue::QUICPacketReceiveQueue(QUICPacketFactory &packet_factory, QUICPacketHeaderProtector &ph_protector)
   : _packet_factory(packet_factory), _ph_protector(ph_protector)
 {
@@ -91,6 +61,7 @@
     // Create a QUIC packet
     this->_udp_con     = udp_packet->getConnection();
     this->_from        = udp_packet->from;
+    this->_to          = udp_packet->to;
     this->_payload_len = udp_packet->getPktLength();
     this->_payload     = ats_unique_malloc(this->_payload_len);
     IOBufferBlock *b   = udp_packet->getIOBlockChain();
@@ -124,7 +95,7 @@
         if (type == QUICPacketType::RETRY) {
           pkt_len = remaining_len;
         } else {
-          if (!long_hdr_pkt_len(pkt_len, this->_payload.get() + this->_offset, remaining_len)) {
+          if (!QUICPacketLongHeader::packet_length(pkt_len, this->_payload.get() + this->_offset, remaining_len)) {
             this->_payload.release();
             this->_payload     = nullptr;
             this->_payload_len = 0;
@@ -177,7 +148,7 @@
   }
 
   if (this->_ph_protector.unprotect(pkt.get(), pkt_len)) {
-    quic_packet = this->_packet_factory.create(this->_udp_con, this->_from, std::move(pkt), pkt_len,
+    quic_packet = this->_packet_factory.create(this->_udp_con, this->_from, this->_to, std::move(pkt), pkt_len,
                                                this->_largest_received_packet_number, result);
   } else {
     // ZERO_RTT might be rejected
diff --git a/iocore/net/quic/QUICPacketReceiveQueue.h b/iocore/net/quic/QUICPacketReceiveQueue.h
index 5027df3..e4e3e29 100644
--- a/iocore/net/quic/QUICPacketReceiveQueue.h
+++ b/iocore/net/quic/QUICPacketReceiveQueue.h
@@ -51,4 +51,5 @@
   size_t _offset          = 0;
   UDPConnection *_udp_con;
   IpEndpoint _from;
+  IpEndpoint _to;
 };
diff --git a/iocore/net/quic/QUICPadder.cc b/iocore/net/quic/QUICPadder.cc
new file mode 100644
index 0000000..0ae913e
--- /dev/null
+++ b/iocore/net/quic/QUICPadder.cc
@@ -0,0 +1,109 @@
+/** @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 "QUICIntUtil.h"
+#include "QUICPadder.h"
+
+static constexpr uint32_t MINIMUM_INITIAL_PACKET_SIZE = 1200;
+static constexpr uint32_t MIN_PKT_PAYLOAD_LEN         = 3; ///< Minimum payload length for sampling for header protection
+
+void
+QUICPadder::request(QUICEncryptionLevel level)
+{
+  SCOPED_MUTEX_LOCK(lock, this->_mutex, this_ethread());
+  ++this->_need_to_fire[static_cast<int>(level)];
+}
+
+void
+QUICPadder::cancel(QUICEncryptionLevel level)
+{
+  SCOPED_MUTEX_LOCK(lock, this->_mutex, this_ethread());
+  this->_need_to_fire[static_cast<int>(level)] = 0;
+}
+
+uint64_t
+QUICPadder::count(QUICEncryptionLevel level)
+{
+  SCOPED_MUTEX_LOCK(lock, this->_mutex, this_ethread());
+  return this->_need_to_fire[static_cast<int>(level)];
+}
+
+bool
+QUICPadder::_will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting)
+{
+  SCOPED_MUTEX_LOCK(lock, this->_mutex, this_ethread());
+  // no extre padding packet
+  if (current_packet_size == 0 && this->_need_to_fire[static_cast<int>(level)] == 0) {
+    return false;
+  }
+
+  // every packets need to be padded
+  return true;
+}
+
+QUICFrame *
+QUICPadder::_generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
+                            size_t current_packet_size)
+{
+  SCOPED_MUTEX_LOCK(lock, this->_mutex, this_ethread());
+  QUICFrame *frame = nullptr;
+
+  uint64_t min_size = 0;
+  if (level == QUICEncryptionLevel::INITIAL && this->_context == NET_VCONNECTION_OUT) {
+    min_size = this->_minimum_quic_packet_size();
+    if (this->_av_token_len && min_size > (QUICVariableInt::size(this->_av_token_len) + this->_av_token_len)) {
+      min_size -= (QUICVariableInt::size(this->_av_token_len) + this->_av_token_len);
+    }
+  } else {
+    min_size = MIN_PKT_PAYLOAD_LEN;
+  }
+
+  if (min_size > current_packet_size) { // ignore if we don't need to pad.
+    frame = QUICFrameFactory::create_padding_frame(
+      buf, std::min(min_size - current_packet_size, static_cast<uint64_t>(maximum_frame_size)));
+  }
+
+  this->_need_to_fire[static_cast<int>(level)] = 0;
+  return frame;
+}
+
+uint32_t
+QUICPadder::_minimum_quic_packet_size()
+{
+  SCOPED_MUTEX_LOCK(lock, this->_mutex, this_ethread());
+  if (this->_context == NET_VCONNECTION_OUT) {
+    // FIXME Only the first packet need to be 1200 bytes at least
+    return MINIMUM_INITIAL_PACKET_SIZE;
+  } else {
+    // FIXME This size should be configurable and should have some randomness
+    // This is just for providing protection against packet analysis for protected packets
+    return 32 + (this->_rnd() & 0x3f); // 32 to 96
+  }
+}
+
+void
+QUICPadder::set_av_token_len(uint32_t len)
+{
+  SCOPED_MUTEX_LOCK(lock, this->_mutex, this_ethread());
+  this->_av_token_len = len;
+}
diff --git a/iocore/net/quic/QUICPadder.h b/iocore/net/quic/QUICPadder.h
new file mode 100644
index 0000000..bba8dfc
--- /dev/null
+++ b/iocore/net/quic/QUICPadder.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 "QUICTypes.h"
+#include "QUICFrameHandler.h"
+#include "QUICFrameGenerator.h"
+
+#include "I_Lock.h"
+
+class QUICPadder : public QUICFrameOnceGenerator
+{
+public:
+  QUICPadder(NetVConnectionContext_t context) : _mutex(new_ProxyMutex()), _context(context) {}
+
+  void request(QUICEncryptionLevel level);
+  void cancel(QUICEncryptionLevel level);
+  uint64_t count(QUICEncryptionLevel level);
+  void set_av_token_len(uint32_t len);
+
+private:
+  uint32_t _minimum_quic_packet_size();
+
+  // QUICFrameGenerator
+  bool _will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting) override;
+  QUICFrame *_generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
+                             size_t current_packet_size) override;
+
+  Ptr<ProxyMutex> _mutex;
+  std::random_device _rnd;
+
+  uint32_t _av_token_len = 0;
+  // Initial, 0/1-RTT, and Handshake
+  uint64_t _need_to_fire[4]        = {0};
+  NetVConnectionContext_t _context = NET_VCONNECTION_OUT;
+};
diff --git a/iocore/net/quic/QUICPathManager.cc b/iocore/net/quic/QUICPathManager.cc
new file mode 100644
index 0000000..613efec
--- /dev/null
+++ b/iocore/net/quic/QUICPathManager.cc
@@ -0,0 +1,84 @@
+/** @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 "QUICPathManager.h"
+#include "QUICPathValidator.h"
+#include "QUICConnection.h"
+
+#define QUICDebug(fmt, ...) Debug("quic_path", "[%s] " fmt, this->_cinfo.cids().data(), ##__VA_ARGS__)
+
+void
+QUICPathManager::open_new_path(const QUICPath &path, ink_hrtime timeout_in)
+{
+  if (this->_verify_timeout_at == 0) {
+    // Overwrite _previous_path only if _current_path is verified
+    // _previous_path should always have a verified path if available
+    this->_previous_path = this->_current_path;
+  }
+  this->_current_path = path;
+  this->_path_validator.validate(path);
+  this->_verify_timeout_at = Thread::get_hrtime() + timeout_in;
+}
+
+void
+QUICPathManager::set_trusted_path(const QUICPath &path)
+{
+  this->_current_path  = path;
+  this->_previous_path = path;
+}
+
+void
+QUICPathManager::_check_verify_timeout()
+{
+  if (this->_verify_timeout_at != 0) {
+    if (this->_path_validator.is_validated(this->_current_path)) {
+      // Address validation succeeded
+      this->_verify_timeout_at = 0;
+      this->_previous_path     = {{}, {}};
+    } else if (this->_verify_timeout_at < Thread::get_hrtime()) {
+      // Address validation failed
+      QUICDebug("Switching back to the previous path");
+      this->_current_path      = this->_previous_path;
+      this->_verify_timeout_at = 0;
+      this->_previous_path     = {{}, {}};
+    }
+  }
+}
+
+const QUICPath &
+QUICPathManager::get_current_path()
+{
+  this->_check_verify_timeout();
+  return this->_current_path;
+}
+
+const QUICPath &
+QUICPathManager::get_verified_path()
+{
+  this->_check_verify_timeout();
+  if (this->_verify_timeout_at != 0) {
+    return this->_previous_path;
+  } else {
+    return this->_current_path;
+  }
+}
diff --git a/iocore/net/quic/QUICPathManager.h b/iocore/net/quic/QUICPathManager.h
new file mode 100644
index 0000000..9f9eb37
--- /dev/null
+++ b/iocore/net/quic/QUICPathManager.h
@@ -0,0 +1,53 @@
+/** @file
+ *
+ *  A brief file description
+ *
+ *  @section license License
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#pragma once
+
+#include "QUICTypes.h"
+
+class QUICConnectionInfoProvider;
+class QUICPathValidator;
+
+class QUICPathManager
+{
+public:
+  QUICPathManager(const QUICConnectionInfoProvider &info, QUICPathValidator &path_validator)
+    : _cinfo(info), _path_validator(path_validator)
+  {
+  }
+
+  const QUICPath &get_current_path();
+  const QUICPath &get_verified_path();
+  void open_new_path(const QUICPath &path, ink_hrtime timeout_in);
+  void set_trusted_path(const QUICPath &path);
+
+private:
+  const QUICConnectionInfoProvider &_cinfo;
+  QUICPathValidator &_path_validator;
+
+  ink_hrtime _verify_timeout_at = 0;
+  QUICPath _current_path        = {{}, {}};
+  QUICPath _previous_path       = {{}, {}};
+
+  void _check_verify_timeout();
+};
diff --git a/iocore/net/quic/QUICPathValidator.cc b/iocore/net/quic/QUICPathValidator.cc
index 40a2390..21a97c9 100644
--- a/iocore/net/quic/QUICPathValidator.cc
+++ b/iocore/net/quic/QUICPathValidator.cc
@@ -23,32 +23,85 @@
 
 #include <chrono>
 #include "QUICPathValidator.h"
+#include "QUICPacket.h"
+
+#define QUICDebug(fmt, ...) Debug("quic_path", "[%s] " fmt, this->_cinfo.cids().data(), ##__VA_ARGS__)
 
 bool
-QUICPathValidator::is_validating()
+QUICPathValidator::ValidationJob::has_more_challenges() const
+{
+  return this->_has_outgoing_challenge;
+}
+
+const uint8_t *
+QUICPathValidator::ValidationJob::get_next_challenge() const
+{
+  if (this->_has_outgoing_challenge) {
+    return this->_outgoing_challenge + ((this->_has_outgoing_challenge - 1) * 8);
+  } else {
+    return nullptr;
+  }
+}
+
+void
+QUICPathValidator::ValidationJob::consume_challenge()
+{
+  --(this->_has_outgoing_challenge);
+}
+
+bool
+QUICPathValidator::is_validating(const QUICPath &path)
+{
+  if (auto j = this->_jobs.find(path); j != this->_jobs.end()) {
+    return j->second.is_validating();
+  } else {
+    return false;
+  }
+}
+
+bool
+QUICPathValidator::is_validated(const QUICPath &path)
+{
+  if (auto j = this->_jobs.find(path); j != this->_jobs.end()) {
+    return j->second.is_validated();
+  } else {
+    return false;
+  }
+}
+
+void
+QUICPathValidator::validate(const QUICPath &path)
+{
+  if (this->_jobs.find(path) != this->_jobs.end()) {
+    // Do nothing
+  } else {
+    auto result = this->_jobs.emplace(std::piecewise_construct, std::forward_as_tuple(path), std::forward_as_tuple());
+    result.first->second.start();
+    // QUICDebug("Validating a path between %s and %s", path.local_ep(), path.remote_ep());
+  }
+}
+
+bool
+QUICPathValidator::ValidationJob::is_validating()
 {
   return this->_state == ValidationState::VALIDATING;
 }
 
 bool
-QUICPathValidator::is_validated()
+QUICPathValidator::ValidationJob::is_validated()
 {
   return this->_state == ValidationState::VALIDATED;
 }
 
 void
-QUICPathValidator::validate()
+QUICPathValidator::ValidationJob::start()
 {
-  if (this->_state == ValidationState::VALIDATING) {
-    // Do nothing
-  } else {
-    this->_state = ValidationState::VALIDATING;
-    this->_generate_challenge();
-  }
+  this->_state = ValidationState::VALIDATING;
+  this->_generate_challenge();
 }
 
 void
-QUICPathValidator::_generate_challenge()
+QUICPathValidator::ValidationJob::_generate_challenge()
 {
   size_t seed =
     std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
@@ -60,29 +113,17 @@
   this->_has_outgoing_challenge = 3;
 }
 
-void
-QUICPathValidator::_generate_response(const QUICPathChallengeFrame &frame)
+bool
+QUICPathValidator::ValidationJob::validate_response(const uint8_t *data)
 {
-  memcpy(this->_incoming_challenge, frame.data(), QUICPathChallengeFrame::DATA_LEN);
-  this->_has_outgoing_response = true;
-}
-
-QUICConnectionErrorUPtr
-QUICPathValidator::_validate_response(const QUICPathResponseFrame &frame)
-{
-  QUICConnectionErrorUPtr error = std::make_unique<QUICConnectionError>(QUICTransErrorCode::PROTOCOL_VIOLATION);
-
   for (int i = 0; i < 3; ++i) {
-    if (memcmp(this->_outgoing_challenge + (QUICPathChallengeFrame::DATA_LEN * i), frame.data(),
-               QUICPathChallengeFrame::DATA_LEN) == 0) {
+    if (memcmp(this->_outgoing_challenge + (QUICPathChallengeFrame::DATA_LEN * i), data, QUICPathChallengeFrame::DATA_LEN) == 0) {
       this->_state                  = ValidationState::VALIDATED;
       this->_has_outgoing_challenge = 0;
-      error                         = nullptr;
-      break;
+      return true;
     }
   }
-
-  return error;
+  return false;
 }
 
 //
@@ -101,10 +142,21 @@
 
   switch (frame.type()) {
   case QUICFrameType::PATH_CHALLENGE:
-    this->_generate_response(static_cast<const QUICPathChallengeFrame &>(frame));
+    this->_incoming_challenges.emplace(this->_incoming_challenges.begin(),
+                                       static_cast<const QUICPathChallengeFrame &>(frame).data());
     break;
   case QUICFrameType::PATH_RESPONSE:
-    error = this->_validate_response(static_cast<const QUICPathResponseFrame &>(frame));
+    if (auto item = this->_jobs.find({frame.packet()->to(), frame.packet()->from()}); item != this->_jobs.end()) {
+      if (item->second.validate_response(static_cast<const QUICPathResponseFrame &>(frame).data())) {
+        QUICDebug("validation succeeded");
+        this->_on_validation_callback(true);
+      } else {
+        QUICDebug("validation failed");
+        this->_on_validation_callback(false);
+      }
+    } else {
+      error = std::make_unique<QUICConnectionError>(QUICTransErrorCode::PROTOCOL_VIOLATION);
+    }
     break;
   default:
     ink_assert(!"Can't happen");
@@ -117,17 +169,30 @@
 // QUICFrameGenerator
 //
 bool
-QUICPathValidator::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp)
+QUICPathValidator::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;
   }
 
-  if (this->_last_sent_at == timestamp) {
+  if (this->_latest_seq_num == seq_num) {
     return false;
   }
 
-  return (this->_has_outgoing_challenge || this->_has_outgoing_response);
+  // Check challenges
+  for (auto &&item : this->_jobs) {
+    auto &j = item.second;
+    if (!j.is_validating() && !j.is_validated()) {
+      j.start();
+      return true;
+    }
+    if (j.has_more_challenges()) {
+      return true;
+    }
+  }
+
+  // Check responses
+  return !this->_incoming_challenges.empty();
 }
 
 /**
@@ -135,7 +200,7 @@
  */
 QUICFrame *
 QUICPathValidator::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /* connection_credit */,
-                                  uint16_t maximum_frame_size, ink_hrtime timestamp)
+                                  uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num)
 {
   QUICFrame *frame = nullptr;
 
@@ -143,27 +208,31 @@
     return frame;
   }
 
-  if (this->_has_outgoing_response) {
-    frame = QUICFrameFactory::create_path_response_frame(buf, this->_incoming_challenge);
+  if (!this->_incoming_challenges.empty()) {
+    frame = QUICFrameFactory::create_path_response_frame(buf, this->_incoming_challenges.back());
     if (frame && frame->size() > maximum_frame_size) {
       // Cancel generating frame
       frame = nullptr;
     } else {
-      this->_has_outgoing_response = false;
+      this->_incoming_challenges.pop_back();
     }
-  } else if (this->_has_outgoing_challenge) {
-    frame = QUICFrameFactory::create_path_challenge_frame(
-      buf, this->_outgoing_challenge + (QUICPathChallengeFrame::DATA_LEN * (this->_has_outgoing_challenge - 1)));
-    if (frame && frame->size() > maximum_frame_size) {
-      // Cancel generating frame
-      frame = nullptr;
-    } else {
-      --this->_has_outgoing_challenge;
-      ink_assert(this->_has_outgoing_challenge >= 0);
+  } else {
+    for (auto &&item : this->_jobs) {
+      auto &j = item.second;
+      if (j.has_more_challenges()) {
+        frame = QUICFrameFactory::create_path_challenge_frame(buf, j.get_next_challenge());
+        if (frame && frame->size() > maximum_frame_size) {
+          // Cancel generating frame
+          frame = nullptr;
+        } else {
+          j.consume_challenge();
+        }
+        break;
+      }
     }
   }
 
-  this->_last_sent_at = timestamp;
+  this->_latest_seq_num = seq_num;
 
   return frame;
 }
diff --git a/iocore/net/quic/QUICPathValidator.h b/iocore/net/quic/QUICPathValidator.h
index 47a2975..c575f5a 100644
--- a/iocore/net/quic/QUICPathValidator.h
+++ b/iocore/net/quic/QUICPathValidator.h
@@ -24,26 +24,31 @@
 #pragma once
 
 #include <vector>
+#include <unordered_map>
 #include "QUICTypes.h"
 #include "QUICFrameHandler.h"
 #include "QUICFrameGenerator.h"
+#include "QUICConnection.h"
 
 class QUICPathValidator : public QUICFrameHandler, public QUICFrameGenerator
 {
 public:
-  QUICPathValidator() {}
-  bool is_validating();
-  bool is_validated();
-  void validate();
+  QUICPathValidator(const QUICConnectionInfoProvider &info, std::function<void(bool)> callback)
+    : _cinfo(info), _on_validation_callback(callback)
+  {
+  }
+  bool is_validating(const QUICPath &path);
+  bool is_validated(const QUICPath &path);
+  void validate(const QUICPath &path);
 
   // QUICFrameHandler
   std::vector<QUICFrameType> interests() override;
   QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override;
 
   // QUICFrameGeneratro
-  bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override;
+  bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) override;
   QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            ink_hrtime timestamp) override;
+                            size_t current_packet_size, uint32_t seq_num) override;
 
 private:
   enum class ValidationState : int {
@@ -51,14 +56,33 @@
     VALIDATING,
     VALIDATED,
   };
-  ValidationState _state      = ValidationState::NOT_VALIDATED;
-  int _has_outgoing_challenge = 0;
-  bool _has_outgoing_response = false;
-  ink_hrtime _last_sent_at    = 0;
-  uint8_t _incoming_challenge[QUICPathChallengeFrame::DATA_LEN];
-  uint8_t _outgoing_challenge[QUICPathChallengeFrame::DATA_LEN * 3];
 
-  void _generate_challenge();
-  void _generate_response(const QUICPathChallengeFrame &frame);
-  QUICConnectionErrorUPtr _validate_response(const QUICPathResponseFrame &frame);
+  class ValidationJob
+  {
+  public:
+    ValidationJob(){};
+    ~ValidationJob(){};
+    bool is_validating();
+    bool is_validated();
+    void start();
+    bool validate_response(const uint8_t *data);
+    bool has_more_challenges() const;
+    const uint8_t *get_next_challenge() const;
+    void consume_challenge();
+
+  private:
+    ValidationState _state = ValidationState::NOT_VALIDATED;
+    uint8_t _outgoing_challenge[QUICPathChallengeFrame::DATA_LEN * 3];
+    int _has_outgoing_challenge = 0;
+
+    void _generate_challenge();
+  };
+
+  const QUICConnectionInfoProvider &_cinfo;
+  std::unordered_map<QUICPath, ValidationJob, QUICPathHasher> _jobs;
+
+  std::function<void(bool)> _on_validation_callback;
+
+  uint32_t _latest_seq_num = 0;
+  std::vector<QUICPathValidationData> _incoming_challenges;
 };
diff --git a/iocore/net/quic/QUICPinger.cc b/iocore/net/quic/QUICPinger.cc
index e6f69c6..84454c8 100644
--- a/iocore/net/quic/QUICPinger.cc
+++ b/iocore/net/quic/QUICPinger.cc
@@ -24,54 +24,72 @@
 #include "QUICPinger.h"
 
 void
-QUICPinger::request(QUICEncryptionLevel level)
+QUICPinger::request()
 {
-  if (!this->_is_level_matched(level)) {
-    return;
-  }
-  ++this->_need_to_fire[static_cast<int>(level)];
+  SCOPED_MUTEX_LOCK(lock, this->_mutex, this_ethread());
+  ++this->_need_to_fire;
 }
 
 void
-QUICPinger::cancel(QUICEncryptionLevel level)
+QUICPinger::cancel()
 {
-  if (!this->_is_level_matched(level)) {
-    return;
-  }
-
-  if (this->_need_to_fire[static_cast<int>(level)] > 0) {
-    --this->_need_to_fire[static_cast<int>(level)];
+  SCOPED_MUTEX_LOCK(lock, this->_mutex, this_ethread());
+  if (this->_need_to_fire > 0) {
+    --this->_need_to_fire;
   }
 }
 
 bool
-QUICPinger::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp)
+QUICPinger::_will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting)
 {
-  if (!this->_is_level_matched(level)) {
+  SCOPED_MUTEX_LOCK(lock, this->_mutex, this_ethread());
+
+  if (level != QUICEncryptionLevel::ONE_RTT) {
     return false;
   }
 
-  return this->_need_to_fire[static_cast<int>(QUICTypeUtil::pn_space(level))] > 0;
+  // PING Frame is meaningless for ack_eliciting packet. Cancel it.
+  if (ack_eliciting) {
+    this->_ack_eliciting_packet_out = true;
+    this->cancel();
+    return false;
+  }
+
+  if (this->_ack_eliciting_packet_out == false && !ack_eliciting && current_packet_size > 0 && this->_need_to_fire == 0) {
+    // force to send an PING Frame
+    this->request();
+  }
+
+  // only update `_ack_eliciting_packet_out` when we has something to send.
+  if (current_packet_size) {
+    this->_ack_eliciting_packet_out = ack_eliciting;
+  }
+  return this->_need_to_fire > 0;
 }
 
 /**
  * @param connection_credit This is not used. Because PING frame is not flow-controlled
  */
 QUICFrame *
-QUICPinger::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /* connection_credit */, uint16_t maximum_frame_size,
-                           ink_hrtime timestamp)
+QUICPinger::_generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /* connection_credit */, uint16_t maximum_frame_size,
+                            size_t current_packet_size)
 {
+  SCOPED_MUTEX_LOCK(lock, this->_mutex, this_ethread());
   QUICFrame *frame = nullptr;
 
-  if (!this->_is_level_matched(level)) {
-    return frame;
-  }
-
-  if (this->_need_to_fire[static_cast<int>(level)] > 0 && maximum_frame_size > 0) {
+  if (level == QUICEncryptionLevel::ONE_RTT && this->_need_to_fire > 0 && maximum_frame_size > 0) {
     // don't care ping frame lost or acked
-    frame                                        = QUICFrameFactory::create_ping_frame(buf, 0, nullptr);
-    this->_need_to_fire[static_cast<int>(level)] = 0;
+    frame = QUICFrameFactory::create_ping_frame(buf, 0, nullptr);
+    --this->_need_to_fire;
+    this->_ack_eliciting_packet_out = true;
   }
 
   return frame;
 }
+
+uint64_t
+QUICPinger::count()
+{
+  SCOPED_MUTEX_LOCK(lock, this->_mutex, this_ethread());
+  return this->_need_to_fire;
+}
diff --git a/iocore/net/quic/QUICPinger.h b/iocore/net/quic/QUICPinger.h
index efa59a3..483cfb2 100644
--- a/iocore/net/quic/QUICPinger.h
+++ b/iocore/net/quic/QUICPinger.h
@@ -28,20 +28,25 @@
 #include "QUICFrameHandler.h"
 #include "QUICFrameGenerator.h"
 
-class QUICPinger : public QUICFrameGenerator
+#include "I_Lock.h"
+
+class QUICPinger : public QUICFrameOnceGenerator
 {
 public:
-  QUICPinger() {}
+  QUICPinger() : _mutex(new_ProxyMutex()) {}
 
-  void request(QUICEncryptionLevel level);
-  void cancel(QUICEncryptionLevel level);
-
-  // QUICFrameGenerator
-  bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override;
-  QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            ink_hrtime timestamp) override;
+  void request();
+  void cancel();
+  uint64_t count();
 
 private:
-  // Initial, 0/1-RTT, and Handshake
-  uint64_t _need_to_fire[4] = {0};
+  // QUICFrameGenerator
+  bool _will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting) override;
+  QUICFrame *_generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
+                             size_t current_packet_size) override;
+
+  bool _ack_eliciting_packet_out = false;
+
+  Ptr<ProxyMutex> _mutex;
+  uint64_t _need_to_fire = 0;
 };
diff --git a/iocore/net/quic/QUICStreamManager.cc b/iocore/net/quic/QUICStreamManager.cc
index 91edd1b..de4ffb9 100644
--- a/iocore/net/quic/QUICStreamManager.cc
+++ b/iocore/net/quic/QUICStreamManager.cc
@@ -29,8 +29,6 @@
 static constexpr char tag[]                     = "quic_stream_manager";
 static constexpr QUICStreamId QUIC_STREAM_TYPES = 4;
 
-ClassAllocator<QUICStreamManager> quicStreamManagerAllocator("quicStreamManagerAllocator");
-
 QUICStreamManager::QUICStreamManager(QUICConnectionInfoProvider *info, QUICRTTProvider *rtt_provider, QUICApplicationMap *app_map)
   : _stream_factory(rtt_provider, info), _info(info), _app_map(app_map)
 {
@@ -92,7 +90,7 @@
   QUICConnectionErrorUPtr error    = nullptr;
   QUICStreamVConnection *stream_vc = this->_find_or_create_stream_vc(stream_id);
   if (!stream_vc) {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_ID_ERROR);
+    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
   }
 
   QUICApplication *application = this->_app_map->get(stream_id);
@@ -176,7 +174,7 @@
   if (stream) {
     return stream->recv(frame);
   } else {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_ID_ERROR);
+    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
   }
 }
 
@@ -187,7 +185,7 @@
   if (stream) {
     return stream->recv(frame);
   } else {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_ID_ERROR);
+    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
   }
 }
 
@@ -196,7 +194,7 @@
 {
   QUICStreamVConnection *stream = this->_find_or_create_stream_vc(frame.stream_id());
   if (!stream) {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_ID_ERROR);
+    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
   }
 
   QUICApplication *application = this->_app_map->get(frame.stream_id());
@@ -215,7 +213,7 @@
   if (stream) {
     return stream->recv(frame);
   } else {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_ID_ERROR);
+    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
   }
 }
 
@@ -226,7 +224,7 @@
   if (stream) {
     return stream->recv(frame);
   } else {
-    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_ID_ERROR);
+    return std::make_unique<QUICConnectionError>(QUICTransErrorCode::STREAM_LIMIT_ERROR);
   }
 }
 
@@ -407,7 +405,7 @@
 }
 
 bool
-QUICStreamManager::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp)
+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;
@@ -419,7 +417,7 @@
   }
 
   for (QUICStreamVConnection *s = this->stream_list.head; s; s = s->link.next) {
-    if (s->will_generate_frame(level, timestamp)) {
+    if (s->will_generate_frame(level, current_packet_size, ack_eliciting, seq_num)) {
       return true;
     }
   }
@@ -429,7 +427,7 @@
 
 QUICFrame *
 QUICStreamManager::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                                  ink_hrtime timestamp)
+                                  size_t current_packet_size, uint32_t seq_num)
 {
   QUICFrame *frame = nullptr;
 
@@ -444,7 +442,7 @@
 
   // FIXME We should pick a stream based on priority
   for (QUICStreamVConnection *s = this->stream_list.head; s; s = s->link.next) {
-    frame = s->generate_frame(buf, level, connection_credit, maximum_frame_size, timestamp);
+    frame = s->generate_frame(buf, level, connection_credit, maximum_frame_size, current_packet_size, seq_num);
     if (frame) {
       break;
     }
diff --git a/iocore/net/quic/QUICStreamManager.h b/iocore/net/quic/QUICStreamManager.h
index fbdc49d..fb38ecc 100644
--- a/iocore/net/quic/QUICStreamManager.h
+++ b/iocore/net/quic/QUICStreamManager.h
@@ -37,7 +37,6 @@
 class QUICStreamManager : public QUICFrameHandler, public QUICFrameGenerator
 {
 public:
-  QUICStreamManager() : _stream_factory(nullptr, nullptr){};
   QUICStreamManager(QUICConnectionInfoProvider *info, QUICRTTProvider *rtt_provider, QUICApplicationMap *app_map);
 
   void init_flow_control_params(const std::shared_ptr<const QUICTransportParameters> &local_tp,
@@ -63,9 +62,9 @@
   virtual QUICConnectionErrorUPtr handle_frame(QUICEncryptionLevel level, const QUICFrame &frame) override;
 
   // QUICFrameGenerator
-  bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override;
+  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,
-                            ink_hrtime timestamp) override;
+                            size_t current_packet_size, uint32_t timestamp) override;
 
 protected:
   virtual bool _is_level_matched(QUICEncryptionLevel level) override;
diff --git a/iocore/net/quic/QUICTokenCreator.cc b/iocore/net/quic/QUICTokenCreator.cc
new file mode 100644
index 0000000..9d1ef62
--- /dev/null
+++ b/iocore/net/quic/QUICTokenCreator.cc
@@ -0,0 +1,71 @@
+/** @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 "QUICTokenCreator.h"
+
+bool
+QUICTokenCreator::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;
+  }
+
+  return !this->_is_resumption_token_sent;
+}
+
+QUICFrame *
+QUICTokenCreator::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;
+  }
+
+  if (this->_is_resumption_token_sent) {
+    return frame;
+  }
+
+  if (this->_context->connection_info()->direction() == NET_VCONNECTION_IN) {
+    // TODO Make expiration period configurable
+    QUICResumptionToken token(this->_context->connection_info()->five_tuple().source(),
+                              this->_context->connection_info()->connection_id(), Thread::get_hrtime() + HRTIME_HOURS(24));
+    frame = QUICFrameFactory::create_new_token_frame(buf, token, this->_issue_frame_id(), this);
+    if (frame) {
+      if (frame->size() < maximum_frame_size) {
+        this->_is_resumption_token_sent = true;
+      } else {
+        // Cancel generating frame
+        frame = nullptr;
+      }
+    }
+  }
+
+  return frame;
+}
+
+void
+QUICTokenCreator::_on_frame_lost(QUICFrameInformationUPtr &info)
+{
+  this->_is_resumption_token_sent = false;
+}
diff --git a/iocore/net/quic/QUICTokenCreator.h b/iocore/net/quic/QUICTokenCreator.h
new file mode 100644
index 0000000..e107fb9
--- /dev/null
+++ b/iocore/net/quic/QUICTokenCreator.h
@@ -0,0 +1,42 @@
+/** @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 "QUICFrameGenerator.h"
+#include "QUICContext.h"
+
+class QUICTokenCreator final : public QUICFrameGenerator
+{
+public:
+  QUICTokenCreator(QUICContext *context) : _context(context) {}
+
+  bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) 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 seq_num) override;
+
+private:
+  void _on_frame_lost(QUICFrameInformationUPtr &info) override;
+
+  bool _is_resumption_token_sent = false;
+  QUICContext *_context          = nullptr;
+};
diff --git a/iocore/net/quic/QUICTransportParameters.cc b/iocore/net/quic/QUICTransportParameters.cc
index 04f0009..248cfcb 100644
--- a/iocore/net/quic/QUICTransportParameters.cc
+++ b/iocore/net/quic/QUICTransportParameters.cc
@@ -183,7 +183,7 @@
   if ((ite = this->_parameters.find(QUICTransportParameterId::INITIAL_MAX_STREAM_DATA_UNI)) != this->_parameters.end()) {
   }
 
-  if ((ite = this->_parameters.find(QUICTransportParameterId::DISABLE_MIGRATION)) != this->_parameters.end()) {
+  if ((ite = this->_parameters.find(QUICTransportParameterId::DISABLE_ACTIVE_MIGRATION)) != this->_parameters.end()) {
   }
 
   if ((ite = this->_parameters.find(QUICTransportParameterId::MAX_ACK_DELAY)) != this->_parameters.end()) {
@@ -290,11 +290,12 @@
       uint64_t int_value;
       size_t int_value_len;
       QUICVariableInt::decode(int_value, int_value_len, p.second->data(), p.second->len());
-      Debug(tag, "%s: 0x%" PRIx64 " (%" PRIu64 ")", QUICDebugNames::transport_parameter_id(p.first), int_value, int_value);
+      Debug(tag, "%s (%" PRIu32 "): 0x%" PRIx64 " (%" PRIu64 ")", QUICDebugNames::transport_parameter_id(p.first),
+            static_cast<uint16_t>(p.first), int_value, int_value);
     } else if (p.second->len() <= 24) {
       char hex_str[65];
       to_hex_str(hex_str, sizeof(hex_str), p.second->data(), p.second->len());
-      Debug(tag, "%s: %s", QUICDebugNames::transport_parameter_id(p.first), hex_str);
+      Debug(tag, "%s (%" PRIu32 "): %s", QUICDebugNames::transport_parameter_id(p.first), static_cast<uint16_t>(p.first), hex_str);
     } else if (QUICTransportParameterId::PREFERRED_ADDRESS == p.first) {
       QUICPreferredAddress pref_addr(p.second->data(), p.second->len());
       char cid_hex_str[QUICConnectionId::MAX_HEX_STR_LENGTH];
diff --git a/iocore/net/quic/QUICTransportParameters.h b/iocore/net/quic/QUICTransportParameters.h
index 6886bd0..df46d9d 100644
--- a/iocore/net/quic/QUICTransportParameters.h
+++ b/iocore/net/quic/QUICTransportParameters.h
@@ -46,8 +46,9 @@
     INITIAL_MAX_STREAMS_UNI,
     ACK_DELAY_EXPONENT,
     MAX_ACK_DELAY,
-    DISABLE_MIGRATION,
+    DISABLE_ACTIVE_MIGRATION,
     PREFERRED_ADDRESS,
+    ACTIVE_CONNECTION_ID_LIMIT,
   };
 
   explicit operator bool() const { return true; }
diff --git a/iocore/net/quic/QUICTypes.cc b/iocore/net/quic/QUICTypes.cc
index 29e09d2..5c354a9 100644
--- a/iocore/net/quic/QUICTypes.cc
+++ b/iocore/net/quic/QUICTypes.cc
@@ -252,15 +252,15 @@
 }
 
 void
-QUICTypeUtil::write_QUICTransErrorCode(uint16_t error_code, uint8_t *buf, size_t *len)
+QUICTypeUtil::write_QUICTransErrorCode(uint64_t error_code, uint8_t *buf, size_t *len)
 {
-  QUICIntUtil::write_uint_as_nbytes(static_cast<uint64_t>(error_code), 2, buf, len);
+  QUICIntUtil::write_QUICVariableInt(static_cast<uint64_t>(error_code), buf, len);
 }
 
 void
 QUICTypeUtil::write_QUICAppErrorCode(QUICAppErrorCode error_code, uint8_t *buf, size_t *len)
 {
-  QUICIntUtil::write_uint_as_nbytes(static_cast<uint64_t>(error_code), 2, buf, len);
+  QUICIntUtil::write_QUICVariableInt(static_cast<uint64_t>(error_code), buf, len);
 }
 
 void
@@ -553,6 +553,24 @@
 }
 
 //
+// QUICPath
+//
+
+QUICPath::QUICPath(IpEndpoint local_ep, IpEndpoint remote_ep) : _local_ep(local_ep), _remote_ep(remote_ep) {}
+
+const IpEndpoint &
+QUICPath::local_ep() const
+{
+  return this->_local_ep;
+}
+
+const IpEndpoint &
+QUICPath::remote_ep() const
+{
+  return this->_remote_ep;
+}
+
+//
 // QUICConnectionId
 //
 QUICConnectionId
@@ -660,7 +678,7 @@
     return false;
   }
 
-  dst = buf[QUICInvariants::LH_CIL_OFFSET] >> 4;
+  dst = buf[QUICInvariants::LH_CIL_OFFSET];
 
   return true;
 }
@@ -670,11 +688,12 @@
 {
   ink_assert(QUICInvariants::is_long_header(buf));
 
-  if (buf_len < QUICInvariants::LH_CIL_OFFSET) {
+  uint8_t dcil = 0;
+  if (!QUICInvariants::dcil(dcil, buf, buf_len)) {
     return false;
   }
 
-  dst = buf[QUICInvariants::LH_CIL_OFFSET] & 0x0F;
+  dst = buf[QUICInvariants::LH_CIL_OFFSET + 1 + dcil];
 
   return true;
 }
@@ -691,13 +710,12 @@
       return false;
     }
 
-    if (dcil) {
-      dcid_len = dcil + QUICInvariants::CIL_BASE;
-    } else {
+    if (dcil == 0) {
       dst = QUICConnectionId::ZERO();
       return true;
     }
 
+    dcid_len    = dcil;
     dcid_offset = QUICInvariants::LH_DCID_OFFSET;
   } else {
     // remote dcil is local scil
@@ -724,34 +742,30 @@
   }
 
   uint8_t scid_offset = QUICInvariants::LH_DCID_OFFSET;
-  uint8_t scid_len    = 0;
 
   uint8_t dcil = 0;
   if (!QUICInvariants::dcil(dcil, buf, buf_len)) {
     return false;
   }
 
-  if (dcil) {
-    scid_offset += (dcil + QUICInvariants::CIL_BASE);
-  }
+  scid_offset += dcil;
 
   uint8_t scil = 0;
   if (!QUICInvariants::scil(scil, buf, buf_len)) {
     return false;
   }
+  scid_offset += 1;
 
-  if (scil) {
-    scid_len = scil + QUICInvariants::CIL_BASE;
-  } else {
+  if (scil == 0) {
     dst = QUICConnectionId::ZERO();
     return true;
   }
 
-  if (scid_offset + scid_len > buf_len) {
+  if (scid_offset + scil > buf_len) {
     return false;
   }
 
-  dst = QUICTypeUtil::read_QUICConnectionId(buf + scid_offset, scid_len);
+  dst = QUICTypeUtil::read_QUICConnectionId(buf + scid_offset, scil);
 
   return true;
 }
diff --git a/iocore/net/quic/QUICTypes.h b/iocore/net/quic/QUICTypes.h
index ee19ab0..7c3a537 100644
--- a/iocore/net/quic/QUICTypes.h
+++ b/iocore/net/quic/QUICTypes.h
@@ -51,7 +51,7 @@
 // Note: Fix QUIC_ALPN_PROTO_LIST in QUICConfig.cc
 // Note: Change ExtensionType (QUICTransportParametersHandler::TRANSPORT_PARAMETER_ID) if it's changed
 constexpr QUICVersion QUIC_SUPPORTED_VERSIONS[] = {
-  0xff000014,
+  0xff000017,
 };
 constexpr QUICVersion QUIC_EXERCISE_VERSION = 0x1a2a3a4a;
 
@@ -75,9 +75,10 @@
 
 // introduce by draft-19 kPacketNumberSpace
 enum class QUICPacketNumberSpace {
-  Initial,
-  Handshake,
-  ApplicationData,
+  None            = -1,
+  Initial         = 0,
+  Handshake       = 1,
+  ApplicationData = 2,
 };
 
 // Devide to QUICPacketType and QUICPacketLongHeaderType ?
@@ -147,24 +148,23 @@
   APPLICATION,
 };
 
-enum class QUICTransErrorCode : uint16_t {
+enum class QUICTransErrorCode : uint64_t {
   NO_ERROR = 0x00,
   INTERNAL_ERROR,
   SERVER_BUSY,
   FLOW_CONTROL_ERROR,
-  STREAM_ID_ERROR,
+  STREAM_LIMIT_ERROR,
   STREAM_STATE_ERROR,
-  FINAL_OFFSET_ERROR,
+  FINAL_SIZE_ERROR,
   FRAME_ENCODING_ERROR,
   TRANSPORT_PARAMETER_ERROR,
-  VERSION_NEGOTIATION_ERROR,
-  PROTOCOL_VIOLATION,
-  INVALID_MIGRATION = 0x0C,
-  CRYPTO_ERROR      = 0x0100, // 0x100 - 0x1FF
+  PROTOCOL_VIOLATION     = 0x0A,
+  CRYPTO_BUFFER_EXCEEDED = 0x0D,
+  CRYPTO_ERROR           = 0x0100, // 0x100 - 0x1FF
 };
 
 // Application Protocol Error Codes defined in application
-using QUICAppErrorCode                          = uint16_t;
+using QUICAppErrorCode                          = uint64_t;
 constexpr uint16_t QUIC_APP_ERROR_CODE_STOPPING = 0;
 
 class QUICError
@@ -225,7 +225,7 @@
   static uint8_t SCID_LEN;
 
   static const int MIN_LENGTH_FOR_INITIAL = 8;
-  static const int MAX_LENGTH             = 18;
+  static const int MAX_LENGTH             = 20;
   static const size_t MAX_HEX_STR_LENGTH  = MAX_LENGTH * 2 + 1;
   static QUICConnectionId ZERO();
   QUICConnectionId();
@@ -398,8 +398,8 @@
 class QUICPreferredAddress
 {
 public:
-  constexpr static int16_t MIN_LEN = 26;
-  constexpr static int16_t MAX_LEN = 295;
+  constexpr static int16_t MIN_LEN = 41;
+  constexpr static int16_t MAX_LEN = 61;
 
   QUICPreferredAddress(IpEndpoint endpoint_ipv4, IpEndpoint endpoint_ipv6, const QUICConnectionId &cid,
                        QUICStatelessResetToken token)
@@ -419,8 +419,8 @@
   void store(uint8_t *buf, uint16_t &len) const;
 
 private:
-  IpEndpoint _endpoint_ipv4;
-  IpEndpoint _endpoint_ipv6;
+  IpEndpoint _endpoint_ipv4 = {};
+  IpEndpoint _endpoint_ipv6 = {};
   QUICConnectionId _cid;
   QUICStatelessResetToken _token;
   bool _valid = false;
@@ -457,6 +457,61 @@
   uint64_t _hash_code = 0;
 };
 
+class QUICPath
+{
+public:
+  QUICPath(IpEndpoint local_ep, IpEndpoint remote_ep);
+  const IpEndpoint &local_ep() const;
+  const IpEndpoint &remote_ep() const;
+
+  inline bool
+  operator==(const QUICPath &x) const
+  {
+    if ((this->_local_ep.port() != 0 && x._local_ep.port() != 0) && this->_local_ep.port() != x._local_ep.port()) {
+      return false;
+    }
+
+    if ((this->_remote_ep.port() != 0 && x._remote_ep.port() != 0) && this->_remote_ep.port() != x._remote_ep.port()) {
+      return false;
+    }
+
+    if ((!IpAddr(this->_local_ep).isAnyAddr() && !IpAddr(x._local_ep).isAnyAddr()) && this->_local_ep != x._local_ep) {
+      return false;
+    }
+
+    if ((!IpAddr(this->_remote_ep).isAnyAddr() || !IpAddr(x._remote_ep).isAnyAddr()) && this->_remote_ep != x._remote_ep) {
+      return false;
+    }
+
+    return true;
+  }
+
+private:
+  IpEndpoint _local_ep;
+  IpEndpoint _remote_ep;
+};
+
+class QUICPathHasher
+{
+public:
+  std::size_t
+  operator()(const QUICPath &k) const
+  {
+    return k.remote_ep().port();
+  }
+};
+
+class QUICPathValidationData
+{
+public:
+  QUICPathValidationData(const uint8_t *data) { memcpy(this->_data, data, sizeof(this->_data)); }
+
+  inline operator const uint8_t *() const { return this->_data; }
+
+private:
+  uint8_t _data[8];
+};
+
 class QUICTPConfig
 {
 public:
@@ -471,11 +526,13 @@
   virtual uint64_t initial_max_streams_uni() const             = 0;
   virtual uint8_t ack_delay_exponent() const                   = 0;
   virtual uint8_t max_ack_delay() const                        = 0;
+  virtual uint8_t active_cid_limit() const                     = 0;
 };
 
 class QUICLDConfig
 {
 public:
+  virtual ~QUICLDConfig() {}
   virtual uint32_t packet_threshold() const = 0;
   virtual float time_threshold() const      = 0;
   virtual ink_hrtime granularity() const    = 0;
@@ -485,6 +542,7 @@
 class QUICCCConfig
 {
 public:
+  virtual ~QUICCCConfig() {}
   virtual uint32_t max_datagram_size() const               = 0;
   virtual uint32_t initial_window() const                  = 0;
   virtual uint32_t minimum_window() const                  = 0;
@@ -520,7 +578,7 @@
   static void write_QUICVersion(QUICVersion version, uint8_t *buf, size_t *len);
   static void write_QUICStreamId(QUICStreamId stream_id, uint8_t *buf, size_t *len);
   static void write_QUICOffset(QUICOffset offset, uint8_t *buf, size_t *len);
-  static void write_QUICTransErrorCode(uint16_t error_code, uint8_t *buf, size_t *len);
+  static void write_QUICTransErrorCode(uint64_t error_code, uint8_t *buf, size_t *len);
   static void write_QUICAppErrorCode(QUICAppErrorCode error_code, uint8_t *buf, size_t *len);
   static void write_QUICMaxData(uint64_t max_data, uint8_t *buf, size_t *len);
 
@@ -533,18 +591,11 @@
   static bool is_long_header(const uint8_t *buf);
   static bool is_version_negotiation(QUICVersion v);
   static bool version(QUICVersion &dst, const uint8_t *buf, uint64_t buf_len);
-  /**
-   * This function returns the raw value. You'll need to add 3 to the returned value to get the actual connection id length.
-   */
   static bool dcil(uint8_t &dst, const uint8_t *buf, uint64_t buf_len);
-  /**
-   * This function returns the raw value. You'll need to add 3 to the returned value to get the actual connection id length.
-   */
   static bool scil(uint8_t &dst, const uint8_t *buf, uint64_t buf_len);
   static bool dcid(QUICConnectionId &dst, const uint8_t *buf, uint64_t buf_len);
   static bool scid(QUICConnectionId &dst, const uint8_t *buf, uint64_t buf_len);
 
-  static const size_t CIL_BASE          = 3;
   static const size_t LH_VERSION_OFFSET = 1;
   static const size_t LH_CIL_OFFSET     = 5;
   static const size_t LH_DCID_OFFSET    = 6;
diff --git a/iocore/net/quic/QUICUnidirectionalStream.cc b/iocore/net/quic/QUICUnidirectionalStream.cc
index b6dae8f..90bd1dd 100644
--- a/iocore/net/quic/QUICUnidirectionalStream.cc
+++ b/iocore/net/quic/QUICUnidirectionalStream.cc
@@ -125,14 +125,14 @@
 }
 
 bool
-QUICSendStream::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp)
+QUICSendStream::will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num)
 {
   return !this->is_retransmited_frame_queue_empty() || this->_write_vio.get_reader()->is_read_avail_more_than(0);
 }
 
 QUICFrame *
 QUICSendStream::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                               ink_hrtime timestamp)
+                               size_t current_packet_size, uint32_t seq_num)
 {
   SCOPED_MUTEX_LOCK(lock, this->_write_vio.mutex, this_ethread());
 
@@ -183,7 +183,8 @@
     uint64_t stream_credit = this->_remote_flow_controller.credit();
     if (stream_credit == 0) {
       // STREAM_DATA_BLOCKED
-      frame = this->_remote_flow_controller.generate_frame(buf, level, UINT16_MAX, maximum_frame_size, timestamp);
+      frame =
+        this->_remote_flow_controller.generate_frame(buf, level, UINT16_MAX, maximum_frame_size, current_packet_size, seq_num);
       return frame;
     }
 
@@ -526,15 +527,15 @@
 }
 
 bool
-QUICReceiveStream::will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp)
+QUICReceiveStream::will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num)
 {
-  return this->_local_flow_controller.will_generate_frame(level, timestamp) ||
+  return this->_local_flow_controller.will_generate_frame(level, current_packet_size, ack_eliciting, seq_num) ||
          (this->_stop_sending_reason != nullptr && this->_is_stop_sending_sent == false);
 }
 
 QUICFrame *
 QUICReceiveStream::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                                  ink_hrtime timestamp)
+                                  size_t current_packet_size, uint32_t seq_num)
 {
   QUICFrame *frame = nullptr;
   // STOP_SENDING
@@ -548,7 +549,7 @@
   }
 
   // MAX_STREAM_DATA
-  frame = this->_local_flow_controller.generate_frame(buf, level, UINT16_MAX, maximum_frame_size, timestamp);
+  frame = this->_local_flow_controller.generate_frame(buf, level, UINT16_MAX, maximum_frame_size, current_packet_size, seq_num);
   return frame;
 }
 
diff --git a/iocore/net/quic/QUICUnidirectionalStream.h b/iocore/net/quic/QUICUnidirectionalStream.h
index 0c71627..5ebe552 100644
--- a/iocore/net/quic/QUICUnidirectionalStream.h
+++ b/iocore/net/quic/QUICUnidirectionalStream.h
@@ -37,9 +37,9 @@
   int state_stream_closed(int event, void *data);
 
   // QUICFrameGenerator
-  bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override;
+  bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) override;
   QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            ink_hrtime timestamp) override;
+                            size_t current_packet_size, uint32_t seq_num) override;
 
   virtual QUICConnectionErrorUPtr recv(const QUICMaxStreamDataFrame &frame) override;
   virtual QUICConnectionErrorUPtr recv(const QUICStopSendingFrame &frame) override;
@@ -86,9 +86,9 @@
   int state_stream_closed(int event, void *data);
 
   // QUICFrameGenerator
-  bool will_generate_frame(QUICEncryptionLevel level, ink_hrtime timestamp) override;
+  bool will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num) override;
   QUICFrame *generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t connection_credit, uint16_t maximum_frame_size,
-                            ink_hrtime timestamp) override;
+                            size_t current_packet_size, uint32_t seq_num) override;
 
   virtual QUICConnectionErrorUPtr recv(const QUICStreamFrame &frame) override;
   virtual QUICConnectionErrorUPtr recv(const QUICStreamDataBlockedFrame &frame) override;
diff --git a/iocore/net/quic/test/test_QUICAckFrameCreator.cc b/iocore/net/quic/test/test_QUICAckFrameCreator.cc
index 99b40e7..39b7bc9 100644
--- a/iocore/net/quic/test/test_QUICAckFrameCreator.cc
+++ b/iocore/net/quic/test/test_QUICAckFrameCreator.cc
@@ -32,63 +32,67 @@
   uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE];
 
   // Initial state
-  QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+  QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
   QUICAckFrame *frame  = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame == nullptr);
 
   // One packet
   ack_manager.update(level, 1, 1, false);
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
   frame     = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame != nullptr);
   CHECK(frame->ack_block_count() == 0);
   CHECK(frame->largest_acknowledged() == 1);
   CHECK(frame->ack_block_section()->first_ack_block() == 0);
+  ack_frame->~QUICFrame();
 
   // retry
-  CHECK(ack_manager.will_generate_frame(level, 0) == false);
+  CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == false);
 
   // Not sequential
   ack_manager.update(level, 2, 1, false);
   ack_manager.update(level, 5, 1, false);
   ack_manager.update(level, 3, 1, false);
   ack_manager.update(level, 4, 1, false);
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
   frame     = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame != nullptr);
   CHECK(frame->ack_block_count() == 0);
   CHECK(frame->largest_acknowledged() == 5);
   CHECK(frame->ack_block_section()->first_ack_block() == 4);
+  ack_frame->~QUICFrame();
 
   // Loss
   ack_manager.update(level, 6, 1, false);
   ack_manager.update(level, 7, 1, false);
   ack_manager.update(level, 10, 1, false);
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
   frame     = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame != nullptr);
   CHECK(frame->ack_block_count() == 1);
   CHECK(frame->largest_acknowledged() == 10);
   CHECK(frame->ack_block_section()->first_ack_block() == 0);
   CHECK(frame->ack_block_section()->begin()->gap() == 1);
+  ack_frame->~QUICFrame();
 
   // on frame acked
   ack_manager.on_frame_acked(frame->id());
 
-  CHECK(ack_manager.will_generate_frame(level, 0) == false);
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+  CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == false);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
   CHECK(ack_frame == nullptr);
 
   ack_manager.update(level, 11, 1, false);
   ack_manager.update(level, 12, 1, false);
   ack_manager.update(level, 13, 1, false);
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
   frame     = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame != nullptr);
   CHECK(frame->ack_block_count() == 0);
   CHECK(frame->largest_acknowledged() == 13);
   CHECK(frame->ack_block_section()->first_ack_block() == 2);
   CHECK(frame->ack_block_section()->begin()->gap() == 0);
+  ack_frame->~QUICFrame();
 
   ack_manager.on_frame_acked(frame->id());
 
@@ -96,17 +100,18 @@
   ack_manager.update(level, 14, 1, true);
   ack_manager.update(level, 15, 1, true);
   ack_manager.update(level, 16, 1, true);
-  CHECK(ack_manager.will_generate_frame(level, 0) == false);
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+  CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == false);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
 
   ack_manager.update(level, 17, 1, false);
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
   frame     = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame != nullptr);
   CHECK(frame->ack_block_count() == 0);
   CHECK(frame->largest_acknowledged() == 17);
   CHECK(frame->ack_block_section()->first_ack_block() == 3);
   CHECK(frame->ack_block_section()->begin()->gap() == 0);
+  ack_frame->~QUICFrame();
 }
 
 TEST_CASE("QUICAckFrameManager should send", "[quic]")
@@ -117,7 +122,7 @@
 
     QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT;
     ack_manager.update(level, 2, 1, false);
-    CHECK(ack_manager.will_generate_frame(level, 0) == true);
+    CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == true);
   }
 
   SECTION("QUIC delay ack and unorder packet", "[quic]")
@@ -126,13 +131,13 @@
 
     QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT;
     ack_manager.update(level, 0, 1, false);
-    CHECK(ack_manager.will_generate_frame(level, 0) == false);
+    CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == false);
 
     ack_manager.update(level, 1, 1, false);
-    CHECK(ack_manager.will_generate_frame(level, 0) == false);
+    CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == false);
 
     ack_manager.update(level, 3, 1, false);
-    CHECK(ack_manager.will_generate_frame(level, 0) == true);
+    CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == true);
   }
 
   SECTION("QUIC delay too much time", "[quic]")
@@ -142,12 +147,12 @@
 
     QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT;
     ack_manager.update(level, 0, 1, false);
-    CHECK(ack_manager.will_generate_frame(level, 0) == false);
+    CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == false);
 
     sleep(1);
     Thread::get_hrtime_updated();
     ack_manager.update(level, 1, 1, false);
-    CHECK(ack_manager.will_generate_frame(level, 0) == true);
+    CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == true);
   }
 
   SECTION("QUIC intial packet", "[quic]")
@@ -156,7 +161,7 @@
 
     QUICEncryptionLevel level = QUICEncryptionLevel::INITIAL;
     ack_manager.update(level, 0, 1, false);
-    CHECK(ack_manager.will_generate_frame(level, 0) == true);
+    CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == true);
   }
 
   SECTION("QUIC handshake packet", "[quic]")
@@ -165,7 +170,7 @@
 
     QUICEncryptionLevel level = QUICEncryptionLevel::HANDSHAKE;
     ack_manager.update(level, 0, 1, false);
-    CHECK(ack_manager.will_generate_frame(level, 0) == true);
+    CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == true);
   }
 
   SECTION("QUIC frame fired", "[quic]")
@@ -174,11 +179,11 @@
     QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT;
 
     ack_manager.update(level, 0, 1, false);
-    CHECK(ack_manager.will_generate_frame(level, 0) == false);
+    CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == false);
 
     sleep(1);
     Thread::get_hrtime_updated();
-    CHECK(ack_manager.will_generate_frame(level, 0) == true);
+    CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == true);
   }
 
   SECTION("QUIC refresh frame", "[quic]")
@@ -187,27 +192,28 @@
     QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT;
 
     uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE];
-    QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+    QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
     QUICAckFrame *frame  = static_cast<QUICAckFrame *>(ack_frame);
     CHECK(frame == nullptr);
 
     // unorder frame should sent immediately
     ack_manager.update(level, 1, 1, false);
-    CHECK(ack_manager.will_generate_frame(level, 0) == true);
+    CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == true);
 
     ack_manager.update(level, 2, 1, false);
 
     // Delay due to some reason, the frame is not valued any more, but still valued
     sleep(1);
     Thread::get_hrtime_updated();
-    CHECK(ack_manager.will_generate_frame(level, 0) == true);
-    ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+    CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == true);
+    ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
     frame     = static_cast<QUICAckFrame *>(ack_frame);
 
     CHECK(frame->ack_block_count() == 0);
     CHECK(frame->largest_acknowledged() == 2);
     CHECK(frame->ack_block_section()->first_ack_block() == 1);
     CHECK(frame->ack_block_section()->begin()->gap() == 0);
+    ack_frame->~QUICFrame();
   }
 }
 
@@ -218,7 +224,7 @@
 
   // Initial state
   uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE];
-  QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+  QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
   QUICAckFrame *frame  = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame == nullptr);
 
@@ -228,25 +234,27 @@
   ack_manager.update(level, 8, 1, false);
   ack_manager.update(level, 9, 1, false);
 
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
   frame     = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame != nullptr);
   CHECK(frame->ack_block_count() == 2);
   CHECK(frame->largest_acknowledged() == 9);
   CHECK(frame->ack_block_section()->first_ack_block() == 1);
   CHECK(frame->ack_block_section()->begin()->gap() == 0);
+  ack_frame->~QUICFrame();
 
-  CHECK(ack_manager.will_generate_frame(level, 0) == false);
+  CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == false);
 
   ack_manager.update(level, 7, 1, false);
   ack_manager.update(level, 4, 1, false);
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
   frame     = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame != nullptr);
   CHECK(frame->ack_block_count() == 1);
   CHECK(frame->largest_acknowledged() == 9);
   CHECK(frame->ack_block_section()->first_ack_block() == 5);
   CHECK(frame->ack_block_section()->begin()->gap() == 0);
+  ack_frame->~QUICFrame();
 }
 
 TEST_CASE("QUICAckFrameManager_QUICAckFrameCreator", "[quic]")
@@ -296,7 +304,7 @@
   uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE];
 
   // Initial state
-  QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+  QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
   QUICAckFrame *frame  = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame == nullptr);
 
@@ -306,39 +314,42 @@
   ack_manager.update(level, 8, 1, false);
   ack_manager.update(level, 9, 1, false);
 
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
   frame     = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame != nullptr);
   CHECK(frame->ack_block_count() == 2);
   CHECK(frame->largest_acknowledged() == 9);
   CHECK(frame->ack_block_section()->first_ack_block() == 1);
   CHECK(frame->ack_block_section()->begin()->gap() == 0);
+  ack_frame->~QUICFrame();
 
   ack_manager.on_frame_lost(frame->id());
-  CHECK(ack_manager.will_generate_frame(level, 0) == true);
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+  CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == true);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
   frame     = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame != nullptr);
   CHECK(frame->ack_block_count() == 2);
   CHECK(frame->largest_acknowledged() == 9);
   CHECK(frame->ack_block_section()->first_ack_block() == 1);
   CHECK(frame->ack_block_section()->begin()->gap() == 0);
+  ack_frame->~QUICFrame();
 
-  CHECK(ack_manager.will_generate_frame(level, 0) == false);
+  CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == false);
   ack_manager.on_frame_lost(frame->id());
-  CHECK(ack_manager.will_generate_frame(level, 0) == true);
+  CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == true);
   ack_manager.update(level, 7, 1, false);
   ack_manager.update(level, 4, 1, false);
 
-  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+  ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
   frame     = static_cast<QUICAckFrame *>(ack_frame);
   CHECK(frame != nullptr);
   CHECK(frame->ack_block_count() == 1);
   CHECK(frame->largest_acknowledged() == 9);
   CHECK(frame->ack_block_section()->first_ack_block() == 5);
   CHECK(frame->ack_block_section()->begin()->gap() == 0);
+  ack_frame->~QUICFrame();
 
-  CHECK(ack_manager.will_generate_frame(level, 0) == false);
+  CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == false);
 }
 
 TEST_CASE("QUICAckFrameManager ack only packet", "[quic]")
@@ -350,7 +361,7 @@
     uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE];
 
     // Initial state
-    QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+    QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
     QUICAckFrame *frame  = static_cast<QUICAckFrame *>(ack_frame);
     CHECK(frame == nullptr);
 
@@ -360,19 +371,20 @@
     ack_manager.update(level, 4, 1, false);
     ack_manager.update(level, 5, 1, false);
 
-    CHECK(ack_manager.will_generate_frame(level, 0) == true);
+    CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == true);
 
-    ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+    ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
     frame     = static_cast<QUICAckFrame *>(ack_frame);
     CHECK(frame != nullptr);
     CHECK(frame->ack_block_count() == 0);
     CHECK(frame->largest_acknowledged() == 5);
     CHECK(frame->ack_block_section()->first_ack_block() == 4);
     CHECK(frame->ack_block_section()->begin()->gap() == 0);
+    ack_frame->~QUICFrame();
 
     ack_manager.update(level, 6, 1, true);
     ack_manager.update(level, 7, 1, true);
-    CHECK(ack_manager.will_generate_frame(level, 0) == false);
+    CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == false);
   }
 
   SECTION("ONE_RTT")
@@ -382,7 +394,7 @@
     uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE];
 
     // Initial state
-    QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+    QUICFrame *ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
     QUICAckFrame *frame  = static_cast<QUICAckFrame *>(ack_frame);
     CHECK(frame == nullptr);
 
@@ -392,18 +404,19 @@
     ack_manager.update(level, 4, 1, false);
     ack_manager.update(level, 5, 1, false);
 
-    CHECK(ack_manager.will_generate_frame(level, 0) == true);
+    CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == true);
 
-    ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0);
+    ack_frame = ack_manager.generate_frame(frame_buf, level, UINT16_MAX, UINT16_MAX, 0, 0);
     frame     = static_cast<QUICAckFrame *>(ack_frame);
     CHECK(frame != nullptr);
     CHECK(frame->ack_block_count() == 0);
     CHECK(frame->largest_acknowledged() == 5);
     CHECK(frame->ack_block_section()->first_ack_block() == 4);
     CHECK(frame->ack_block_section()->begin()->gap() == 0);
+    ack_frame->~QUICFrame();
 
     ack_manager.update(level, 6, 1, true);
     ack_manager.update(level, 7, 1, true);
-    CHECK(ack_manager.will_generate_frame(level, 0) == false);
+    CHECK(ack_manager.will_generate_frame(level, 0, true, 0) == false);
   }
 }
diff --git a/iocore/net/quic/test/test_QUICAltConnectionManager.cc b/iocore/net/quic/test/test_QUICAltConnectionManager.cc
index d30da7b..298f21b 100644
--- a/iocore/net/quic/test/test_QUICAltConnectionManager.cc
+++ b/iocore/net/quic/test/test_QUICAltConnectionManager.cc
@@ -58,6 +58,7 @@
     CHECK(memcmp(pref_addr->endpoint_ipv6().sin6.sin6_addr.s6_addr, ipv6_addr.s6_addr, 16) == 0);
     CHECK(pref_addr->cid() == cid55);
     CHECK(memcmp(pref_addr->token().buf(), buf + 26, 16) == 0);
+    delete pref_addr;
   }
 
   SECTION("store")
@@ -85,6 +86,7 @@
     pref_addr->store(actual, len);
     CHECK(sizeof(buf) == len);
     CHECK(memcmp(buf, actual, sizeof(buf)) == 0);
+    delete pref_addr;
   }
 
   SECTION("unavailable")
@@ -93,5 +95,6 @@
     CHECK(!pref_addr->is_available());
     CHECK(!pref_addr->has_ipv4());
     CHECK(!pref_addr->has_ipv6());
+    delete pref_addr;
   }
 }
diff --git a/iocore/net/quic/test/test_QUICFlowController.cc b/iocore/net/quic/test/test_QUICFlowController.cc
index d58a311..61bb652 100644
--- a/iocore/net/quic/test/test_QUICFlowController.cc
+++ b/iocore/net/quic/test/test_QUICFlowController.cc
@@ -116,7 +116,7 @@
   fc.forward_limit(2048);
   CHECK(fc.current_offset() == 1024);
   CHECK(fc.current_limit() == 2048);
-  QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0);
+  QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0, 0);
   CHECK(frame);
   CHECK(frame->type() == QUICFrameType::MAX_DATA);
 
@@ -168,7 +168,7 @@
   CHECK(fc.current_offset() == 1000);
   CHECK(fc.current_limit() == 1024);
   CHECK(ret != 0);
-  QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0);
+  QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0, 0);
   CHECK(frame);
   CHECK(frame->type() == QUICFrameType::DATA_BLOCKED);
 
@@ -199,9 +199,9 @@
   CHECK(fc.current_limit() == 1024);
   CHECK(ret == 0);
 
-  CHECK(fc.will_generate_frame(QUICEncryptionLevel::ONE_RTT, 0));
+  CHECK(fc.will_generate_frame(QUICEncryptionLevel::ONE_RTT, 0, true, 0));
   // if there're anything to send
-  QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0);
+  QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0, 0);
   CHECK(frame);
   CHECK(frame->type() == QUICFrameType::DATA_BLOCKED);
 
@@ -265,7 +265,7 @@
   fc.forward_limit(2048);
   CHECK(fc.current_offset() == 1024);
   CHECK(fc.current_limit() == 2048);
-  QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0);
+  QUICFrame *frame = fc.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 0, 1024, 0, 0);
   CHECK(frame);
   CHECK(frame->type() == QUICFrameType::MAX_STREAM_DATA);
 
@@ -306,7 +306,7 @@
   CHECK(ret == 0);
 
   CHECK(fc.credit() == 0);
-  CHECK(fc.will_generate_frame(QUICEncryptionLevel::ONE_RTT, 0));
+  CHECK(fc.will_generate_frame(QUICEncryptionLevel::ONE_RTT, 0, true, 0));
 
   // Delay
   ret = fc.update(512);
@@ -342,23 +342,23 @@
     QUICRemoteConnectionFlowController fc(1024);
 
     // Check initial state
-    auto frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    auto frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     CHECK(!frame);
 
     ret = fc.update(1024);
     CHECK(ret == 0);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     REQUIRE(frame);
     CHECK(static_cast<QUICDataBlockedFrame *>(frame)->offset() == 1024);
     QUICFrameId id = frame->id();
 
     // Don't retransmit unless the frame is lost
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     REQUIRE(!frame);
 
     // Retransmit
     fc.on_frame_lost(id);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     REQUIRE(frame);
     CHECK(static_cast<QUICDataBlockedFrame *>(frame)->offset() == 1024);
 
@@ -366,12 +366,12 @@
     fc.on_frame_lost(frame->id());
     fc.forward_limit(2048);
     ret   = fc.update(1536);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     CHECK(!frame);
 
     // This should not be retransmition
     ret   = fc.update(2048);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     REQUIRE(frame);
     CHECK(static_cast<QUICDataBlockedFrame *>(frame)->offset() == 2048);
   }
@@ -383,23 +383,23 @@
     QUICRemoteStreamFlowController fc(1024, 0);
 
     // Check initial state
-    auto frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    auto frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     CHECK(!frame);
 
     ret = fc.update(1024);
     CHECK(ret == 0);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     REQUIRE(frame);
     CHECK(static_cast<QUICStreamDataBlockedFrame *>(frame)->offset() == 1024);
     QUICFrameId id = frame->id();
 
     // Don't retransmit unless the frame is lost
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     REQUIRE(!frame);
 
     // Retransmit
     fc.on_frame_lost(id);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     REQUIRE(frame);
     CHECK(static_cast<QUICStreamDataBlockedFrame *>(frame)->offset() == 1024);
 
@@ -407,12 +407,12 @@
     fc.on_frame_lost(frame->id());
     fc.forward_limit(2048);
     ret   = fc.update(1536);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     CHECK(!frame);
 
     // This should not be retransmition
     ret   = fc.update(2048);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     REQUIRE(frame);
     CHECK(static_cast<QUICStreamDataBlockedFrame *>(frame)->offset() == 2048);
   }
@@ -424,23 +424,23 @@
     QUICLocalConnectionFlowController fc(&rp, 1024);
 
     // Check initial state
-    auto frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    auto frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     CHECK(!frame);
 
     fc.update(1024);
     fc.forward_limit(1024);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     REQUIRE(frame);
     CHECK(static_cast<QUICMaxDataFrame *>(frame)->maximum_data() == 1024);
     QUICFrameId id = frame->id();
 
     // Don't retransmit unless the frame is lost
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     REQUIRE(!frame);
 
     // Retransmit
     fc.on_frame_lost(id);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     REQUIRE(frame);
     CHECK(static_cast<QUICMaxDataFrame *>(frame)->maximum_data() == 1024);
 
@@ -448,7 +448,7 @@
     fc.on_frame_lost(id);
     fc.forward_limit(2048);
     fc.update(2048);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     REQUIRE(frame);
     CHECK(static_cast<QUICMaxDataFrame *>(frame)->maximum_data() == 2048);
   }
@@ -460,23 +460,23 @@
     QUICLocalStreamFlowController fc(&rp, 1024, 0);
 
     // Check initial state
-    auto frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    auto frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     CHECK(!frame);
 
     fc.update(1024);
     fc.forward_limit(1024);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     REQUIRE(frame);
     CHECK(static_cast<QUICMaxStreamDataFrame *>(frame)->maximum_stream_data() == 1024);
     QUICFrameId id = frame->id();
 
     // Don't retransmit unless the frame is lost
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     REQUIRE(!frame);
 
     // Retransmit
     fc.on_frame_lost(id);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     REQUIRE(frame);
     CHECK(static_cast<QUICMaxStreamDataFrame *>(frame)->maximum_stream_data() == 1024);
 
@@ -484,7 +484,7 @@
     fc.on_frame_lost(id);
     fc.forward_limit(2048);
     fc.update(2048);
-    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0);
+    frame = fc.generate_frame(frame_buf, level, 1024, 1024, 0, 0);
     REQUIRE(frame);
     CHECK(static_cast<QUICMaxStreamDataFrame *>(frame)->maximum_stream_data() == 2048);
   }
diff --git a/iocore/net/quic/test/test_QUICFrame.cc b/iocore/net/quic/test/test_QUICFrame.cc
index a8f81fd..58f68e4 100644
--- a/iocore/net/quic/test/test_QUICFrame.cc
+++ b/iocore/net/quic/test/test_QUICFrame.cc
@@ -84,7 +84,7 @@
       0x01,                   // Stream ID
       0x01, 0x02, 0x03, 0x04, // Stream Data
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::STREAM);
     CHECK(frame1->size() == 6);
     const QUICStreamFrame *stream_frame = static_cast<const QUICStreamFrame *>(frame1);
@@ -103,7 +103,7 @@
       0x05,                         // Data Length
       0x01, 0x02, 0x03, 0x04, 0x05, // Stream Data
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::STREAM);
     CHECK(frame1->size() == 8);
     const QUICStreamFrame *stream_frame = static_cast<const QUICStreamFrame *>(frame1);
@@ -123,7 +123,7 @@
       0x05,                         // Data Length
       0x01, 0x02, 0x03, 0x04, 0x05, // Stream Data
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::STREAM);
     CHECK(frame1->size() == 9);
 
@@ -144,7 +144,7 @@
       0x05,                         // Data Length
       0x01, 0x02, 0x03, 0x04, 0x05, // Stream Data
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::STREAM);
     CHECK(frame1->size() == 9);
 
@@ -165,7 +165,7 @@
       0x05,                   // Data Length
       0x01, 0x02, 0x03, 0x04, // BAD Stream Data
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::STREAM);
     CHECK(frame1->valid() == false);
   }
@@ -176,7 +176,7 @@
       0x0e, // 0b00001OLF (OLF=110)
       0x01, // Stream ID
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::STREAM);
     CHECK(frame1->valid() == false);
   }
@@ -428,7 +428,7 @@
       0x05,                         // Length
       0x01, 0x02, 0x03, 0x04, 0x05, // Crypto Data
     };
-    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf));
+    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf), nullptr);
     CHECK(frame->type() == QUICFrameType::CRYPTO);
     CHECK(frame->size() == sizeof(buf));
 
@@ -444,15 +444,15 @@
       0x06,                   // Type
       0x80, 0x01, 0x00, 0x00, // Offset
     };
-    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf));
+    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf), nullptr);
     CHECK(frame->type() == QUICFrameType::CRYPTO);
     CHECK(frame->valid() == false);
   }
 
   SECTION("Storing")
   {
-    uint8_t buf[32] = {0};
-    size_t len;
+    uint8_t buf[32]    = {0};
+    size_t len         = 0;
     uint8_t expected[] = {
       0x06,                         // Typr
       0x80, 0x01, 0x00, 0x00,       // Offset
@@ -468,7 +468,11 @@
     QUICCryptoFrame crypto_frame(block, 0x010000);
     CHECK(crypto_frame.size() == sizeof(expected));
 
-    crypto_frame.store(buf, &len, 32);
+    Ptr<IOBufferBlock> ibb = crypto_frame.to_io_buffer_block(sizeof(buf));
+    for (auto b = ibb; b; b = b->next) {
+      memcpy(buf + len, b->start(), b->size());
+      len += b->size();
+    }
     CHECK(len == sizeof(expected));
     CHECK(memcmp(buf, expected, sizeof(expected)) == 0);
   }
@@ -486,7 +490,7 @@
       0x00,       // Ack Block Count
       0x00,       // Ack Block Section
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::ACK);
     CHECK(frame1->size() == 6);
     const QUICAckFrame *ack_frame1 = static_cast<const QUICAckFrame *>(frame1);
@@ -494,6 +498,7 @@
     CHECK(ack_frame1->ack_block_count() == 0);
     CHECK(ack_frame1->largest_acknowledged() == 0x12);
     CHECK(ack_frame1->ack_delay() == 0x3456);
+    frame1->~QUICFrame();
   }
 
   SECTION("0 Ack Block, 8 bit packet number length, 8 bit block length")
@@ -505,7 +510,7 @@
       0x00,                   // Ack Block Count
       0x80, 0x00, 0x00, 0x01, // Ack Block Section (First ACK Block Length)
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::ACK);
     CHECK(frame1->size() == 12);
 
@@ -518,6 +523,7 @@
 
     const QUICAckFrame::AckBlockSection *section = ack_frame1->ack_block_section();
     CHECK(section->first_ack_block() == 0x01);
+    frame1->~QUICFrame();
   }
 
   SECTION("2 Ack Block, 8 bit packet number length, 8 bit block length")
@@ -534,7 +540,7 @@
       0xc9, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, // Ack Block Section (ACK Block 2 Length)
     };
 
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::ACK);
     CHECK(frame1->size() == 21);
     const QUICAckFrame *ack_frame1 = static_cast<const QUICAckFrame *>(frame1);
@@ -556,6 +562,7 @@
     CHECK(ite->length() == 0x090a0b0c0d0e0f10);
     ++ite;
     CHECK(ite == section->end());
+    frame1->~QUICFrame();
   }
 
   SECTION("load bad frame")
@@ -565,9 +572,10 @@
       0x12, // Largest Acknowledged
     };
 
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::ACK);
     CHECK(frame1->valid() == false);
+    frame1->~QUICFrame();
   }
 
   SECTION("load bad block")
@@ -583,9 +591,10 @@
       0x85, 0x06, 0x07, 0x08, // Ack Block Section (Gap 2)
     };
 
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::ACK);
     CHECK(frame1->valid() == false);
+    frame1->~QUICFrame();
   }
 
   SECTION("0 Ack Block, 8 bit packet number length, 8 bit block length with ECN section")
@@ -601,7 +610,7 @@
       0x02, // ECT1
       0x03, // ECN-CE
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::ACK);
     CHECK(frame1->size() == 9);
     const QUICAckFrame *ack_frame1 = static_cast<const QUICAckFrame *>(frame1);
@@ -613,6 +622,7 @@
     CHECK(ack_frame1->ecn_section()->ect0_count() == 1);
     CHECK(ack_frame1->ecn_section()->ect1_count() == 2);
     CHECK(ack_frame1->ecn_section()->ecn_ce_count() == 3);
+    frame1->~QUICFrame();
   }
 
   SECTION("0 Ack Block, 8 bit packet number length, 8 bit block length with ECN section")
@@ -627,9 +637,10 @@
       0x01, // ECT0
       0x02, // ECT1
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::ACK);
     CHECK(frame1->valid() == false);
+    frame1->~QUICFrame();
   }
 }
 
@@ -638,7 +649,7 @@
   SECTION("0 Ack Block, 8 bit packet number length, 8 bit block length")
   {
     uint8_t buf[32] = {0};
-    size_t len;
+    size_t len      = 0;
 
     uint8_t expected[] = {
       0x02,       // Type
@@ -651,7 +662,11 @@
     QUICAckFrame ack_frame(0x12, 0x3456, 0);
     CHECK(ack_frame.size() == 6);
 
-    ack_frame.store(buf, &len, 32);
+    Ptr<IOBufferBlock> ibb = ack_frame.to_io_buffer_block(sizeof(buf));
+    for (auto b = ibb; b; b = b->next) {
+      memcpy(buf + len, b->start(), b->size());
+      len += b->size();
+    }
     CHECK(len == 6);
     CHECK(memcmp(buf, expected, len) == 0);
   }
@@ -659,7 +674,7 @@
   SECTION("2 Ack Block, 8 bit packet number length, 8 bit block length")
   {
     uint8_t buf[32] = {0};
-    size_t len;
+    size_t len      = 0;
 
     uint8_t expected[] = {
       0x02,                                           // Type
@@ -678,7 +693,11 @@
     section->add_ack_block({0x05060708, 0x090a0b0c0d0e0f10});
     CHECK(ack_frame.size() == 21);
 
-    ack_frame.store(buf, &len, 32);
+    Ptr<IOBufferBlock> ibb = ack_frame.to_io_buffer_block(sizeof(buf));
+    for (auto b = ibb; b; b = b->next) {
+      memcpy(buf + len, b->start(), b->size());
+      len += b->size();
+    }
     CHECK(len == 21);
     CHECK(memcmp(buf, expected, len) == 0);
   }
@@ -692,12 +711,12 @@
     uint8_t buf1[] = {
       0x04,                                          // Type
       0x92, 0x34, 0x56, 0x78,                        // Stream ID
-      0x00, 0x01,                                    // Error Code
+      0x01,                                          // Error Code
       0xd1, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 // Final Offset
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::RESET_STREAM);
-    CHECK(frame1->size() == 15);
+    CHECK(frame1->size() == 14);
     const QUICRstStreamFrame *rst_stream_frame1 = static_cast<const QUICRstStreamFrame *>(frame1);
     CHECK(rst_stream_frame1 != nullptr);
     CHECK(rst_stream_frame1->error_code() == 0x0001);
@@ -710,9 +729,9 @@
     uint8_t buf1[] = {
       0x04,                   // Type
       0x92, 0x34, 0x56, 0x78, // Stream ID
-      0x00, 0x01,             // Error Code
+      0x01,                   // Error Code
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::RESET_STREAM);
     CHECK(frame1->valid() == false);
   }
@@ -721,19 +740,23 @@
 TEST_CASE("Store RESET_STREAM Frame", "[quic]")
 {
   uint8_t buf[65535];
-  size_t len;
+  size_t len = 0;
 
   uint8_t expected[] = {
     0x04,                                          // Type
     0x92, 0x34, 0x56, 0x78,                        // Stream ID
-    0x00, 0x01,                                    // Error Code
+    0x01,                                          // Error Code
     0xd1, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 // Final Offset
   };
   QUICRstStreamFrame rst_stream_frame(0x12345678, 0x0001, 0x1122334455667788);
-  CHECK(rst_stream_frame.size() == 15);
+  CHECK(rst_stream_frame.size() == 14);
 
-  rst_stream_frame.store(buf, &len, 65535);
-  CHECK(len == 15);
+  Ptr<IOBufferBlock> ibb = rst_stream_frame.to_io_buffer_block(sizeof(buf));
+  for (auto b = ibb; b; b = b->next) {
+    memcpy(buf + len, b->start(), b->size());
+    len += b->size();
+  }
+  CHECK(len == 14);
   CHECK(memcmp(buf, expected, len) == 0);
 }
 
@@ -743,7 +766,7 @@
   uint8_t buf[] = {
     0x01, // Type
   };
-  const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf));
+  const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf), nullptr);
   CHECK(frame->type() == QUICFrameType::PING);
   CHECK(frame->size() == 1);
 
@@ -754,7 +777,7 @@
 TEST_CASE("Store Ping Frame", "[quic]")
 {
   uint8_t buf[16];
-  size_t len;
+  size_t len = 0;
 
   uint8_t expected[] = {
     0x01, // Type
@@ -763,7 +786,11 @@
   QUICPingFrame frame;
   CHECK(frame.size() == 1);
 
-  frame.store(buf, &len, 16);
+  Ptr<IOBufferBlock> ibb = frame.to_io_buffer_block(sizeof(buf));
+  for (auto b = ibb; b; b = b->next) {
+    memcpy(buf + len, b->start(), b->size());
+    len += b->size();
+  }
   CHECK(len == 1);
   CHECK(memcmp(buf, expected, len) == 0);
 }
@@ -772,11 +799,11 @@
 {
   uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE];
   uint8_t buf1[] = {
-    0x00, // Type
+    0x00, 0x00, 0x00 // Type
   };
-  const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+  const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
   CHECK(frame1->type() == QUICFrameType::PADDING);
-  CHECK(frame1->size() == 1);
+  CHECK(frame1->size() == 3);
   const QUICPaddingFrame *paddingFrame1 = static_cast<const QUICPaddingFrame *>(frame1);
   CHECK(paddingFrame1 != nullptr);
 }
@@ -784,14 +811,18 @@
 TEST_CASE("Store Padding Frame", "[quic]")
 {
   uint8_t buf[65535];
-  size_t len;
+  size_t len = 0;
 
   uint8_t expected[] = {
-    0x00, // Type
+    0x00, 0x00, 0x00, // Type
   };
-  QUICPaddingFrame padding_frame;
-  padding_frame.store(buf, &len, 65535);
-  CHECK(len == 1);
+  QUICPaddingFrame padding_frame(3);
+  Ptr<IOBufferBlock> ibb = padding_frame.to_io_buffer_block(sizeof(buf));
+  for (auto b = ibb; b; b = b->next) {
+    memcpy(buf + len, b->start(), b->size());
+    len += b->size();
+  }
+  CHECK(len == 3);
   CHECK(memcmp(buf, expected, len) == 0);
 }
 
@@ -805,13 +836,13 @@
   {
     uint8_t buf[] = {
       0x1c,                        // Type
-      0x00, 0x0A,                  // Error Code
+      0x0A,                        // Error Code
       0x00,                        // Frame Type
       0x05,                        // Reason Phrase Length
       0x41, 0x42, 0x43, 0x44, 0x45 // Reason Phrase ("ABCDE");
     };
 
-    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf));
+    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf), nullptr);
     CHECK(frame->type() == QUICFrameType::CONNECTION_CLOSE);
     CHECK(frame->size() == sizeof(buf));
 
@@ -826,13 +857,13 @@
   SECTION("Bad loading")
   {
     uint8_t buf[] = {
-      0x1c,       // Type
-      0x00, 0x0A, // Error Code
-      0x00,       // Frame Type
-      0x05,       // Reason Phrase Length
+      0x1c, // Type
+      0x0A, // Error Code
+      0x00, // Frame Type
+      0x05, // Reason Phrase Length
     };
 
-    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf));
+    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf), nullptr);
     CHECK(frame->type() == QUICFrameType::CONNECTION_CLOSE);
     CHECK(frame->valid() == false);
   }
@@ -840,12 +871,12 @@
   SECTION("loading w/o reason phrase")
   {
     uint8_t buf[] = {
-      0x1c,       // Type
-      0x00, 0x0A, // Error Code
-      0x04,       // Frame Type
-      0x00,       // Reason Phrase Length
+      0x1c, // Type
+      0x0A, // Error Code
+      0x04, // Frame Type
+      0x00, // Reason Phrase Length
     };
-    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf));
+    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf), nullptr);
     CHECK(frame->type() == QUICFrameType::CONNECTION_CLOSE);
     CHECK(frame->size() == sizeof(buf));
 
@@ -859,11 +890,11 @@
   SECTION("storing w/ reason phrase")
   {
     uint8_t buf[32];
-    size_t len;
+    size_t len = 0;
 
     uint8_t expected[] = {
       0x1c,                        // Type
-      0x00, 0x0A,                  // Error Code
+      0x0A,                        // Error Code
       0x08,                        // Frame Type
       0x05,                        // Reason Phrase Length
       0x41, 0x42, 0x43, 0x44, 0x45 // Reason Phrase ("ABCDE");
@@ -872,7 +903,11 @@
                                                     QUICFrameType::STREAM, 5, "ABCDE");
     CHECK(connection_close_frame.size() == sizeof(expected));
 
-    connection_close_frame.store(buf, &len, 32);
+    Ptr<IOBufferBlock> ibb = connection_close_frame.to_io_buffer_block(sizeof(buf));
+    for (auto b = ibb; b; b = b->next) {
+      memcpy(buf + len, b->start(), b->size());
+      len += b->size();
+    }
     CHECK(len == sizeof(expected));
     CHECK(memcmp(buf, expected, len) == 0);
   }
@@ -880,17 +915,21 @@
   SECTION("storing w/o reason phrase")
   {
     uint8_t buf[32];
-    size_t len;
+    size_t len = 0;
 
     uint8_t expected[] = {
-      0x1c,       // Type
-      0x00, 0x0A, // Error Code
-      0x00,       // Frame Type
-      0x00,       // Reason Phrase Length
+      0x1c, // Type
+      0x0A, // Error Code
+      0x00, // Frame Type
+      0x00, // Reason Phrase Length
     };
     QUICConnectionCloseFrame connection_close_frame(static_cast<uint16_t>(QUICTransErrorCode::PROTOCOL_VIOLATION),
                                                     QUICFrameType::UNKNOWN, 0, nullptr);
-    connection_close_frame.store(buf, &len, 32);
+    Ptr<IOBufferBlock> ibb = connection_close_frame.to_io_buffer_block(sizeof(buf));
+    for (auto b = ibb; b; b = b->next) {
+      memcpy(buf + len, b->start(), b->size());
+      len += b->size();
+    }
     CHECK(len == sizeof(expected));
     CHECK(memcmp(buf, expected, len) == 0);
   }
@@ -905,7 +944,7 @@
       0x10,                                          // Type
       0xd1, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 // Maximum Data
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::MAX_DATA);
     CHECK(frame1->size() == 9);
     const QUICMaxDataFrame *max_data_frame = static_cast<const QUICMaxDataFrame *>(frame1);
@@ -918,7 +957,7 @@
     uint8_t buf1[] = {
       0x10, // Type
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::MAX_DATA);
     CHECK(frame1->valid() == false);
   }
@@ -927,7 +966,7 @@
 TEST_CASE("Store MaxData Frame", "[quic]")
 {
   uint8_t buf[65535];
-  size_t len;
+  size_t len = 0;
 
   uint8_t expected[] = {
     0x10,                                          // Type
@@ -936,7 +975,11 @@
   QUICMaxDataFrame max_data_frame(0x1122334455667788, 0, nullptr);
   CHECK(max_data_frame.size() == 9);
 
-  max_data_frame.store(buf, &len, 65535);
+  Ptr<IOBufferBlock> ibb = max_data_frame.to_io_buffer_block(sizeof(buf));
+  for (auto b = ibb; b; b = b->next) {
+    memcpy(buf + len, b->start(), b->size());
+    len += b->size();
+  }
   CHECK(len == 9);
   CHECK(memcmp(buf, expected, len) == 0);
 }
@@ -951,7 +994,7 @@
       0x81, 0x02, 0x03, 0x04,                        // Stream ID
       0xd1, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 // Maximum Stream Data
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::MAX_STREAM_DATA);
     CHECK(frame1->size() == 13);
     const QUICMaxStreamDataFrame *maxStreamDataFrame1 = static_cast<const QUICMaxStreamDataFrame *>(frame1);
@@ -966,7 +1009,7 @@
       0x11,                   // Type
       0x81, 0x02, 0x03, 0x04, // Stream ID
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::MAX_STREAM_DATA);
     CHECK(frame1->valid() == false);
   }
@@ -975,7 +1018,7 @@
 TEST_CASE("Store MaxStreamData Frame", "[quic]")
 {
   uint8_t buf[65535];
-  size_t len;
+  size_t len = 0;
 
   uint8_t expected[] = {
     0x11,                                          // Type
@@ -985,7 +1028,11 @@
   QUICMaxStreamDataFrame max_stream_data_frame(0x01020304, 0x1122334455667788ULL);
   CHECK(max_stream_data_frame.size() == 13);
 
-  max_stream_data_frame.store(buf, &len, 65535);
+  Ptr<IOBufferBlock> ibb = max_stream_data_frame.to_io_buffer_block(sizeof(buf));
+  for (auto b = ibb; b; b = b->next) {
+    memcpy(buf + len, b->start(), b->size());
+    len += b->size();
+  }
   CHECK(len == 13);
   CHECK(memcmp(buf, expected, len) == 0);
 }
@@ -999,7 +1046,7 @@
       0x12,                   // Type
       0x81, 0x02, 0x03, 0x04, // Stream ID
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::MAX_STREAMS);
     CHECK(frame1->size() == 5);
     const QUICMaxStreamsFrame *max_streams_frame = static_cast<const QUICMaxStreamsFrame *>(frame1);
@@ -1011,7 +1058,7 @@
     uint8_t buf1[] = {
       0x12, // Type
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::MAX_STREAMS);
     CHECK(frame1->valid() == false);
   }
@@ -1020,7 +1067,7 @@
 TEST_CASE("Store MaxStreams Frame", "[quic]")
 {
   uint8_t buf[65535];
-  size_t len;
+  size_t len = 0;
 
   uint8_t expected[] = {
     0x12,                   // Type
@@ -1029,7 +1076,11 @@
   QUICMaxStreamsFrame max_streams_frame(0x01020304, 0, nullptr);
   CHECK(max_streams_frame.size() == 5);
 
-  max_streams_frame.store(buf, &len, 65535);
+  Ptr<IOBufferBlock> ibb = max_streams_frame.to_io_buffer_block(sizeof(buf));
+  for (auto b = ibb; b; b = b->next) {
+    memcpy(buf + len, b->start(), b->size());
+    len += b->size();
+  }
   CHECK(len == 5);
   CHECK(memcmp(buf, expected, len) == 0);
 }
@@ -1043,7 +1094,7 @@
       0x14, // Type
       0x07, // Offset
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::DATA_BLOCKED);
     CHECK(frame1->size() == 2);
     const QUICDataBlockedFrame *blocked_stream_frame = static_cast<const QUICDataBlockedFrame *>(frame1);
@@ -1056,7 +1107,7 @@
     uint8_t buf1[] = {
       0x14, // Type
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::DATA_BLOCKED);
     CHECK(frame1->valid() == false);
   }
@@ -1065,7 +1116,7 @@
 TEST_CASE("Store DataBlocked Frame", "[quic]")
 {
   uint8_t buf[65535];
-  size_t len;
+  size_t len = 0;
 
   uint8_t expected[] = {
     0x14, // Type
@@ -1074,7 +1125,11 @@
   QUICDataBlockedFrame blocked_stream_frame(0x07, 0, nullptr);
   CHECK(blocked_stream_frame.size() == 2);
 
-  blocked_stream_frame.store(buf, &len, 65535);
+  Ptr<IOBufferBlock> ibb = blocked_stream_frame.to_io_buffer_block(sizeof(buf));
+  for (auto b = ibb; b; b = b->next) {
+    memcpy(buf + len, b->start(), b->size());
+    len += b->size();
+  }
   CHECK(len == 2);
   CHECK(memcmp(buf, expected, len) == 0);
 }
@@ -1089,7 +1144,7 @@
       0x81, 0x02, 0x03, 0x04, // Stream ID
       0x07,                   // Offset
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::STREAM_DATA_BLOCKED);
     CHECK(frame1->size() == 6);
     const QUICStreamDataBlockedFrame *stream_blocked_frame = static_cast<const QUICStreamDataBlockedFrame *>(frame1);
@@ -1104,7 +1159,7 @@
       0x15,                   // Type
       0x81, 0x02, 0x03, 0x04, // Stream ID
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::STREAM_DATA_BLOCKED);
     CHECK(frame1->valid() == false);
   }
@@ -1113,7 +1168,7 @@
 TEST_CASE("Store StreamDataBlocked Frame", "[quic]")
 {
   uint8_t buf[65535];
-  size_t len;
+  size_t len = 0;
 
   uint8_t expected[] = {
     0x15,                   // Type
@@ -1123,7 +1178,11 @@
   QUICStreamDataBlockedFrame stream_blocked_frame(0x01020304, 0x07);
   CHECK(stream_blocked_frame.size() == 6);
 
-  stream_blocked_frame.store(buf, &len, 65535);
+  Ptr<IOBufferBlock> ibb = stream_blocked_frame.to_io_buffer_block(sizeof(buf));
+  for (auto b = ibb; b; b = b->next) {
+    memcpy(buf + len, b->start(), b->size());
+    len += b->size();
+  }
   CHECK(len == 6);
   CHECK(memcmp(buf, expected, len) == 0);
 }
@@ -1137,7 +1196,7 @@
       0x16,       // Type
       0x41, 0x02, // Stream ID
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::STREAMS_BLOCKED);
     CHECK(frame1->size() == 3);
     const QUICStreamIdBlockedFrame *stream_id_blocked_frame = static_cast<const QUICStreamIdBlockedFrame *>(frame1);
@@ -1150,7 +1209,7 @@
     uint8_t buf1[] = {
       0x16, // Type
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
     CHECK(frame1->type() == QUICFrameType::STREAMS_BLOCKED);
     CHECK(frame1->valid() == false);
   }
@@ -1159,7 +1218,7 @@
 TEST_CASE("Store StreamsBlocked Frame", "[quic]")
 {
   uint8_t buf[65535];
-  size_t len;
+  size_t len = 0;
 
   uint8_t expected[] = {
     0x16,       // Type
@@ -1168,7 +1227,11 @@
   QUICStreamIdBlockedFrame stream_id_blocked_frame(0x0102, 0, nullptr);
   CHECK(stream_id_blocked_frame.size() == 3);
 
-  stream_id_blocked_frame.store(buf, &len, 65535);
+  Ptr<IOBufferBlock> ibb = stream_id_blocked_frame.to_io_buffer_block(sizeof(buf));
+  for (auto b = ibb; b; b = b->next) {
+    memcpy(buf + len, b->start(), b->size());
+    len += b->size();
+  }
   CHECK(len == 3);
   CHECK(memcmp(buf, expected, len) == 0);
 }
@@ -1176,61 +1239,74 @@
 TEST_CASE("Load NewConnectionId Frame", "[quic]")
 {
   uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE];
+
   SECTION("load")
   {
-    uint8_t buf1[] = {
+    uint8_t buf[] = {
       0x18,                                           // Type
       0x41, 0x02,                                     // Sequence
+      0x41, 0x00,                                     // Retire Prior To
       0x08,                                           // Length
       0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // Connection ID
       0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, // Stateless Reset Token
       0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0,
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
-    CHECK(frame1->type() == QUICFrameType::NEW_CONNECTION_ID);
-    CHECK(frame1->size() == 28);
-    const QUICNewConnectionIdFrame *new_con_id_frame = static_cast<const QUICNewConnectionIdFrame *>(frame1);
+    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf), nullptr);
+    CHECK(frame->type() == QUICFrameType::NEW_CONNECTION_ID);
+    CHECK(frame->size() == sizeof(buf));
+    CHECK(frame->valid() == true);
+
+    const QUICNewConnectionIdFrame *new_con_id_frame = static_cast<const QUICNewConnectionIdFrame *>(frame);
     CHECK(new_con_id_frame != nullptr);
     CHECK(new_con_id_frame->sequence() == 0x0102);
+    CHECK(new_con_id_frame->retire_prior_to() == 0x0100);
     CHECK((new_con_id_frame->connection_id() ==
            QUICConnectionId(reinterpret_cast<const uint8_t *>("\x11\x22\x33\x44\x55\x66\x77\x88"), 8)));
-    CHECK(memcmp(new_con_id_frame->stateless_reset_token().buf(), buf1 + 12, 16) == 0);
+    CHECK(memcmp(new_con_id_frame->stateless_reset_token().buf(), buf + sizeof(buf) - QUICStatelessResetToken::LEN,
+                 QUICStatelessResetToken::LEN) == 0);
   }
 
   SECTION("Bad Load")
   {
-    uint8_t buf1[] = {
+    uint8_t buf[] = {
       0x18,                                           // Type
       0x41, 0x02,                                     // Sequence
+      0x41, 0x00,                                     // Retire Prior To
       0x08,                                           // Length
       0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // Connection ID
       0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, // Stateless Reset Token
     };
-    const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
-    CHECK(frame1->type() == QUICFrameType::NEW_CONNECTION_ID);
-    CHECK(frame1->valid() == false);
+    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf), nullptr);
+    CHECK(frame->type() == QUICFrameType::NEW_CONNECTION_ID);
+    CHECK(frame->valid() == false);
   }
 }
 
 TEST_CASE("Store NewConnectionId Frame", "[quic]")
 {
   uint8_t buf[32];
-  size_t len;
+  size_t len = 0;
 
   uint8_t expected[] = {
     0x18,                                           // Type
-    0x41, 0x02,                                     // Sequence
+    0x41, 0x02,                                     // Sequence Number
+    0x41, 0x00,                                     // Retire Prior To
     0x08,                                           // Length
     0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // Connection ID
     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // Stateless Reset Token
     0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
   };
-  QUICNewConnectionIdFrame new_con_id_frame(0x0102, {reinterpret_cast<const uint8_t *>("\x11\x22\x33\x44\x55\x66\x77\x88"), 8},
-                                            {expected + 12});
-  CHECK(new_con_id_frame.size() == 28);
+  QUICNewConnectionIdFrame new_con_id_frame(0x0102, 0x0100,
+                                            {reinterpret_cast<const uint8_t *>("\x11\x22\x33\x44\x55\x66\x77\x88"), 8},
+                                            {expected + sizeof(expected) - QUICStatelessResetToken::LEN});
+  CHECK(new_con_id_frame.size() == sizeof(expected));
 
-  new_con_id_frame.store(buf, &len, 32);
-  CHECK(len == 28);
+  Ptr<IOBufferBlock> ibb = new_con_id_frame.to_io_buffer_block(sizeof(buf));
+  for (auto b = ibb; b; b = b->next) {
+    memcpy(buf + len, b->start(), b->size());
+    len += b->size();
+  }
+  CHECK(len == sizeof(expected));
   CHECK(memcmp(buf, expected, len) == 0);
 }
 
@@ -1242,16 +1318,16 @@
     uint8_t buf[] = {
       0x05,                   // Type
       0x92, 0x34, 0x56, 0x78, // Stream ID
-      0x00, 0x01,             // Error Code
+      0x01,                   // Error Code
     };
-    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf));
+    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf), nullptr);
     CHECK(frame->type() == QUICFrameType::STOP_SENDING);
-    CHECK(frame->size() == 7);
+    CHECK(frame->size() == 6);
 
     const QUICStopSendingFrame *stop_sending_frame = static_cast<const QUICStopSendingFrame *>(frame);
     CHECK(stop_sending_frame != nullptr);
     CHECK(stop_sending_frame->stream_id() == 0x12345678);
-    CHECK(stop_sending_frame->error_code() == 0x0001);
+    CHECK(stop_sending_frame->error_code() == 0x01);
   }
 
   SECTION("Bad LOAD")
@@ -1260,7 +1336,7 @@
       0x05,                   // Type
       0x92, 0x34, 0x56, 0x78, // Stream ID
     };
-    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf));
+    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf), nullptr);
     CHECK(frame->type() == QUICFrameType::STOP_SENDING);
     CHECK(frame->valid() == false);
   }
@@ -1269,18 +1345,22 @@
 TEST_CASE("Store STOP_SENDING Frame", "[quic]")
 {
   uint8_t buf[65535];
-  size_t len;
+  size_t len = 0;
 
   uint8_t expected[] = {
     0x05,                   // Type
     0x92, 0x34, 0x56, 0x78, // Stream ID
-    0x00, 0x01,             // Error Code
+    0x01,                   // Error Code
   };
   QUICStopSendingFrame stop_sending_frame(0x12345678, static_cast<QUICAppErrorCode>(0x01));
-  CHECK(stop_sending_frame.size() == 7);
+  CHECK(stop_sending_frame.size() == 6);
 
-  stop_sending_frame.store(buf, &len, 65535);
-  CHECK(len == 7);
+  Ptr<IOBufferBlock> ibb = stop_sending_frame.to_io_buffer_block(sizeof(buf));
+  for (auto b = ibb; b; b = b->next) {
+    memcpy(buf + len, b->start(), b->size());
+    len += b->size();
+  }
+  CHECK(len == 6);
   CHECK(memcmp(buf, expected, len) == 0);
 }
 
@@ -1293,13 +1373,14 @@
       0x1a,                                           // Type
       0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, // Data
     };
-    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf));
+    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf), nullptr);
     CHECK(frame->type() == QUICFrameType::PATH_CHALLENGE);
     CHECK(frame->size() == 9);
 
     const QUICPathChallengeFrame *path_challenge_frame = static_cast<const QUICPathChallengeFrame *>(frame);
     CHECK(path_challenge_frame != nullptr);
     CHECK(memcmp(path_challenge_frame->data(), "\x01\x23\x45\x67\x89\xab\xcd\xef", QUICPathChallengeFrame::DATA_LEN) == 0);
+    frame->~QUICFrame();
   }
 
   SECTION("Load")
@@ -1308,16 +1389,17 @@
       0x1a,                                     // Type
       0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xef, // Data
     };
-    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf));
+    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf), nullptr);
     CHECK(frame->type() == QUICFrameType::PATH_CHALLENGE);
     CHECK(frame->valid() == false);
+    frame->~QUICFrame();
   }
 }
 
 TEST_CASE("Store PATH_CHALLENGE Frame", "[quic]")
 {
   uint8_t buf[16];
-  size_t len;
+  size_t len = 0;
 
   uint8_t expected[] = {
     0x1a,                                           // Type
@@ -1332,7 +1414,11 @@
   QUICPathChallengeFrame frame(std::move(data));
   CHECK(frame.size() == 9);
 
-  frame.store(buf, &len, 16);
+  Ptr<IOBufferBlock> ibb = frame.to_io_buffer_block(sizeof(buf));
+  for (auto b = ibb; b; b = b->next) {
+    memcpy(buf + len, b->start(), b->size());
+    len += b->size();
+  }
   CHECK(len == 9);
   CHECK(memcmp(buf, expected, len) == 0);
 }
@@ -1346,13 +1432,14 @@
       0x1b,                                           // Type
       0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, // Data
     };
-    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf));
+    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf), nullptr);
     CHECK(frame->type() == QUICFrameType::PATH_RESPONSE);
     CHECK(frame->size() == 9);
 
     const QUICPathResponseFrame *path_response_frame = static_cast<const QUICPathResponseFrame *>(frame);
     CHECK(path_response_frame != nullptr);
     CHECK(memcmp(path_response_frame->data(), "\x01\x23\x45\x67\x89\xab\xcd\xef", QUICPathResponseFrame::DATA_LEN) == 0);
+    frame->~QUICFrame();
   }
 
   SECTION("Load")
@@ -1361,16 +1448,17 @@
       0x1b,                                     // Type
       0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, // Data
     };
-    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf));
+    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, buf, sizeof(buf), nullptr);
     CHECK(frame->type() == QUICFrameType::PATH_RESPONSE);
     CHECK(frame->valid() == false);
+    frame->~QUICFrame();
   }
 }
 
 TEST_CASE("Store PATH_RESPONSE Frame", "[quic]")
 {
   uint8_t buf[16];
-  size_t len;
+  size_t len = 0;
 
   uint8_t expected[] = {
     0x1b,                                           // Type
@@ -1385,7 +1473,11 @@
   QUICPathResponseFrame frame(std::move(data));
   CHECK(frame.size() == 9);
 
-  frame.store(buf, &len, 16);
+  Ptr<IOBufferBlock> ibb = frame.to_io_buffer_block(sizeof(buf));
+  for (auto b = ibb; b; b = b->next) {
+    memcpy(buf + len, b->start(), b->size());
+    len += b->size();
+  }
   CHECK(len == 9);
   CHECK(memcmp(buf, expected, len) == 0);
 }
@@ -1405,7 +1497,7 @@
 
   SECTION("load")
   {
-    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, raw_new_token_frame, raw_new_token_frame_len);
+    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, raw_new_token_frame, raw_new_token_frame_len, nullptr);
     CHECK(frame->type() == QUICFrameType::NEW_TOKEN);
     CHECK(frame->size() == raw_new_token_frame_len);
 
@@ -1413,19 +1505,21 @@
     CHECK(new_token_frame != nullptr);
     CHECK(new_token_frame->token_length() == raw_token_len);
     CHECK(memcmp(new_token_frame->token(), raw_token, raw_token_len) == 0);
+    frame->~QUICFrame();
   }
 
   SECTION("bad load")
   {
-    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, raw_new_token_frame, raw_new_token_frame_len - 5);
+    const QUICFrame *frame = QUICFrameFactory::create(frame_buf, raw_new_token_frame, raw_new_token_frame_len - 5, nullptr);
     CHECK(frame->type() == QUICFrameType::NEW_TOKEN);
     CHECK(frame->valid() == false);
+    frame->~QUICFrame();
   }
 
   SECTION("store")
   {
     uint8_t buf[32];
-    size_t len;
+    size_t len = 0;
 
     ats_unique_buf token = ats_unique_malloc(raw_token_len);
     memcpy(token.get(), raw_token, raw_token_len);
@@ -1433,7 +1527,11 @@
     QUICNewTokenFrame frame(std::move(token), raw_token_len);
     CHECK(frame.size() == raw_new_token_frame_len);
 
-    frame.store(buf, &len, 16);
+    Ptr<IOBufferBlock> ibb = frame.to_io_buffer_block(sizeof(buf));
+    for (auto b = ibb; b; b = b->next) {
+      memcpy(buf + len, b->start(), b->size());
+      len += b->size();
+    }
     CHECK(len == raw_new_token_frame_len);
     CHECK(memcmp(buf, raw_new_token_frame, len) == 0);
   }
@@ -1452,7 +1550,7 @@
   SECTION("load")
   {
     const QUICFrame *frame =
-      QUICFrameFactory::create(frame_buf, raw_retire_connection_id_frame, raw_retire_connection_id_frame_len);
+      QUICFrameFactory::create(frame_buf, raw_retire_connection_id_frame, raw_retire_connection_id_frame_len, nullptr);
     CHECK(frame->type() == QUICFrameType::RETIRE_CONNECTION_ID);
     CHECK(frame->size() == raw_retire_connection_id_frame_len);
 
@@ -1464,7 +1562,7 @@
   SECTION("bad load")
   {
     const QUICFrame *frame =
-      QUICFrameFactory::create(frame_buf, raw_retire_connection_id_frame, raw_retire_connection_id_frame_len - 1);
+      QUICFrameFactory::create(frame_buf, raw_retire_connection_id_frame, raw_retire_connection_id_frame_len - 1, nullptr);
     CHECK(frame->type() == QUICFrameType::RETIRE_CONNECTION_ID);
     CHECK(frame->valid() == false);
   }
@@ -1472,12 +1570,16 @@
   SECTION("store")
   {
     uint8_t buf[32];
-    size_t len;
+    size_t len = 0;
 
     QUICRetireConnectionIdFrame frame(seq_num, 0, nullptr);
     CHECK(frame.size() == raw_retire_connection_id_frame_len);
 
-    frame.store(buf, &len, 16);
+    Ptr<IOBufferBlock> ibb = frame.to_io_buffer_block(sizeof(buf));
+    for (auto b = ibb; b; b = b->next) {
+      memcpy(buf + len, b->start(), b->size());
+      len += b->size();
+    }
     CHECK(len == raw_retire_connection_id_frame_len);
     CHECK(memcmp(buf, raw_retire_connection_id_frame, len) == 0);
   }
@@ -1489,7 +1591,7 @@
   uint8_t buf1[] = {
     0x20, // Type
   };
-  const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1));
+  const QUICFrame *frame1 = QUICFrameFactory::create(frame_buf, buf1, sizeof(buf1), nullptr);
   CHECK(frame1 == nullptr);
 }
 
@@ -1505,13 +1607,13 @@
     0x12,                   // Type
     0x85, 0x06, 0x07, 0x08, // Stream Data
   };
-  const QUICFrame &frame1 = factory.fast_create(buf1, sizeof(buf1));
+  const QUICFrame &frame1 = factory.fast_create(buf1, sizeof(buf1), nullptr);
   CHECK(frame1.type() == QUICFrameType::MAX_STREAMS);
 
   const QUICMaxStreamsFrame &max_streams_frame1 = static_cast<const QUICMaxStreamsFrame &>(frame1);
   CHECK(max_streams_frame1.maximum_streams() == 0x01020304);
 
-  const QUICFrame &frame2 = factory.fast_create(buf2, sizeof(buf2));
+  const QUICFrame &frame2 = factory.fast_create(buf2, sizeof(buf2), nullptr);
   CHECK(frame2.type() == QUICFrameType::MAX_STREAMS);
 
   const QUICMaxStreamsFrame &max_streams_frame2 = static_cast<const QUICMaxStreamsFrame &>(frame2);
@@ -1527,7 +1629,7 @@
   uint8_t buf1[] = {
     0x20, // Type
   };
-  const QUICFrame &frame1 = factory.fast_create(buf1, sizeof(buf1));
+  const QUICFrame &frame1 = factory.fast_create(buf1, sizeof(buf1), nullptr);
   CHECK(frame1.type() == QUICFrameType::UNKNOWN);
 }
 
diff --git a/iocore/net/quic/test/test_QUICFrameDispatcher.cc b/iocore/net/quic/test/test_QUICFrameDispatcher.cc
index be81c2e..eb89aa7 100644
--- a/iocore/net/quic/test/test_QUICFrameDispatcher.cc
+++ b/iocore/net/quic/test/test_QUICFrameDispatcher.cc
@@ -36,14 +36,12 @@
 
   QUICStreamFrame streamFrame(block, 0x03, 0);
 
-  MockQUICLDConfig ld_config;
-  MockQUICCCConfig cc_config;
+  MockQUICContext context;
+
   MockQUICConnection connection;
-  MockQUICStreamManager streamManager;
+  MockQUICStreamManager streamManager = {&connection};
   MockQUICConnectionInfoProvider info;
-  MockQUICCongestionController cc(&info, cc_config);
-  QUICRTTMeasure rtt_measure;
-  MockQUICLossDetector lossDetector(&info, &cc, &rtt_measure, ld_config);
+  MockQUICLossDetector lossDetector(context);
 
   QUICFrameDispatcher quicFrameDispatcher(&info);
   quicFrameDispatcher.add_handler(&connection);
@@ -64,14 +62,19 @@
   }
   bool should_send_ack;
   bool is_flow_controlled;
-  quicFrameDispatcher.receive_frames(QUICEncryptionLevel::INITIAL, buf, len, should_send_ack, is_flow_controlled, nullptr);
+  quicFrameDispatcher.receive_frames(QUICEncryptionLevel::INITIAL, buf, len, should_send_ack, is_flow_controlled, nullptr, nullptr);
   CHECK(connection.getTotalFrameCount() == 0);
   CHECK(streamManager.getTotalFrameCount() == 1);
 
   // CONNECTION_CLOSE frame
   QUICConnectionCloseFrame connectionCloseFrame(0, 0, "", 0, nullptr);
-  connectionCloseFrame.store(buf, &len, 4096);
-  quicFrameDispatcher.receive_frames(QUICEncryptionLevel::INITIAL, buf, len, should_send_ack, is_flow_controlled, nullptr);
+  ibb = connectionCloseFrame.to_io_buffer_block(sizeof(buf));
+  len = 0;
+  for (auto b = ibb; b; b = b->next) {
+    memcpy(buf + len, b->start(), b->size());
+    len += b->size();
+  }
+  quicFrameDispatcher.receive_frames(QUICEncryptionLevel::INITIAL, buf, len, should_send_ack, is_flow_controlled, nullptr, nullptr);
   CHECK(connection.getTotalFrameCount() == 1);
   CHECK(streamManager.getTotalFrameCount() == 1);
 }
diff --git a/iocore/net/quic/test/test_QUICIncomingFrameBuffer.cc b/iocore/net/quic/test/test_QUICIncomingFrameBuffer.cc
index 50a3dfa..3b4e8a9 100644
--- a/iocore/net/quic/test/test_QUICIncomingFrameBuffer.cc
+++ b/iocore/net/quic/test/test_QUICIncomingFrameBuffer.cc
@@ -71,7 +71,7 @@
     buffer.insert(new QUICStreamFrame(*stream1_frame_2_r));
     err = buffer.insert(new QUICStreamFrame(*stream1_frame_3_r));
     CHECK(err->cls == QUICErrorClass::TRANSPORT);
-    CHECK(err->code == static_cast<uint16_t>(QUICTransErrorCode::FINAL_OFFSET_ERROR));
+    CHECK(err->code == static_cast<uint16_t>(QUICTransErrorCode::FINAL_SIZE_ERROR));
 
     buffer.clear();
 
@@ -82,7 +82,7 @@
     buffer2.insert(new QUICStreamFrame(*stream1_frame_1_r));
     err = buffer2.insert(new QUICStreamFrame(*stream1_frame_2_r));
     CHECK(err->cls == QUICErrorClass::TRANSPORT);
-    CHECK(err->code == static_cast<uint16_t>(QUICTransErrorCode::FINAL_OFFSET_ERROR));
+    CHECK(err->code == static_cast<uint16_t>(QUICTransErrorCode::FINAL_SIZE_ERROR));
 
     buffer2.clear();
 
@@ -91,7 +91,7 @@
     buffer3.insert(new QUICStreamFrame(*stream1_frame_4_r));
     err = buffer3.insert(new QUICStreamFrame(*stream1_frame_3_r));
     CHECK(err->cls == QUICErrorClass::TRANSPORT);
-    CHECK(err->code == static_cast<uint16_t>(QUICTransErrorCode::FINAL_OFFSET_ERROR));
+    CHECK(err->code == static_cast<uint16_t>(QUICTransErrorCode::FINAL_SIZE_ERROR));
 
     buffer3.clear();
   }
@@ -158,14 +158,19 @@
 
   auto frame = static_cast<const QUICStreamFrame *>(buffer.pop());
   CHECK(frame->offset() == 0);
+  delete frame;
   frame = static_cast<const QUICStreamFrame *>(buffer.pop());
   CHECK(frame->offset() == 1024);
+  delete frame;
   frame = static_cast<const QUICStreamFrame *>(buffer.pop());
   CHECK(frame->offset() == 2048);
+  delete frame;
   frame = static_cast<const QUICStreamFrame *>(buffer.pop());
   CHECK(frame->offset() == 3072);
+  delete frame;
   frame = static_cast<const QUICStreamFrame *>(buffer.pop());
   CHECK(frame->offset() == 4096);
+  delete frame;
   CHECK(buffer.empty());
 
   buffer.clear();
@@ -179,14 +184,19 @@
 
   frame = static_cast<const QUICStreamFrame *>(buffer.pop());
   CHECK(frame->offset() == 0);
+  delete frame;
   frame = static_cast<const QUICStreamFrame *>(buffer.pop());
   CHECK(frame->offset() == 1024);
+  delete frame;
   frame = static_cast<const QUICStreamFrame *>(buffer.pop());
   CHECK(frame->offset() == 2048);
+  delete frame;
   frame = static_cast<const QUICStreamFrame *>(buffer.pop());
   CHECK(frame->offset() == 3072);
+  delete frame;
   frame = static_cast<const QUICStreamFrame *>(buffer.pop());
   CHECK(frame->offset() == 4096);
+  delete frame;
   CHECK(buffer.empty());
 
   delete stream;
@@ -228,12 +238,16 @@
 
   auto frame = static_cast<const QUICStreamFrame *>(buffer.pop());
   CHECK(frame->offset() == 0);
+  delete frame;
   frame = static_cast<const QUICStreamFrame *>(buffer.pop());
   CHECK(frame->offset() == 1024);
+  delete frame;
   frame = static_cast<const QUICStreamFrame *>(buffer.pop());
   CHECK(frame->offset() == 2048);
+  delete frame;
   frame = static_cast<const QUICStreamFrame *>(buffer.pop());
   CHECK(frame == nullptr);
+  delete frame;
   CHECK(buffer.empty());
 
   buffer.clear();
@@ -251,10 +265,13 @@
 
   frame = static_cast<const QUICStreamFrame *>(buffer.pop());
   CHECK(frame->offset() == 0);
+  delete frame;
   frame = static_cast<const QUICStreamFrame *>(buffer.pop());
   CHECK(frame->offset() == 1024);
+  delete frame;
   frame = static_cast<const QUICStreamFrame *>(buffer.pop());
   CHECK(frame->offset() == 2048);
+  delete frame;
   frame = static_cast<const QUICStreamFrame *>(buffer.pop());
   CHECK(frame == nullptr);
   CHECK(buffer.empty());
diff --git a/iocore/net/quic/test/test_QUICInvariants.cc b/iocore/net/quic/test/test_QUICInvariants.cc
index 5cdbc28..e57aab1 100644
--- a/iocore/net/quic/test/test_QUICInvariants.cc
+++ b/iocore/net/quic/test/test_QUICInvariants.cc
@@ -37,8 +37,9 @@
     const uint8_t buf[] = {
       0x80,                                           // Long header, Type: NONE
       0x11, 0x22, 0x33, 0x44,                         // Version
-      0x55,                                           // DCIL/SCIL
+      0x08,                                           // DCID Len
       0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Destination Connection ID
+      0x08,                                           // SCID Len
       0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Source Connection ID
     };
     uint64_t buf_len = sizeof(buf);
@@ -53,12 +54,12 @@
     CHECK(version == 0x11223344);
 
     CHECK(QUICInvariants::dcil(dcil, buf, buf_len));
-    CHECK(dcil == 5);
+    CHECK(dcil == sizeof(raw_dcid));
     CHECK(QUICInvariants::dcid(dcid, buf, buf_len));
     CHECK(dcid == expected_dcid);
 
     CHECK(QUICInvariants::scil(scil, buf, buf_len));
-    CHECK(scil == 5);
+    CHECK(scil == sizeof(raw_scid));
     CHECK(QUICInvariants::scid(scid, buf, buf_len));
     CHECK(scid == expected_scid);
   }
@@ -68,7 +69,8 @@
     const uint8_t buf[] = {
       0x80,                                           // Long header, Type: NONE
       0x11, 0x22, 0x33, 0x44,                         // Version
-      0x05,                                           // DCIL/SCIL
+      0x00,                                           // DCID Len
+      0x08,                                           // SCID Len
       0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Source Connection ID
     };
     uint64_t buf_len = sizeof(buf);
@@ -88,7 +90,7 @@
     CHECK(dcid == QUICConnectionId::ZERO());
 
     CHECK(QUICInvariants::scil(scil, buf, buf_len));
-    CHECK(scil == 5);
+    CHECK(scil == sizeof(raw_scid));
     CHECK(QUICInvariants::scid(scid, buf, buf_len));
     CHECK(scid == expected_scid);
   }
@@ -98,8 +100,9 @@
     const uint8_t buf[] = {
       0x80,                                           // Long header, Type: NONE
       0x11, 0x22, 0x33, 0x44,                         // Version
-      0x50,                                           // DCIL/SCIL
+      0x08,                                           // DCID Len
       0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Destination Connection ID
+      0x00,                                           // SCID Len
     };
     uint64_t buf_len = sizeof(buf);
 
@@ -113,7 +116,7 @@
     CHECK(version == 0x11223344);
 
     CHECK(QUICInvariants::dcil(dcil, buf, buf_len));
-    CHECK(dcil == 5);
+    CHECK(dcil == sizeof(raw_dcid));
     CHECK(QUICInvariants::dcid(dcid, buf, buf_len));
     CHECK(dcid == expected_dcid);
 
@@ -143,8 +146,9 @@
     const uint8_t buf[] = {
       0x80,                   // Long header, Type: NONE
       0x11, 0x22, 0x33, 0x44, // Version
-      0x55,                   // DCIL/SCIL
+      0x08,                   // DCID Len
       0x01, 0x02, 0x03, 0x04, // Invalid Destination Connection ID
+      0x00,                   // SCID Len
     };
     uint64_t buf_len = sizeof(buf);
 
@@ -164,8 +168,9 @@
     const uint8_t buf[] = {
       0x80,                                           // Long header, Type: NONE
       0x11, 0x22, 0x33, 0x44,                         // Version
-      0x55,                                           // DCIL/SCIL
+      0x08,                                           // DCID Len
       0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Destination Connection ID
+      0x08,                                           // SCID Len
       0x11, 0x12, 0x13, 0x14,                         // Invalid Source Connection ID
     };
     uint64_t buf_len = sizeof(buf);
diff --git a/iocore/net/quic/test/test_QUICKeyGenerator.cc b/iocore/net/quic/test/test_QUICKeyGenerator.cc
index e24d3db..4c1858a 100644
--- a/iocore/net/quic/test/test_QUICKeyGenerator.cc
+++ b/iocore/net/quic/test/test_QUICKeyGenerator.cc
@@ -36,7 +36,7 @@
 #include "QUICPacketProtectionKeyInfo.h"
 
 // https://github.com/quicwg/base-drafts/wiki/Test-Vector-for-the-Clear-Text-AEAD-key-derivation
-TEST_CASE("draft-17 Test Vectors", "[quic]")
+TEST_CASE("draft-23 Test Vectors", "[quic]")
 {
   SECTION("CLIENT Initial")
   {
@@ -44,15 +44,9 @@
 
     QUICConnectionId cid = {reinterpret_cast<const uint8_t *>("\xc6\x54\xef\xd8\xa3\x1b\x47\x92"), 8};
 
-    uint8_t expected_client_key[] = {
-      0x86, 0xd1, 0x83, 0x04, 0x80, 0xb4, 0x0f, 0x86, 0xcf, 0x9d, 0x68, 0xdc, 0xad, 0xf3, 0x5d, 0xfe,
-    };
-    uint8_t expected_client_iv[] = {
-      0x12, 0xf3, 0x93, 0x8a, 0xca, 0x34, 0xaa, 0x02, 0x54, 0x31, 0x63, 0xd4,
-    };
-    uint8_t expected_client_hp[] = {
-      0xcd, 0x25, 0x3a, 0x36, 0xff, 0x93, 0x93, 0x7c, 0x46, 0x93, 0x84, 0xa8, 0x23, 0xaf, 0x6c, 0x56,
-    };
+    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};
 
     QUICPacketProtectionKeyInfo pp_key_info;
     pp_key_info.set_cipher_initial(EVP_aes_128_gcm());
@@ -74,15 +68,10 @@
 
     QUICConnectionId cid = {reinterpret_cast<const uint8_t *>("\xc6\x54\xef\xd8\xa3\x1b\x47\x92"), 8};
 
-    uint8_t expected_server_key[] = {
-      0x2c, 0x78, 0x63, 0x3e, 0x20, 0x6e, 0x99, 0xad, 0x25, 0x19, 0x64, 0xf1, 0x9f, 0x6d, 0xcd, 0x6d,
-    };
-    uint8_t expected_server_iv[] = {
-      0x7b, 0x50, 0xbf, 0x36, 0x98, 0xa0, 0x6d, 0xfa, 0xbf, 0x75, 0xf2, 0x87,
-    };
-    uint8_t expected_server_hp[] = {
-      0x25, 0x79, 0xd8, 0x69, 0x6f, 0x85, 0xed, 0xa6, 0x8d, 0x35, 0x02, 0xb6, 0x55, 0x96, 0x58, 0x6b,
-    };
+    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};
 
     QUICPacketProtectionKeyInfo pp_key_info;
     pp_key_info.set_cipher_initial(EVP_aes_128_gcm());
diff --git a/iocore/net/quic/test/test_QUICLossDetector.cc b/iocore/net/quic/test/test_QUICLossDetector.cc
index 0016724..6c3604f 100644
--- a/iocore/net/quic/test/test_QUICLossDetector.cc
+++ b/iocore/net/quic/test/test_QUICLossDetector.cc
@@ -38,11 +38,11 @@
 
   QUICAckFrameManager afm;
   QUICConnectionId connection_id = {reinterpret_cast<const uint8_t *>("\x01"), 1};
-  MockQUICCCConfig cc_config;
-  MockQUICLDConfig ld_config;
-  MockQUICConnectionInfoProvider info;
-  MockQUICCongestionController cc(&info, cc_config);
-  QUICLossDetector detector(&info, &cc, &rtt_measure, ld_config);
+  MockQUICContext context;
+  QUICPinger pinger;
+  QUICPadder padder(NetVConnectionContext_t::NET_VCONNECTION_IN);
+  MockQUICCongestionController cc;
+  QUICLossDetector detector(context, &cc, &rtt_measure, &pinger, &padder);
   ats_unique_buf payload = ats_unique_malloc(512);
   size_t payload_len     = 512;
   QUICPacketUPtr packet  = QUICPacketFactory::create_null_packet();
@@ -54,11 +54,16 @@
     // Check initial state
     uint8_t frame_buffer[1024] = {0};
     CHECK(g.lost_frame_count == 0);
-    QUICFrame *ping_frame = g.generate_frame(frame_buffer, QUICEncryptionLevel::HANDSHAKE, 4, UINT16_MAX, 0);
+    QUICFrame *ping_frame = g.generate_frame(frame_buffer, QUICEncryptionLevel::HANDSHAKE, 4, UINT16_MAX, 0, 0);
 
     uint8_t raw[4];
-    size_t len;
-    CHECK(ping_frame->store(raw, &len, 10240) < 4);
+    size_t len             = 0;
+    Ptr<IOBufferBlock> ibb = ping_frame->to_io_buffer_block(sizeof(raw));
+    for (auto b = ibb; b; b = b->next) {
+      memcpy(raw + len, b->start(), b->size());
+      len += b->size();
+    }
+    CHECK(len < 4);
 
     // Send SERVER_CLEARTEXT (Handshake message)
     ats_unique_buf payload = ats_unique_malloc(sizeof(raw));
@@ -245,7 +250,7 @@
     afm.update(level, pn9, payload_len, false);
     afm.update(level, pn10, payload_len, false);
     uint8_t buf[QUICFrame::MAX_INSTANCE_SIZE];
-    QUICFrame *x = afm.generate_frame(buf, level, 2048, 2048, 0);
+    QUICFrame *x = afm.generate_frame(buf, level, 2048, 2048, 0, 0);
     frame        = static_cast<QUICAckFrame *>(x);
     ink_hrtime_sleep(HRTIME_MSECONDS(1000));
     detector.handle_frame(level, *frame);
@@ -263,18 +268,19 @@
     CHECK(cc.lost_packets.find(pn8) == cc.lost_packets.end());
     CHECK(cc.lost_packets.find(pn9) == cc.lost_packets.end());
     CHECK(cc.lost_packets.find(pn9) == cc.lost_packets.end());
+    x->~QUICFrame();
   }
 }
 
 TEST_CASE("QUICLossDetector_HugeGap", "[quic]")
 {
   uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE];
-  MockQUICConnectionInfoProvider info;
-  MockQUICCCConfig cc_config;
-  MockQUICLDConfig ld_config;
-  MockQUICCongestionController cc(&info, cc_config);
   QUICRTTMeasure rtt_measure;
-  QUICLossDetector detector(&info, &cc, &rtt_measure, ld_config);
+  MockQUICContext context;
+  QUICPinger pinger;
+  QUICPadder padder(NetVConnectionContext_t::NET_VCONNECTION_IN);
+  MockQUICCongestionController cc;
+  QUICLossDetector detector(context, &cc, &rtt_measure, &pinger, &padder);
 
   auto t1           = Thread::get_hrtime();
   QUICAckFrame *ack = QUICFrameFactory::create_ack_frame(frame_buf, 100000000, 100, 10000000);
@@ -282,4 +288,5 @@
   detector.handle_frame(QUICEncryptionLevel::INITIAL, *ack);
   auto t2 = Thread::get_hrtime();
   CHECK(t2 - t1 < HRTIME_MSECONDS(100));
+  ack->~QUICAckFrame();
 }
diff --git a/iocore/net/quic/test/test_QUICPacket.cc b/iocore/net/quic/test/test_QUICPacket.cc
index f50d156..573e1a8 100644
--- a/iocore/net/quic/test/test_QUICPacket.cc
+++ b/iocore/net/quic/test/test_QUICPacket.cc
@@ -32,8 +32,9 @@
     const uint8_t input[] = {
       0xc0,                                           // Long header, Type: NONE
       0x00, 0x00, 0x00, 0x00,                         // Version
-      0x55,                                           // DCIL/SCIL
+      0x08,                                           // DCID Len
       0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Destination Connection ID
+      0x08,                                           // SCID Len
       0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Source Connection ID
       0x00, 0x00, 0x00, 0x08,                         // Supported Version 1
       0x00, 0x00, 0x00, 0x09,                         // Supported Version 1
@@ -41,9 +42,9 @@
     ats_unique_buf uinput = ats_unique_malloc(sizeof(input));
     memcpy(uinput.get(), input, sizeof(input));
 
-    QUICPacketHeaderUPtr header = QUICPacketHeader::load({}, std::move(uinput), sizeof(input), 0);
-    CHECK(header->size() == 22);
-    CHECK(header->packet_size() == 30);
+    QUICPacketHeaderUPtr header = QUICPacketHeader::load({}, {}, std::move(uinput), sizeof(input), 0);
+    CHECK(header->size() == sizeof(input) - 8);
+    CHECK(header->packet_size() == sizeof(input));
     CHECK(header->type() == QUICPacketType::VERSION_NEGOTIATION);
     CHECK(
       (header->destination_cid() == QUICConnectionId(reinterpret_cast<const uint8_t *>("\x01\x02\x03\x04\x05\x06\x07\x08"), 8)));
@@ -57,18 +58,19 @@
     const uint8_t input[] = {
       0xc3,                                           // Long header, Type: INITIAL
       0x11, 0x22, 0x33, 0x44,                         // Version
-      0x55,                                           // DCIL/SCIL
+      0x08,                                           // DCID Len
       0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Destination Connection ID
+      0x08,                                           // SCID Len
       0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Source Connection ID
       0x00,                                           // Token Length (i), Token (*)
-      0x02,                                           // Payload length
+      0x06,                                           // Length
       0x01, 0x23, 0x45, 0x67,                         // Packet number
       0xff, 0xff,                                     // Payload (dummy)
     };
     ats_unique_buf uinput = ats_unique_malloc(sizeof(input));
     memcpy(uinput.get(), input, sizeof(input));
 
-    QUICPacketHeaderUPtr header = QUICPacketHeader::load({}, std::move(uinput), sizeof(input), 0);
+    QUICPacketHeaderUPtr header = QUICPacketHeader::load({}, {}, std::move(uinput), sizeof(input), 0);
     CHECK(header->size() == sizeof(input) - 2); // Packet Length - Payload Length
     CHECK(header->packet_size() == sizeof(input));
     CHECK(header->type() == QUICPacketType::INITIAL);
@@ -85,9 +87,11 @@
     const uint8_t input[] = {
       0xf5,                                           // Long header, Type: RETRY
       0x11, 0x22, 0x33, 0x44,                         // Version
-      0x55,                                           // DCIL/SCIL
+      0x08,                                           // DCID Len
       0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Destination Connection ID
+      0x08,                                           // SCID Len
       0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Source Connection ID
+      0x08,                                           // ODCID Len
       0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, // Original Destination Connection ID
       0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, // Retry Token
       0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0,
@@ -97,7 +101,7 @@
 
     const uint8_t retry_token[] = {0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0};
 
-    QUICPacketHeaderUPtr header = QUICPacketHeader::load({}, std::move(uinput), sizeof(input), 0);
+    QUICPacketHeaderUPtr header = QUICPacketHeader::load({}, {}, std::move(uinput), sizeof(input), 0);
     CHECK(header->size() == sizeof(input) - 16); // Packet Length - Payload Length (Retry Token)
     CHECK(header->packet_size() == sizeof(input));
     CHECK(header->type() == QUICPacketType::RETRY);
@@ -114,6 +118,62 @@
     CHECK(header->version() == 0x11223344);
   }
 
+  SECTION("Long Header (parse) INITIAL Packet")
+  {
+    const uint8_t buf[] = {
+      0xc3,                                           // Long header, Type: INITIAL
+      0x11, 0x22, 0x33, 0x44,                         // Version
+      0x08,                                           // DCID Len
+      0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Destination Connection ID
+      0x08,                                           // SCID Len
+      0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Source Connection ID
+      0x00,                                           // Token Length (i), Token (*)
+      0x06,                                           // Length
+      0x01, 0x23, 0x45, 0x67,                         // Packet number
+      0xff, 0xff,                                     // Payload (dummy)
+    };
+
+    QUICPacketType type;
+    REQUIRE(QUICPacketLongHeader::type(type, buf, sizeof(buf)));
+    CHECK(type == QUICPacketType::INITIAL);
+
+    QUICVersion version;
+    REQUIRE(QUICPacketLongHeader::version(version, buf, sizeof(buf)));
+    CHECK(version == 0x11223344);
+
+    uint8_t dcil;
+    REQUIRE(QUICPacketLongHeader::dcil(dcil, buf, sizeof(buf)));
+    CHECK(dcil == 8);
+
+    uint8_t scil;
+    REQUIRE(QUICPacketLongHeader::scil(scil, buf, sizeof(buf)));
+    CHECK(dcil == 8);
+
+    size_t token_length;
+    uint8_t token_length_field_len;
+    size_t token_length_field_offset;
+    REQUIRE(QUICPacketLongHeader::token_length(token_length, token_length_field_len, token_length_field_offset, buf, sizeof(buf)));
+    CHECK(token_length == 0);
+    CHECK(token_length_field_len == 1);
+    CHECK(token_length_field_offset == 23);
+
+    size_t length;
+    uint8_t length_field_len;
+    size_t length_field_offset;
+    REQUIRE(QUICPacketLongHeader::length(length, length_field_len, length_field_offset, buf, sizeof(buf)));
+    CHECK(length == 6);
+    CHECK(length_field_len == 1);
+    CHECK(length_field_offset == 24);
+
+    size_t pn_offset;
+    REQUIRE(QUICPacketLongHeader::packet_number_offset(pn_offset, buf, sizeof(buf)));
+    CHECK(pn_offset == 25);
+
+    size_t packet_length;
+    REQUIRE(QUICPacketLongHeader::packet_length(packet_length, buf, sizeof(buf)));
+    CHECK(packet_length == sizeof(buf));
+  }
+
   SECTION("Long Header (store) INITIAL Packet")
   {
     uint8_t buf[64] = {0};
@@ -122,8 +182,9 @@
     const uint8_t expected[] = {
       0xc3,                                           // Long header, Type: INITIAL
       0x11, 0x22, 0x33, 0x44,                         // Version
-      0x55,                                           // DCIL/SCIL
+      0x08,                                           // DCID Len
       0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Destination Connection ID
+      0x08,                                           // SCID Len
       0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Source Connection ID
       0x00,                                           // Token Length (i), Token (*)
       0x19,                                           // Length (Not 0x09 because it will have 16 bytes of AEAD tag)
@@ -160,11 +221,13 @@
     size_t len      = 0;
 
     const uint8_t expected[] = {
-      0xf5,                                           // Long header, Type: RETRY
+      0xf0,                                           // Long header, Type: RETRY
       0x11, 0x22, 0x33, 0x44,                         // Version
-      0x55,                                           // DCIL/SCIL
+      0x08,                                           // DCID Len
       0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Destination Connection ID
+      0x08,                                           // SCID Len
       0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Source Connection ID
+      0x08,                                           // ODCID Len
       0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, // Original Destination Connection ID
       0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, // Retry Token
       0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0,
@@ -220,7 +283,7 @@
     ats_unique_buf uinput = ats_unique_malloc(sizeof(input));
     memcpy(uinput.get(), input, sizeof(input));
 
-    QUICPacketHeaderUPtr header = QUICPacketHeader::load({}, std::move(uinput), sizeof(input), 0);
+    QUICPacketHeaderUPtr header = QUICPacketHeader::load({}, {}, std::move(uinput), sizeof(input), 0);
     CHECK(header->size() == 23);
     CHECK(header->packet_size() == 25);
     CHECK(header->key_phase() == QUICKeyPhase::PHASE_0);
diff --git a/iocore/net/quic/test/test_QUICPacketFactory.cc b/iocore/net/quic/test/test_QUICPacketFactory.cc
index 08d94b4..dc01765 100644
--- a/iocore/net/quic/test/test_QUICPacketFactory.cc
+++ b/iocore/net/quic/test/test_QUICPacketFactory.cc
@@ -50,10 +50,11 @@
   uint8_t expected[] = {
     0xa7,                                           // Long header, Type: NONE
     0x00, 0x00, 0x00, 0x00,                         // Version
-    0x55,                                           // DCIL/SCIL
+    0x08,                                           // DCID Len
     0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Destination Connection ID
+    0x08,                                           // SCID Len
     0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Source Connection ID
-    0xff, 0x00, 0x00, 0x14,                         // Supported Version
+    0xff, 0x00, 0x00, 0x17,                         // Supported Version
     0x1a, 0x2a, 0x3a, 0x4a,                         // Excercise Version
   };
   uint8_t buf[1024] = {0};
diff --git a/iocore/net/quic/test/test_QUICPacketHeaderProtector.cc b/iocore/net/quic/test/test_QUICPacketHeaderProtector.cc
index dc754a4..5cb69af 100644
--- a/iocore/net/quic/test/test_QUICPacketHeaderProtector.cc
+++ b/iocore/net/quic/test/test_QUICPacketHeaderProtector.cc
@@ -70,13 +70,19 @@
   SECTION("Long header", "[quic]")
   {
     uint8_t original[] = {
-      0xC3, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
-      0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
-      0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
-      0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+      0xc3,                                           // Long header, Type: INITIAL
+      0x11, 0x22, 0x33, 0x44,                         // Version
+      0x08,                                           // DCID Len
+      0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // Destination Connection ID
+      0x08,                                           // SCID Len
+      0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // Source Connection ID
+      0x00,                                           // Token Length (i), Token (*)
+      0x19,                                           // Length (Not 0x09 because it will have 16 bytes of AEAD tag)
+      0x01, 0x23, 0x45, 0x67,                         // Packet number
+      0x11, 0x22, 0x33, 0x44, 0x55,                   // Payload (dummy)
     };
     uint8_t tmp[64];
-    memcpy(tmp, original, sizeof(tmp));
+    memcpy(tmp, original, sizeof(original));
 
     QUICPacketProtectionKeyInfo pp_key_info_client;
     QUICPacketProtectionKeyInfo pp_key_info_server;
@@ -91,15 +97,18 @@
     QUICPacketHeaderProtector server_ph_protector(pp_key_info_server);
 
     // ## Client -> Server
-    client_ph_protector.protect(tmp, sizeof(tmp), 18);
+    REQUIRE(client_ph_protector.protect(tmp, sizeof(tmp), 18));
     CHECK(memcmp(original, tmp, sizeof(original)) != 0);
-    server_ph_protector.unprotect(tmp, sizeof(tmp));
+    REQUIRE(server_ph_protector.unprotect(tmp, sizeof(tmp)));
     CHECK(memcmp(original, tmp, sizeof(original)) == 0);
     // ## Server -> Client
-    server_ph_protector.protect(tmp, sizeof(tmp), 18);
+    REQUIRE(server_ph_protector.protect(tmp, sizeof(tmp), 18));
     CHECK(memcmp(original, tmp, sizeof(original)) != 0);
-    client_ph_protector.unprotect(tmp, sizeof(tmp));
+    REQUIRE(client_ph_protector.unprotect(tmp, sizeof(tmp)));
     CHECK(memcmp(original, tmp, sizeof(original)) == 0);
+
+    delete client;
+    delete server;
   }
 
   SECTION("Short header", "[quic]")
@@ -197,14 +206,24 @@
     REQUIRE(client->handshake(&msg5, &msg4) == 1);
 
     // ## Client -> Server
-    client_ph_protector.protect(tmp, sizeof(tmp), 18);
+    REQUIRE(client_ph_protector.protect(tmp, sizeof(tmp), 18));
     CHECK(memcmp(original, tmp, sizeof(original)) != 0);
-    server_ph_protector.unprotect(tmp, sizeof(tmp));
+    REQUIRE(server_ph_protector.unprotect(tmp, sizeof(tmp)));
     CHECK(memcmp(original, tmp, sizeof(original)) == 0);
     // ## Server -> Client
-    server_ph_protector.protect(tmp, sizeof(tmp), 18);
+    REQUIRE(server_ph_protector.protect(tmp, sizeof(tmp), 18));
     CHECK(memcmp(original, tmp, sizeof(original)) != 0);
-    client_ph_protector.unprotect(tmp, sizeof(tmp));
+    REQUIRE(client_ph_protector.unprotect(tmp, sizeof(tmp)));
     CHECK(memcmp(original, tmp, sizeof(original)) == 0);
+
+    delete client;
+    delete server;
   }
+
+  SSL_CTX_free(client_ssl_ctx);
+  SSL_CTX_free(server_ssl_ctx);
+  BIO_free(crt_bio);
+  BIO_free(key_bio);
+  X509_free(x509);
+  EVP_PKEY_free(pkey);
 }
diff --git a/iocore/net/quic/test/test_QUICPathValidator.cc b/iocore/net/quic/test/test_QUICPathValidator.cc
new file mode 100644
index 0000000..78c51f2
--- /dev/null
+++ b/iocore/net/quic/test/test_QUICPathValidator.cc
@@ -0,0 +1,112 @@
+/** @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 "catch.hpp"
+
+#include "quic/QUICPathValidator.h"
+#include "quic/Mock.h"
+#include "stdio.h"
+#include "stdlib.h"
+
+TEST_CASE("QUICPathValidator", "[quic]")
+{
+  MockQUICConnectionInfoProvider cinfo_provider;
+  QUICPathValidator pv_c(cinfo_provider, [](bool x) {});
+  QUICPathValidator pv_s(cinfo_provider, [](bool x) {});
+
+  SECTION("interests")
+  {
+    auto interests = pv_c.interests();
+    CHECK(std::find_if(interests.begin(), interests.end(), [](QUICFrameType t) { return t == QUICFrameType::PATH_CHALLENGE; }) !=
+          interests.end());
+    CHECK(std::find_if(interests.begin(), interests.end(), [](QUICFrameType t) { return t == QUICFrameType::PATH_RESPONSE; }) !=
+          interests.end());
+    CHECK(std::find_if(interests.begin(), interests.end(), [](QUICFrameType t) {
+            return t != QUICFrameType::PATH_CHALLENGE && t != QUICFrameType::PATH_RESPONSE;
+          }) == interests.end());
+  }
+
+  SECTION("basic scenario")
+  {
+    uint8_t frame_buf[1024];
+    uint32_t seq_num = 1;
+    IpEndpoint local, remote;
+    ats_ip_pton("127.0.0.1:4433", &local);
+    ats_ip_pton("127.0.0.1:12345", &remote);
+    QUICPath path = {local, remote};
+
+    // Send a challenge
+    CHECK(!pv_c.is_validating(path));
+    CHECK(!pv_c.is_validated(path));
+    REQUIRE(!pv_c.will_generate_frame(QUICEncryptionLevel::ONE_RTT, 0, false, seq_num));
+    pv_c.validate(path);
+    CHECK(pv_c.is_validating(path));
+    CHECK(!pv_c.is_validated(path));
+    REQUIRE(pv_c.will_generate_frame(QUICEncryptionLevel::ONE_RTT, 0, false, seq_num));
+    auto frame = pv_c.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 1024, 1024, 0, seq_num);
+    REQUIRE(frame);
+    CHECK(frame->type() == QUICFrameType::PATH_CHALLENGE);
+    CHECK(pv_c.is_validating(path));
+    CHECK(!pv_c.is_validated(path));
+    ++seq_num;
+
+    // Receive the challenge and respond
+    CHECK(!pv_s.is_validating(path));
+    CHECK(!pv_s.is_validated(path));
+    REQUIRE(!pv_s.will_generate_frame(QUICEncryptionLevel::ONE_RTT, 0, false, seq_num));
+    auto error = pv_s.handle_frame(QUICEncryptionLevel::ONE_RTT, *frame);
+    REQUIRE(!error);
+    CHECK(!pv_s.is_validating(path));
+    CHECK(!pv_s.is_validated(path));
+    REQUIRE(pv_s.will_generate_frame(QUICEncryptionLevel::ONE_RTT, 0, false, seq_num));
+    frame->~QUICFrame();
+    frame = pv_s.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 1024, 1024, 0, seq_num);
+    REQUIRE(frame);
+    CHECK(frame->type() == QUICFrameType::PATH_RESPONSE);
+    CHECK(!pv_s.is_validating(path));
+    CHECK(!pv_s.is_validated(path));
+    ++seq_num;
+
+    uint8_t buf[1024];
+    size_t len = 0;
+    uint8_t received_frame_buf[1024];
+    Ptr<IOBufferBlock> ibb = frame->to_io_buffer_block(sizeof(buf));
+    for (auto b = ibb; b; b = b->next) {
+      memcpy(buf + len, b->start(), b->size());
+      len += b->size();
+    }
+    MockQUICPacket mock_packet;
+    auto received_frame = QUICFrameFactory::create(received_frame_buf, buf, len, &mock_packet);
+    mock_packet.set_from(remote);
+    mock_packet.set_to(local);
+
+    // Receive the response
+    error = pv_c.handle_frame(QUICEncryptionLevel::ONE_RTT, *received_frame);
+    REQUIRE(!error);
+    CHECK(!pv_c.is_validating(path));
+    CHECK(pv_c.is_validated(path));
+
+    frame->~QUICFrame();
+    received_frame->~QUICFrame();
+  }
+}
diff --git a/iocore/net/quic/test/test_QUICPinger.cc b/iocore/net/quic/test/test_QUICPinger.cc
new file mode 100644
index 0000000..fce04c6
--- /dev/null
+++ b/iocore/net/quic/test/test_QUICPinger.cc
@@ -0,0 +1,101 @@
+/** @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 "catch.hpp"
+
+#include "QUICPinger.h"
+
+static constexpr QUICEncryptionLevel level = QUICEncryptionLevel::ONE_RTT;
+static uint8_t frame[1024]                 = {0};
+
+TEST_CASE("QUICPinger", "[quic]")
+{
+  SECTION("request and cancel")
+  {
+    QUICPinger pinger;
+    pinger.request();
+    REQUIRE(pinger.count() == 1);
+    pinger.request();
+    REQUIRE(pinger.count() == 2);
+    pinger.cancel();
+    REQUIRE(pinger.count() == 1);
+    REQUIRE(pinger.generate_frame(frame, level, UINT64_MAX, UINT16_MAX, 0, 0) != nullptr);
+    REQUIRE(pinger.count() == 0);
+  }
+
+  SECTION("generate PING Frame twice")
+  {
+    QUICPinger pinger;
+    pinger.request();
+    REQUIRE(pinger.count() == 1);
+    pinger.request();
+    REQUIRE(pinger.count() == 2);
+    REQUIRE(pinger.will_generate_frame(level, UINT64_MAX, false, 0) == true);
+    REQUIRE(pinger.count() == 2);
+    REQUIRE(pinger.will_generate_frame(level, UINT64_MAX, false, 0) == false);
+    REQUIRE(pinger.count() == 2);
+  }
+
+  SECTION("don't generate frame when packet is ack_elicting")
+  {
+    QUICPinger pinger;
+    pinger.request();
+    REQUIRE(pinger.count() == 1);
+    pinger.request();
+    REQUIRE(pinger.count() == 2);
+    REQUIRE(pinger.will_generate_frame(level, UINT64_MAX, true, 0) == false);
+    REQUIRE(pinger.count() == 1);
+    REQUIRE(pinger.will_generate_frame(level, UINT64_MAX, true, 1) == false);
+    REQUIRE(pinger.count() == 0);
+  }
+
+  SECTION("generating PING Frame for next continuos un-ack-eliciting packets")
+  {
+    QUICPinger pinger;
+    REQUIRE(pinger.will_generate_frame(level, UINT64_MAX, false, 0) == true);
+    REQUIRE(pinger.count() == 1);
+    REQUIRE(pinger.will_generate_frame(level, UINT64_MAX, true, 1) == false);
+    REQUIRE(pinger.count() == 0);
+    REQUIRE(pinger.will_generate_frame(level, UINT64_MAX, false, 2) == false);
+    REQUIRE(pinger.count() == 0);
+    REQUIRE(pinger.will_generate_frame(level, UINT64_MAX, false, 3) == true);
+    REQUIRE(pinger.count() == 1);
+  }
+
+  SECTION("don't send PING Frame for empty packet")
+  {
+    QUICPinger pinger;
+    REQUIRE(pinger.will_generate_frame(level, 0, false, 0) == false);
+    REQUIRE(pinger.count() == 0);
+    REQUIRE(pinger.will_generate_frame(level, UINT64_MAX, false, 1) == true);
+    REQUIRE(pinger.count() == 1);
+    REQUIRE(pinger.will_generate_frame(level, UINT64_MAX, true, 2) == false);
+    REQUIRE(pinger.count() == 0);
+    REQUIRE(pinger.will_generate_frame(level, UINT64_MAX, false, 3) == false);
+    REQUIRE(pinger.count() == 0);
+    REQUIRE(pinger.will_generate_frame(level, 0, false, 4) == false);
+    REQUIRE(pinger.count() == 0);
+    REQUIRE(pinger.will_generate_frame(level, 1, false, 5) == true);
+    REQUIRE(pinger.count() == 1);
+  }
+}
diff --git a/iocore/net/quic/test/test_QUICStream.cc b/iocore/net/quic/test/test_QUICStream.cc
index aefc488..1ed2356 100644
--- a/iocore/net/quic/test/test_QUICStream.cc
+++ b/iocore/net/quic/test/test_QUICStream.cc
@@ -229,50 +229,50 @@
 
     write_buffer->write(data, 1024);
     stream->handleEvent(VC_EVENT_WRITE_READY, nullptr);
-    CHECK(stream->will_generate_frame(level, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     CHECK(frame->type() == QUICFrameType::STREAM);
-    CHECK(stream->will_generate_frame(level, 0) == false);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
 
     write_buffer->write(data, 1024);
     stream->handleEvent(VC_EVENT_WRITE_READY, nullptr);
-    CHECK(stream->will_generate_frame(level, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     CHECK(frame->type() == QUICFrameType::STREAM);
-    CHECK(stream->will_generate_frame(level, 0) == false);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
 
     write_buffer->write(data, 1024);
     stream->handleEvent(VC_EVENT_WRITE_READY, nullptr);
-    CHECK(stream->will_generate_frame(level, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     CHECK(frame->type() == QUICFrameType::STREAM);
-    CHECK(stream->will_generate_frame(level, 0) == false);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
 
     write_buffer->write(data, 1024);
     stream->handleEvent(VC_EVENT_WRITE_READY, nullptr);
-    CHECK(stream->will_generate_frame(level, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     CHECK(frame->type() == QUICFrameType::STREAM);
-    CHECK(stream->will_generate_frame(level, 0) == false);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
 
     // This should not send a frame because of flow control
     write_buffer->write(data, 1024);
     stream->handleEvent(VC_EVENT_WRITE_READY, nullptr);
-    CHECK(stream->will_generate_frame(level, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     CHECK(frame);
     CHECK(frame->type() == QUICFrameType::STREAM_DATA_BLOCKED);
-    CHECK(stream->will_generate_frame(level, 0) == true);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
 
     // Update window
     stream->recv(*std::make_shared<QUICMaxStreamDataFrame>(stream_id, 5120));
 
     // This should send a frame
     stream->handleEvent(VC_EVENT_WRITE_READY, nullptr);
-    CHECK(stream->will_generate_frame(level, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     CHECK(frame->type() == QUICFrameType::STREAM);
-    CHECK(stream->will_generate_frame(level, 0) == false);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
 
     // Update window
     stream->recv(*std::make_shared<QUICMaxStreamDataFrame>(stream_id, 5632));
@@ -280,24 +280,24 @@
     // This should send a frame
     write_buffer->write(data, 1024);
     stream->handleEvent(VC_EVENT_WRITE_READY, nullptr);
-    CHECK(stream->will_generate_frame(level, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     CHECK(frame->type() == QUICFrameType::STREAM);
-    CHECK(stream->will_generate_frame(level, 0) == true);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
 
     stream->handleEvent(VC_EVENT_WRITE_READY, nullptr);
-    CHECK(stream->will_generate_frame(level, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     CHECK(frame->type() == QUICFrameType::STREAM_DATA_BLOCKED);
 
     // Update window
     stream->recv(*std::make_shared<QUICMaxStreamDataFrame>(stream_id, 6144));
 
     stream->handleEvent(VC_EVENT_WRITE_READY, nullptr);
-    CHECK(stream->will_generate_frame(level, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     CHECK(frame->type() == QUICFrameType::STREAM);
-    CHECK(stream->will_generate_frame(level, 0) == false);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
   }
 
   /*
@@ -329,13 +329,13 @@
     write_buffer->write(data1, sizeof(data1));
     stream->handleEvent(VC_EVENT_WRITE_READY, nullptr);
     // Generate STREAM frame
-    frame  = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    frame  = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     frame1 = static_cast<QUICStreamFrame *>(frame);
     CHECK(frame->type() == QUICFrameType::STREAM);
-    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0) == nullptr);
-    CHECK(stream->will_generate_frame(level, 0) == false);
+    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0) == nullptr);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
     stream->on_frame_lost(frame->id());
-    CHECK(stream->will_generate_frame(level, 0) == true);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
 
     // Write data2
     write_buffer->write(data2, sizeof(data2));
@@ -343,7 +343,7 @@
     // Lost the frame
     stream->on_frame_lost(frame->id());
     // Regenerate a frame
-    frame = stream->generate_frame(frame_buf2, level, 4096, 4096, 0);
+    frame = stream->generate_frame(frame_buf2, level, 4096, 4096, 0, 0);
     // Lost data should be resent first
     frame2 = static_cast<QUICStreamFrame *>(frame);
     CHECK(frame->type() == QUICFrameType::STREAM);
@@ -370,15 +370,15 @@
     QUICFrame *frame          = nullptr;
 
     stream->reset(QUICStreamErrorUPtr(new QUICStreamError(stream.get(), QUIC_APP_ERROR_CODE_STOPPING)));
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     REQUIRE(frame);
     CHECK(frame->type() == QUICFrameType::RESET_STREAM);
     // Don't send it again untill it is considers as lost
-    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0) == nullptr);
+    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0) == nullptr);
     // Loss the frame
     stream->on_frame_lost(frame->id());
     // After the loss the frame should be regenerated
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     REQUIRE(frame);
     CHECK(frame->type() == QUICFrameType::RESET_STREAM);
   }
@@ -401,15 +401,15 @@
     QUICFrame *frame          = nullptr;
 
     stream->stop_sending(QUICStreamErrorUPtr(new QUICStreamError(stream.get(), QUIC_APP_ERROR_CODE_STOPPING)));
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     REQUIRE(frame);
     CHECK(frame->type() == QUICFrameType::STOP_SENDING);
     // Don't send it again untill it is considers as lost
-    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0) == nullptr);
+    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0) == nullptr);
     // Loss the frame
     stream->on_frame_lost(frame->id());
     // After the loss the frame should be regenerated
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     REQUIRE(frame);
     CHECK(frame->type() == QUICFrameType::STOP_SENDING);
   }
@@ -599,15 +599,15 @@
     QUICFrame *frame          = nullptr;
 
     stream->stop_sending(QUICStreamErrorUPtr(new QUICStreamError(stream.get(), QUIC_APP_ERROR_CODE_STOPPING)));
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     REQUIRE(frame);
     CHECK(frame->type() == QUICFrameType::STOP_SENDING);
     // Don't send it again untill it is considers as lost
-    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0) == nullptr);
+    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0) == nullptr);
     // Loss the frame
     stream->on_frame_lost(frame->id());
     // After the loss the frame should be regenerated
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     REQUIRE(frame);
     CHECK(frame->type() == QUICFrameType::STOP_SENDING);
   }
@@ -687,50 +687,50 @@
 
     write_buffer->write(data, 1024);
     stream->handleEvent(VC_EVENT_WRITE_READY, nullptr);
-    CHECK(stream->will_generate_frame(level, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     CHECK(frame->type() == QUICFrameType::STREAM);
-    CHECK(stream->will_generate_frame(level, 0) == false);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
 
     write_buffer->write(data, 1024);
     stream->handleEvent(VC_EVENT_WRITE_READY, nullptr);
-    CHECK(stream->will_generate_frame(level, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     CHECK(frame->type() == QUICFrameType::STREAM);
-    CHECK(stream->will_generate_frame(level, 0) == false);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
 
     write_buffer->write(data, 1024);
     stream->handleEvent(VC_EVENT_WRITE_READY, nullptr);
-    CHECK(stream->will_generate_frame(level, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     CHECK(frame->type() == QUICFrameType::STREAM);
-    CHECK(stream->will_generate_frame(level, 0) == false);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
 
     write_buffer->write(data, 1024);
     stream->handleEvent(VC_EVENT_WRITE_READY, nullptr);
-    CHECK(stream->will_generate_frame(level, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     CHECK(frame->type() == QUICFrameType::STREAM);
-    CHECK(stream->will_generate_frame(level, 0) == false);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
 
     // This should not send a frame because of flow control
     write_buffer->write(data, 1024);
     stream->handleEvent(VC_EVENT_WRITE_READY, nullptr);
-    CHECK(stream->will_generate_frame(level, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     CHECK(frame);
     CHECK(frame->type() == QUICFrameType::STREAM_DATA_BLOCKED);
-    CHECK(stream->will_generate_frame(level, 0) == true);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
 
     // Update window
     stream->recv(*std::make_shared<QUICMaxStreamDataFrame>(stream_id, 5120));
 
     // This should send a frame
     stream->handleEvent(VC_EVENT_WRITE_READY, nullptr);
-    CHECK(stream->will_generate_frame(level, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     CHECK(frame->type() == QUICFrameType::STREAM);
-    CHECK(stream->will_generate_frame(level, 0) == false);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
 
     // Update window
     stream->recv(*std::make_shared<QUICMaxStreamDataFrame>(stream_id, 5632));
@@ -738,24 +738,24 @@
     // This should send a frame
     write_buffer->write(data, 1024);
     stream->handleEvent(VC_EVENT_WRITE_READY, nullptr);
-    CHECK(stream->will_generate_frame(level, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     CHECK(frame->type() == QUICFrameType::STREAM);
-    CHECK(stream->will_generate_frame(level, 0) == true);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
 
     stream->handleEvent(VC_EVENT_WRITE_READY, nullptr);
-    CHECK(stream->will_generate_frame(level, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     CHECK(frame->type() == QUICFrameType::STREAM_DATA_BLOCKED);
 
     // Update window
     stream->recv(*std::make_shared<QUICMaxStreamDataFrame>(stream_id, 6144));
 
     stream->handleEvent(VC_EVENT_WRITE_READY, nullptr);
-    CHECK(stream->will_generate_frame(level, 0) == true);
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     CHECK(frame->type() == QUICFrameType::STREAM);
-    CHECK(stream->will_generate_frame(level, 0) == false);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
   }
 
   /*
@@ -786,13 +786,13 @@
     write_buffer->write(data1, sizeof(data1));
     stream->handleEvent(VC_EVENT_WRITE_READY, nullptr);
     // Generate STREAM frame
-    frame  = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    frame  = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     frame1 = static_cast<QUICStreamFrame *>(frame);
     CHECK(frame->type() == QUICFrameType::STREAM);
-    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0) == nullptr);
-    CHECK(stream->will_generate_frame(level, 0) == false);
+    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0) == nullptr);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == false);
     stream->on_frame_lost(frame->id());
-    CHECK(stream->will_generate_frame(level, 0) == true);
+    CHECK(stream->will_generate_frame(level, 0, false, 0) == true);
 
     // Write data2
     write_buffer->write(data2, sizeof(data2));
@@ -800,7 +800,7 @@
     // Lost the frame
     stream->on_frame_lost(frame->id());
     // Regenerate a frame
-    frame = stream->generate_frame(frame_buf2, level, 4096, 4096, 0);
+    frame = stream->generate_frame(frame_buf2, level, 4096, 4096, 0, 0);
     // Lost data should be resent first
     frame2 = static_cast<QUICStreamFrame *>(frame);
     CHECK(frame->type() == QUICFrameType::STREAM);
@@ -826,15 +826,15 @@
     QUICFrame *frame          = nullptr;
 
     stream->reset(QUICStreamErrorUPtr(new QUICStreamError(stream.get(), QUIC_APP_ERROR_CODE_STOPPING)));
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     REQUIRE(frame);
     CHECK(frame->type() == QUICFrameType::RESET_STREAM);
     // Don't send it again untill it is considers as lost
-    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0) == nullptr);
+    CHECK(stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0) == nullptr);
     // Loss the frame
     stream->on_frame_lost(frame->id());
     // After the loss the frame should be regenerated
-    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0);
+    frame = stream->generate_frame(frame_buf, level, 4096, 4096, 0, 0);
     REQUIRE(frame);
     CHECK(frame->type() == QUICFrameType::RESET_STREAM);
   }
diff --git a/iocore/net/quic/test/test_QUICStreamManager.cc b/iocore/net/quic/test/test_QUICStreamManager.cc
index ced351a..74a37cc 100644
--- a/iocore/net/quic/test/test_QUICStreamManager.cc
+++ b/iocore/net/quic/test/test_QUICStreamManager.cc
@@ -247,12 +247,12 @@
   // total_offset should be a integer in unit of octets
   uint8_t frame_buf[4096];
   mock_app.send(reinterpret_cast<uint8_t *>(block_1024->buf()), 1024, 0);
-  sm.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 16384, 16384, 0);
+  sm.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 16384, 16384, 0, 0);
   CHECK(sm.total_offset_sent() == 1024);
 
   // total_offset should be a integer in unit of octets
   mock_app.send(reinterpret_cast<uint8_t *>(block_1024->buf()), 1024, 4);
-  sm.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 16384, 16384, 0);
+  sm.generate_frame(frame_buf, QUICEncryptionLevel::ONE_RTT, 16384, 16384, 0, 0);
   CHECK(sm.total_offset_sent() == 2048);
 
   // Wait for event processing
diff --git a/iocore/net/quic/test/test_QUICTransportParameters.cc b/iocore/net/quic/test/test_QUICTransportParameters.cc
index d60e260..95fb0ae 100644
--- a/iocore/net/quic/test/test_QUICTransportParameters.cc
+++ b/iocore/net/quic/test/test_QUICTransportParameters.cc
@@ -167,7 +167,7 @@
     CHECK(len == 16);
     CHECK(memcmp(data, buf + 12, 16) == 0);
 
-    CHECK(!params_in_ee.contains(QUICTransportParameterId::DISABLE_MIGRATION));
+    CHECK(!params_in_ee.contains(QUICTransportParameterId::DISABLE_ACTIVE_MIGRATION));
   }
 
   SECTION("OK case - zero length value")
@@ -205,7 +205,7 @@
     CHECK(len == 2);
     CHECK(memcmp(data, "\x51\x23", 2) == 0);
 
-    CHECK(params_in_ee.contains(QUICTransportParameterId::DISABLE_MIGRATION));
+    CHECK(params_in_ee.contains(QUICTransportParameterId::DISABLE_ACTIVE_MIGRATION));
   }
 
   SECTION("Duplicate parameters")
@@ -281,7 +281,7 @@
 
     uint16_t max_packet_size = 0x1bcd;
     params_in_ee.set(QUICTransportParameterId::MAX_PACKET_SIZE, max_packet_size);
-    params_in_ee.set(QUICTransportParameterId::DISABLE_MIGRATION, nullptr, 0);
+    params_in_ee.set(QUICTransportParameterId::DISABLE_ACTIVE_MIGRATION, nullptr, 0);
 
     params_in_ee.add_version(0x01020304);
     params_in_ee.add_version(0x05060708);
diff --git a/iocore/net/quic/test/test_QUICType.cc b/iocore/net/quic/test/test_QUICType.cc
index aff8247..fb45c44 100644
--- a/iocore/net/quic/test/test_QUICType.cc
+++ b/iocore/net/quic/test/test_QUICType.cc
@@ -30,6 +30,74 @@
 
 TEST_CASE("QUICType", "[quic]")
 {
+  SECTION("QUICPath")
+  {
+    IpEndpoint local_a, local_b, remote_a, remote_b;
+    QUICPath path_a = {{}, {}}, path_b = {{}, {}};
+
+    // The same addresses and ports -> TRUE
+    ats_ip_pton("192.168.0.1:4433", &local_a);
+    ats_ip_pton("192.168.1.1:12345", &remote_a);
+    ats_ip_pton("192.168.0.1:4433", &local_b);
+    ats_ip_pton("192.168.1.1:12345", &remote_b);
+    path_a = {local_a, remote_a};
+    path_b = {local_b, remote_b};
+    CHECK(path_a == path_b);
+    CHECK(path_b == path_a);
+    path_a = {remote_a, local_a};
+    path_b = {remote_b, local_b};
+    CHECK(path_a == path_b);
+    CHECK(path_b == path_a);
+
+    // Different ports -> FALSE
+    ats_ip_pton("192.168.0.1:4433", &local_a);
+    ats_ip_pton("192.168.1.1:12345", &remote_a);
+    ats_ip_pton("192.168.0.1:4433", &local_b);
+    ats_ip_pton("192.168.1.1:54321", &remote_b);
+    path_a = {local_a, remote_a};
+    path_b = {local_b, remote_b};
+    CHECK(!(path_a == path_b));
+    CHECK(!(path_b == path_a));
+    path_a = {remote_a, local_a};
+    path_b = {remote_b, local_b};
+    CHECK(!(path_a == path_b));
+    CHECK(!(path_b == path_a));
+
+    // Different addresses but the same ports -> FALSE
+    ats_ip_pton("192.168.0.1:4433", &local_a);
+    ats_ip_pton("192.168.1.1:12345", &remote_a);
+    ats_ip_pton("192.168.0.1:4433", &local_b);
+    ats_ip_pton("192.168.2.1:12345", &remote_b);
+    path_a = {local_a, remote_a};
+    path_b = {local_b, remote_b};
+    CHECK(!(path_a == path_b));
+    CHECK(!(path_b == path_a));
+    path_a = {remote_a, local_a};
+    path_b = {remote_b, local_b};
+    CHECK(!(path_a == path_b));
+    CHECK(!(path_b == path_a));
+
+    // Server local address is any -> TRUE
+    ats_ip_pton("0.0.0.0:4433", &local_a);
+    ats_ip_pton("192.168.1.1:12345", &remote_a);
+    ats_ip_pton("192.168.0.1:4433", &local_b);
+    ats_ip_pton("192.168.1.1:12345", &remote_b);
+    path_a = {local_a, remote_a};
+    path_b = {local_b, remote_b};
+    CHECK(path_a == path_b);
+    CHECK(path_b == path_a);
+
+    // Client local address and port are any -> TRUE
+    ats_ip_pton("0.0.0.0:0", &local_a);
+    ats_ip_pton("192.168.1.1:12345", &remote_a);
+    ats_ip_pton("192.168.0.1:4433", &local_b);
+    ats_ip_pton("192.168.1.1:12345", &remote_b);
+    path_a = {local_a, remote_a};
+    path_b = {local_b, remote_b};
+    CHECK(path_a == path_b);
+    CHECK(path_b == path_a);
+  }
+
   SECTION("QUICRetryToken")
   {
     IpEndpoint ep;
diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc
index 080d9f2..e31cdbb 100644
--- a/mgmt/RecordsConfig.cc
+++ b/mgmt/RecordsConfig.cc
@@ -1372,7 +1372,7 @@
   ,
   {RECT_CONFIG, "proxy.config.quic.initial_max_stream_data_uni_in", RECD_INT, "4096", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.quic.initial_max_stream_data_uni_out", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.quic.initial_max_stream_data_uni_out", RECD_INT, "4096", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
   ,
   {RECT_CONFIG, "proxy.config.quic.initial_max_streams_bidi_in", RECD_INT, "100", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
   ,
@@ -1390,6 +1390,10 @@
   ,
   {RECT_CONFIG, "proxy.config.quic.max_ack_delay_out", RECD_INT, "25", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
   ,
+  {RECT_CONFIG, "proxy.config.quic.active_cid_limit_in", RECD_INT, "4", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
+  ,
+  {RECT_CONFIG, "proxy.config.quic.active_cid_limit_out", RECD_INT, "8", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
+  ,
   // Constants of Loss Detection
   {RECT_CONFIG, "proxy.config.quic.loss_detection.packet_threshold", RECD_INT, "3", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
   ,
@@ -1397,7 +1401,7 @@
   ,
   {RECT_CONFIG, "proxy.config.quic.loss_detection.granularity", RECD_INT, "1", RECU_DYNAMIC, RR_NULL, RECC_STR, "[0-1]", RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.quic.loss_detection.initial_rtt", RECD_INT, "100", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.quic.loss_detection.initial_rtt", RECD_INT, "500", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
   ,
 
   // Constatns of Congestion Control
@@ -1409,7 +1413,7 @@
   ,
   {RECT_CONFIG, "proxy.config.quic.congestion_control.loss_reduction_factor", RECD_FLOAT, "0.5", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[\\.0-9]+$", RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.quic.congestion_control.persistent_congestion_threshold", RECD_INT, "2", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[\\.0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.quic.congestion_control.persistent_congestion_threshold", RECD_INT, "3", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[\\.0-9]+$", RECA_NULL}
   ,
 
   //# Add LOCAL Records Here
diff --git a/proxy/http3/QPACK.h b/proxy/http3/QPACK.h
index 85bd710..feb1068 100644
--- a/proxy/http3/QPACK.h
+++ b/proxy/http3/QPACK.h
@@ -151,12 +151,12 @@
   {
   public:
     DecodeRequest(uint16_t largest_reference, EThread *thread, Continuation *continuation, uint64_t stream_id,
-                  const uint8_t *header_blcok, size_t header_block_len, HTTPHdr &hdr)
+                  const uint8_t *header_block, size_t header_block_len, HTTPHdr &hdr)
       : _largest_reference(largest_reference),
         _thread(thread),
         _continuation(continuation),
         _stream_id(stream_id),
-        _header_block(header_blcok),
+        _header_block(header_block),
         _header_block_len(header_block_len),
         _hdr(hdr)
     {
diff --git a/src/traffic_quic/quic_client.cc b/src/traffic_quic/quic_client.cc
index 007c20a..7f3ec64 100644
--- a/src/traffic_quic/quic_client.cc
+++ b/src/traffic_quic/quic_client.cc
@@ -33,8 +33,8 @@
 // https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_alpn_protos.html
 // Should be integrate with IP_PROTO_TAG_HTTP_QUIC in ts/ink_inet.h ?
 using namespace std::literals;
-static constexpr std::string_view HQ_ALPN_PROTO_LIST("\5hq-20"sv);
-static constexpr std::string_view H3_ALPN_PROTO_LIST("\5h3-20"sv);
+static constexpr std::string_view HQ_ALPN_PROTO_LIST("\5hq-23"sv);
+static constexpr std::string_view H3_ALPN_PROTO_LIST("\5h3-23"sv);
 
 QUICClient::QUICClient(const QUICClientConfig *config) : Continuation(new_ProxyMutex()), _config(config)
 {
diff --git a/src/tscore/ink_inet.cc b/src/tscore/ink_inet.cc
index 6483850..9f36ce9 100644
--- a/src/tscore/ink_inet.cc
+++ b/src/tscore/ink_inet.cc
@@ -50,8 +50,8 @@
 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-20"sv); // HTTP/0.9 over QUIC
-const std::string_view IP_PROTO_TAG_HTTP_3("h3-20"sv);    // HTTP/3 over QUIC
+const std::string_view IP_PROTO_TAG_HTTP_QUIC("hq-23"sv); // HTTP/0.9 over QUIC
+const std::string_view IP_PROTO_TAG_HTTP_3("h3-23"sv);    // HTTP/3 over QUIC
 
 const std::string_view UNIX_PROTO_TAG{"unix"sv};