diff --git a/redhat-4.23.patch b/redhat-4.23.patch index 7f51e2d..0bd1640 100644 --- a/redhat-4.23.patch +++ b/redhat-4.23.patch @@ -1,7 +1,7 @@ From e8384b6daea3b8091ad1bcfce84efc9e2c6a746d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= Date: Thu, 22 Jan 2026 14:27:09 +0100 -Subject: [PATCH 01/33] s3:libads: Allocate cli_credentials on a stackframe +Subject: [PATCH 01/66] s3:libads: Allocate cli_credentials on a stackframe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @@ -82,7 +82,7 @@ index 9d6d962a2bc..d01afa69697 100644 From 7af95c7cb142aeb5f422a69d3b7a0ea3c0d2c2c2 Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Mon, 26 Jan 2026 13:36:02 +0100 -Subject: [PATCH 02/33] s3:rpc_client: Fix memory leak opening local named pipe +Subject: [PATCH 02/66] s3:rpc_client: Fix memory leak opening local named pipe If no local server name was passed to rpc_pipe_open_local_np() then get_myname() was called with NULL talloc context instead of the @@ -125,7 +125,7 @@ index e3f48526492..c61b8eb16cf 100644 From ab1287f78bd9d2397c8eb26fbedafa028e2aaa16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Deschner?= Date: Tue, 2 Dec 2025 17:17:33 +0100 -Subject: [PATCH 03/33] s3-selftest: mention in-memory ccache usage when +Subject: [PATCH 03/66] s3-selftest: mention in-memory ccache usage when nothing is provided BUG: https://bugzilla.samba.org/show_bug.cgi?id=15840 @@ -165,7 +165,7 @@ index 8a3c9ef2bc7..92d3996d078 100755 From 0aa0d39e9a5deb77114f40930b599f11fd7cf3b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Deschner?= Date: Tue, 2 Dec 2025 17:18:41 +0100 -Subject: [PATCH 04/33] s3-selftest: verify KRB5CCNAME presence after kinit +Subject: [PATCH 04/66] s3-selftest: verify KRB5CCNAME presence after kinit using klist BUG: https://bugzilla.samba.org/show_bug.cgi?id=15840 @@ -222,7 +222,7 @@ index 92d3996d078..c53520cf733 100755 From b9c07d59c6a20931b80fa104629477ab8f78b4ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Deschner?= Date: Tue, 2 Dec 2025 17:01:31 +0100 -Subject: [PATCH 05/33] s3-selftest: Activate "net ads kerberos kinit" tests +Subject: [PATCH 05/66] s3-selftest: Activate "net ads kerberos kinit" tests with --use-krb5-ccache BUG: https://bugzilla.samba.org/show_bug.cgi?id=15840 @@ -301,7 +301,7 @@ index c53520cf733..b7933bab6a6 100755 From c82b7636b633575621e8e5964a93332956c238ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Deschner?= Date: Tue, 2 Dec 2025 16:56:44 +0100 -Subject: [PATCH 06/33] s3-net: properly setup krb5 ccache name via +Subject: [PATCH 06/66] s3-net: properly setup krb5 ccache name via --use-krb5-ccache BUG: https://bugzilla.samba.org/show_bug.cgi?id=15840 @@ -389,7 +389,7 @@ index d49b7537e71..5c57a0b290e 100644 From 4f5ffea631d805564f7e92cc5f0f2f7ad55ba493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Deschner?= Date: Sat, 13 Dec 2025 13:49:37 +0100 -Subject: [PATCH 07/33] doc-xml: Document "net ads kerberos" commands +Subject: [PATCH 07/66] doc-xml: Document "net ads kerberos" commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @@ -565,7 +565,7 @@ index d9293d0bb34..737415b3722 100644 From f634526bd95b8396ea7f5f1c8ed059eb01a5286b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= Date: Tue, 3 Feb 2026 12:53:10 +0100 -Subject: [PATCH 08/33] s3:utils: 'net ads kerberos kinit' should use also +Subject: [PATCH 08/66] s3:utils: 'net ads kerberos kinit' should use also default ccache name from krb5.conf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 @@ -672,7 +672,7 @@ index 271c96cf804..0ce03f8213d 100644 From 0ca830d6ddded29b2b5d1969ebcbc4df1156656e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= Date: Thu, 5 Feb 2026 16:04:25 +0100 -Subject: [PATCH 09/33] manpages: Update NET ADS KERBEROS KINIT manpage +Subject: [PATCH 09/66] manpages: Update NET ADS KERBEROS KINIT manpage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @@ -747,7 +747,7 @@ index 737415b3722..b793361a27f 100644 From 44b613d80c6a3818cc6ca593d57d51cd1bc00aa5 Mon Sep 17 00:00:00 2001 From: Noel Power Date: Fri, 13 Feb 2026 11:54:46 +0000 -Subject: [PATCH 10/33] selftest: Update tests to use +Subject: [PATCH 10/66] selftest: Update tests to use --use-kereros=desired|required no creds Add tests to call smbclient without passing credentials to @@ -806,7 +806,7 @@ index 31678d17e28..1139efd70d7 100755 From 65f70c0505759489a8b219e1297f8cdee2cc260a Mon Sep 17 00:00:00 2001 From: Noel Power Date: Mon, 19 Jan 2026 15:46:59 +0000 -Subject: [PATCH 11/33] auth/credentials: Fix regression with +Subject: [PATCH 11/66] auth/credentials: Fix regression with --use-kerberos=desired for smbclient As part of the gse_krb5 processing the following call chain @@ -878,7 +878,7 @@ index f0a5f7bb935..ab2d79d7114 100644 From 8c955cad98b197936fceaf98306047e1f929ddfe Mon Sep 17 00:00:00 2001 From: Noel Power Date: Mon, 19 Jan 2026 16:10:10 +0000 -Subject: [PATCH 12/33] s3/libsmb: cli_session_creds_init fails when kerberos +Subject: [PATCH 12/66] s3/libsmb: cli_session_creds_init fails when kerberos is desired There is a regression with code using cli_session_creds_init when @@ -920,7 +920,7 @@ index 116f746d37e..3fd423d8e5f 100644 From 015167aea7ece2bb683f86aa4b8c688d7a83267d Mon Sep 17 00:00:00 2001 From: Noel Power Date: Mon, 19 Jan 2026 16:18:02 +0000 -Subject: [PATCH 13/33] s3/libsmb: block anon authentication fallback is +Subject: [PATCH 13/66] s3/libsmb: block anon authentication fallback is use-kerberos = desired When cli_credentials_get_kerberos_state returns CRED_USE_KERBEROS_REQUIRED @@ -959,7 +959,7 @@ index f9b52e1f05a..8c7208aaee0 100644 From 3f66a4fbb46f614bf81533677944b1093439aaf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= Date: Wed, 18 Mar 2026 20:24:37 +0100 -Subject: [PATCH 14/33] s3:libnet: Fix DC numeric ip handling +Subject: [PATCH 14/66] s3:libnet: Fix DC numeric ip handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @@ -994,7 +994,7 @@ index 609b2b96222..66d682a5b95 100644 From 4725da7df3028d37d8bf34b3671c553b7337703b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= Date: Mon, 23 Mar 2026 19:03:34 +0100 -Subject: [PATCH 15/33] s3:libads: Allow to specify 'dns_lookup_kdc' in +Subject: [PATCH 15/66] s3:libads: Allow to specify 'dns_lookup_kdc' in krb5.conf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 @@ -1117,7 +1117,7 @@ index a96211c7289..fbeaeff92a9 100644 From 5e1fde5e03c3899b329bd38c36033c385f6fb5f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= Date: Mon, 23 Mar 2026 19:05:31 +0100 -Subject: [PATCH 16/33] s3:libads: Set dns_lookup_kdc=false during net ads join +Subject: [PATCH 16/66] s3:libads: Set dns_lookup_kdc=false during net ads join MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @@ -1225,7 +1225,7 @@ index 66d682a5b95..81bcb9793a6 100644 From 42e48d3dd29027573f6e1c04f78f8d9cb91eb4ab Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Fri, 14 Feb 2025 17:07:14 +0100 -Subject: [PATCH 17/33] vfs: Add VFS_OPEN_HOW_RESOLVE_NO_XDEV flag +Subject: [PATCH 17/66] vfs: Add VFS_OPEN_HOW_RESOLVE_NO_XDEV flag It disallows traversal of mount points during path resolution, including bind mounts. @@ -1257,7 +1257,7 @@ index e87a0d923e5..15e2186eab7 100644 From 5656562fd43507501aea62b7a43b1d3d5431b313 Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Fri, 14 Feb 2025 17:13:39 +0100 -Subject: [PATCH 18/33] vfs: Use RESOLVE_NO_XDEV by default on all shares +Subject: [PATCH 18/66] vfs: Use RESOLVE_NO_XDEV by default on all shares Enable the flag by default on all shares, it will be automatically disabled if the system does not support openat2(). @@ -1313,7 +1313,7 @@ index 83c9cc06de8..d531f277199 100644 From 21ce7f160fb6f2bec613be36f2120002205bf84e Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Wed, 8 Oct 2025 10:54:55 +0200 -Subject: [PATCH 19/33] selftest/Samba3: nt4_dc* use +Subject: [PATCH 19/66] selftest/Samba3: nt4_dc* use vfs_default:VFS_OPEN_HOW_RESOLVE_NO_XDEV=no From 076c22fbd7ecbf22dbfeb1711609f07fd42f88b0, we should always test the @@ -1346,7 +1346,7 @@ index 6f17d659d96..3b990bcb349 100755 From 0f873b9e694c2db4c54bb480183583d04dde3bdd Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Fri, 14 Feb 2025 17:14:59 +0100 -Subject: [PATCH 20/33] vfs: Pass the RESOLVE_NO_XDEV from upper layers to +Subject: [PATCH 20/66] vfs: Pass the RESOLVE_NO_XDEV from upper layers to openat2() syscall BUG: https://bugzilla.samba.org/show_bug.cgi?id=15805 @@ -1416,7 +1416,7 @@ index d531f277199..f0270e96002 100644 From ff04fbf4e754c6eca9ea87a13d91bb84613ff9f9 Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Wed, 8 Oct 2025 13:18:44 +0200 -Subject: [PATCH 21/33] smbd: Refactor reopen_from_fsp(), factor out name based +Subject: [PATCH 21/66] smbd: Refactor reopen_from_fsp(), factor out name based reopen BUG: https://bugzilla.samba.org/show_bug.cgi?id=15805 @@ -1514,7 +1514,7 @@ index f2cbf7d6bf6..8443ddd32d3 100644 From 1f869b2d814930c4c97a9fff461a3ec2327f4503 Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Wed, 8 Oct 2025 13:53:14 +0200 -Subject: [PATCH 22/33] smbd: Refactor reopen_from_fsp(), factor out +Subject: [PATCH 22/66] smbd: Refactor reopen_from_fsp(), factor out automounter mountpoint check BUG: https://bugzilla.samba.org/show_bug.cgi?id=15805 @@ -1617,7 +1617,7 @@ index 8443ddd32d3..67bb9774429 100644 From d1f0323322b0c70998e901ad79aafcce795646a4 Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Wed, 8 Oct 2025 14:17:27 +0200 -Subject: [PATCH 23/33] smbd: Refactor reopen_from_fsp(), factor out pathref +Subject: [PATCH 23/66] smbd: Refactor reopen_from_fsp(), factor out pathref based Best viewed ignoring white space changes @@ -1776,7 +1776,7 @@ index 67bb9774429..69c78ff563c 100644 From 55bddf76fe649ca7ba30f0645a199255f2698a70 Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Wed, 8 Oct 2025 17:09:22 +0200 -Subject: [PATCH 24/33] smbd: Fix crossing direct automounter mount points +Subject: [PATCH 24/66] smbd: Fix crossing direct automounter mount points The workaround implemented in commit ac7a16f9cc4bd97ef546d1b7b02605991000d0f9 to trigger automounts does not work for direct automounts (either with @@ -1912,7 +1912,7 @@ index 69c78ff563c..ea400c41aa8 100644 From 4872b32c28d540618462ee186e91716c9ee3807c Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Fri, 2 May 2025 11:57:30 +0200 -Subject: [PATCH 25/33] vfs:aio_pthread: Handle VFS_OPEN_HOW_RESOLVE_NO_XDEV +Subject: [PATCH 25/66] vfs:aio_pthread: Handle VFS_OPEN_HOW_RESOLVE_NO_XDEV flag This module uses openat() instead of openat2() so the flag won't be used and @@ -1968,7 +1968,7 @@ index bd0c94b8cce..afbaaedf7b5 100644 From e11fa05e10b1a5fadd7c73c87dcb68b048c73878 Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Fri, 2 May 2025 12:11:01 +0200 -Subject: [PATCH 26/33] vfs:ceph: Allow VFS_OPEN_HOW_RESOLVE_NO_XDEV flag +Subject: [PATCH 26/66] vfs:ceph: Allow VFS_OPEN_HOW_RESOLVE_NO_XDEV flag Don't return ENOSYS if the flag is set. It will be ignored, does not make sense in a ceph virtual filesystem. @@ -2003,7 +2003,7 @@ index 8ea7eb09099..4add6cf993e 100644 From 4cd5f6c4c39ac172e3e3e9e2c11fa9be06f2ed38 Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Tue, 30 Sep 2025 10:32:36 +0200 -Subject: [PATCH 27/33] vfs:ceph_new: Allow VFS_OPEN_HOW_RESOLVE_NO_XDEV flag +Subject: [PATCH 27/66] vfs:ceph_new: Allow VFS_OPEN_HOW_RESOLVE_NO_XDEV flag Don't return ENOSYS if the flag is set. It will be ignored, does not make sense in a ceph filesystem. @@ -2038,7 +2038,7 @@ index 6ea8e56c155..37cff6fe051 100644 From 8f4ad32afca03b564d00a5a6795d4b4a50ae8c66 Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Thu, 9 Oct 2025 12:30:17 +0200 -Subject: [PATCH 28/33] vfs:glusterfs: Allow VFS_OPEN_HOW_RESOLVE_NO_XDEV +Subject: [PATCH 28/66] vfs:glusterfs: Allow VFS_OPEN_HOW_RESOLVE_NO_XDEV Don't return ENOSYS if the flag is set. It will be ignored as does not make sense in this module. @@ -2073,7 +2073,7 @@ index 63dc7a30b04..4d7d96f2888 100644 From b41558ebe158f413d2cfa71478c3eeb5e1a201ec Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Fri, 2 May 2025 13:21:52 +0200 -Subject: [PATCH 29/33] vfs:shadow_copy2: Allow RESOLVE_NO_XDEV flag +Subject: [PATCH 29/66] vfs:shadow_copy2: Allow RESOLVE_NO_XDEV flag This module updates the path and calls the next VFS module. @@ -2107,7 +2107,7 @@ index 449d08e8830..465a0f9d28e 100644 From 49ac956d858b455291edd1330a8d428209728ea0 Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Thu, 9 Oct 2025 12:52:11 +0200 -Subject: [PATCH 30/33] vfs:streams_depot: Allow VFS_OPEN_HOW_RESOLVE_NO_XDEV +Subject: [PATCH 30/66] vfs:streams_depot: Allow VFS_OPEN_HOW_RESOLVE_NO_XDEV flag The flag is passed down the modules stack. @@ -2142,7 +2142,7 @@ index 19b9356fd57..bcf5acf79b4 100644 From 7b875e245ec205ee083c5c4158933e0e42e8a064 Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Thu, 9 Oct 2025 12:59:59 +0200 -Subject: [PATCH 31/33] vfs:fruit: Allow RESOLVE_NO_XDEV flag +Subject: [PATCH 31/66] vfs:fruit: Allow RESOLVE_NO_XDEV flag For stream opens, it returns a fake fd. The streams will be stored by vfs_streams_depot or vfs_streams_xattr. @@ -2177,7 +2177,7 @@ index 4da7c1efa07..812e3a351d2 100644 From 97fb984b6c5c5fe1c562cd23980ea8e110438577 Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Thu, 9 Oct 2025 13:05:16 +0200 -Subject: [PATCH 32/33] vfs:streams_xattr: Allow VFS_OPEN_HOW_RESOLVE_NO_XDEV +Subject: [PATCH 32/66] vfs:streams_xattr: Allow VFS_OPEN_HOW_RESOLVE_NO_XDEV The open function returns a fake fd. Extended attributes will be stored by vfs_xattr_tdb or vfs_default. @@ -2215,7 +2215,7 @@ index 58e5b1eb189..410cd793cd9 100644 From 23aa86c38e049eb0f75bdd21d18c670abf2c6134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= Date: Tue, 7 Apr 2026 16:28:05 +0200 -Subject: [PATCH 33/33] smbdotconf: Add "automount fs types" to smb.conf +Subject: [PATCH 33/66] smbdotconf: Add "automount fs types" to smb.conf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @@ -2336,3 +2336,21892 @@ index ea400c41aa8..ae1ce208cba 100644 -- 2.53.0 + +From 1405b4a8f4772d603083535d6db153c9189ae18c Mon Sep 17 00:00:00 2001 +From: Volker Lendecke +Date: Thu, 5 Feb 2026 20:24:12 +0100 +Subject: [PATCH 34/66] CVE-2026-1933 tests: Fix permissions used for creating + reparse points + +SEC_STD_ALL does not lead to fsp->access_mask to include the required +bits. + +Bug: https://bugzilla.samba.org/show_bug.cgi?id=15992 +Signed-off-by: Volker Lendecke +Reviewed-by: Stefan Metzmacher +--- + python/samba/tests/smb3unix.py | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/python/samba/tests/smb3unix.py b/python/samba/tests/smb3unix.py +index 075b2a07b17..3039a68a1cd 100644 +--- a/python/samba/tests/smb3unix.py ++++ b/python/samba/tests/smb3unix.py +@@ -446,7 +446,7 @@ class Smb3UnixTests(samba.tests.libsmb.LibsmbTests): + + wire_mode = libsmb.unix_mode_to_wire(0o600) + f,_,cc_out = c.create_ex('\\reparse', +- DesiredAccess=security.SEC_STD_ALL, ++ DesiredAccess=security.SEC_FILE_WRITE_ATTRIBUTE, + CreateDisposition=libsmb.FILE_CREATE, + CreateContexts=[posix_context(wire_mode)]) + +@@ -460,7 +460,7 @@ class Smb3UnixTests(samba.tests.libsmb.LibsmbTests): + + wire_mode = libsmb.unix_mode_to_wire(0o600) + f,_,cc_out = c.create_ex('\\reparse', +- DesiredAccess=security.SEC_STD_ALL, ++ DesiredAccess=security.SEC_FILE_WRITE_ATTRIBUTE, + CreateDisposition=libsmb.FILE_OPEN, + CreateContexts=[posix_context(wire_mode)]) + c.close(f) +-- +2.53.0 + + +From 6af47933d060e03ab1cceff2652658c2dd230872 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Mon, 2 Feb 2026 11:43:37 +0100 +Subject: [PATCH 35/66] CVE-2026-1933 smbd: Add access checks to reparse point + operations + +On a share marked "read only = yes" and on file handles opened R/O +users can set or delete the reparse point xattrs on files that the +user has write-access in the file system for. Add the required access +checks. + +Thanks to Asim Viladi Oglu Manizada for reporting the issue. + +Bug: https://bugzilla.samba.org/show_bug.cgi?id=15992 +Signed-off-by: Stefan Metzmacher +Reviewed-by: Volker Lendecke +--- + source3/modules/util_reparse.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/source3/modules/util_reparse.c b/source3/modules/util_reparse.c +index 60373d7fd4e..75aa745e070 100644 +--- a/source3/modules/util_reparse.c ++++ b/source3/modules/util_reparse.c +@@ -320,6 +320,14 @@ NTSTATUS fsctl_set_reparse_point(struct files_struct *fsp, + return NT_STATUS_ACCESS_DENIED; + } + ++ if ((fsp->fsp_name->twrp != 0) || ++ ((fsp->access_mask & ++ (SEC_FILE_WRITE_DATA | SEC_FILE_WRITE_ATTRIBUTE)) == 0)) ++ { ++ DBG_DEBUG("Access denied on a readonly handle\n"); ++ return NT_STATUS_ACCESS_DENIED; ++ } ++ + status = reparse_buffer_check(in_data, + in_len, + &reparse_tag, +@@ -390,6 +398,14 @@ NTSTATUS fsctl_del_reparse_point(struct files_struct *fsp, + uint32_t dos_mode; + int ret; + ++ if ((fsp->fsp_name->twrp != 0) || ++ ((fsp->access_mask & ++ (SEC_FILE_WRITE_DATA | SEC_FILE_WRITE_ATTRIBUTE)) == 0)) ++ { ++ DBG_DEBUG("Access denied on a readonly handle\n"); ++ return NT_STATUS_ACCESS_DENIED; ++ } ++ + status = fsctl_get_reparse_tag(fsp, &existing_tag); + if (!NT_STATUS_IS_OK(status)) { + return status; +-- +2.53.0 + + +From aed5ff8b967502a40eefe01f85e292c622931551 Mon Sep 17 00:00:00 2001 +From: Douglas Bagnall +Date: Thu, 19 Feb 2026 12:50:38 +1300 +Subject: [PATCH 36/66] CVE-2026-2340: test whether vfs_worm allows overwrite + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15997 + +Signed-off-by: Douglas Bagnall +Reviewed-by: Volker Lendecke +--- + selftest/knownfail.d/vfs-worm | 2 ++ + source3/script/tests/test_worm.sh | 30 ++++++++++++++++++++++++++++++ + 2 files changed, 32 insertions(+) + create mode 100644 selftest/knownfail.d/vfs-worm + +diff --git a/selftest/knownfail.d/vfs-worm b/selftest/knownfail.d/vfs-worm +new file mode 100644 +index 00000000000..f4a330c744b +--- /dev/null ++++ b/selftest/knownfail.d/vfs-worm +@@ -0,0 +1,2 @@ ++^samba3.blackbox.worm.SMB3 ++^samba3.blackbox.worm.NT1 +diff --git a/source3/script/tests/test_worm.sh b/source3/script/tests/test_worm.sh +index f96c8ec7e47..d38488cb790 100755 +--- a/source3/script/tests/test_worm.sh ++++ b/source3/script/tests/test_worm.sh +@@ -40,6 +40,7 @@ do_cleanup() + #subshell. + cd "$share_test_dir" || return + rm -f must-be-deleted must-not-be-deleted must-be-deleted-after-ctime-refresh ++ rm -f must-not-be-overwritten sentinel-value + ) + rm -f $tmpfile + } +@@ -51,6 +52,10 @@ do_cleanup + + tmpfile=$PREFIX/smbclient_interactive_prompt_commands + ++tmp_sentinel=$PREFIX/sentinel_value ++SENTINEL_VALUE='1' ++echo $SENTINEL_VALUE > $tmp_sentinel ++ + test_worm() + { + # use echo because helo scripts don't support variables +@@ -58,6 +63,7 @@ test_worm() + put $tmpfile must-be-deleted + put $tmpfile must-be-deleted-after-ctime-refresh + put $tmpfile must-not-be-deleted ++put $tmpfile must-not-be-overwritten + del must-be-deleted + quit" > $tmpfile + # make sure the directory is not too old for worm: +@@ -97,6 +103,30 @@ quit" > $tmpfile + printf "$0: ERROR: must-not-be-deleted WAS deleted\n" + return 1 + } ++ ++ # Check we can't change a protected file by renaming over it. ++ # The source file needs to recently created or access will be ++ # denied before RENAME_AT is reached, which is the thing we ++ # want to test. ++ original_contents=`cat $share_test_dir/must-not-be-overwritten` ++ echo " ++put $tmp_sentinel sentinel-value ++rename sentinel-value must-not-be-overwritten -f ++quit" > $tmpfile ++ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U$USERNAME%$PASSWORD //$SERVER/worm -I$SERVER_IP $ADDARGS < $tmpfile 2>&1' ++ eval echo "$cmd" ++ out=$(eval "$cmd") ++ new_contents=`cat $share_test_dir/must-not-be-overwritten` ++ ++ if [ "$new_contents" = "$SENTINEL_VALUE" ]; then ++ echo "must-not-be-overwritten was overwritten" ++ return 1 ++ fi ++ if [ "$new_contents" != "$original_contents" ]; then ++ echo "must-not-be-overwritten was changed (but not precisely overwritten)" ++ return 1 ++ fi ++ + # if we're not root, return here: + test "$UID" = "0" || { + return 0 +-- +2.53.0 + + +From c8e94fe47601143a86d7b9362313e77d4d19fd2d Mon Sep 17 00:00:00 2001 +From: Pavel Kohout +Date: Fri, 13 Feb 2026 15:51:41 +1300 +Subject: [PATCH 37/66] CVE-2026-2340: vfs_worm: Check destination WORM status + in rename + +vfs_worm_renameat() only checked if the source file was WORM-protected, +but not the destination. This allowed overwriting immutable files via +SMB2 rename with ReplaceIfExists=1, bypassing WORM protection. + +Add destination check using FSTATAT on the destination dirfsp, as +suggested by the maintainer. + +CWE-284 (Improper Access Control) + +Reported-by: Pavel Kohout, Aisle Research, www.aisle.com + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15997 + +To backport to 4.23 we change the name of dst_dirfsp and src_dirfsp to +dstfsp and srcfsp, respectively (accounting for +76796180cf3af3252db2c29d0e95282a498a8527 in 4.24/master). + +Signed-off-by: Pavel Kohout +Reviewed-by: Volker Lendecke +Reviewed-by: Douglas Bagnall +--- + selftest/knownfail.d/vfs-worm | 2 -- + source3/modules/vfs_worm.c | 26 ++++++++++++++++++++++++-- + 2 files changed, 24 insertions(+), 4 deletions(-) + delete mode 100644 selftest/knownfail.d/vfs-worm + +diff --git a/selftest/knownfail.d/vfs-worm b/selftest/knownfail.d/vfs-worm +deleted file mode 100644 +index f4a330c744b..00000000000 +--- a/selftest/knownfail.d/vfs-worm ++++ /dev/null +@@ -1,2 +0,0 @@ +-^samba3.blackbox.worm.SMB3 +-^samba3.blackbox.worm.NT1 +diff --git a/source3/modules/vfs_worm.c b/source3/modules/vfs_worm.c +index 0fcda162cd7..a1dca280279 100644 +--- a/source3/modules/vfs_worm.c ++++ b/source3/modules/vfs_worm.c +@@ -218,13 +218,35 @@ static int vfs_worm_renameat(vfs_handle_struct *handle, + const struct smb_filename *smb_fname_dst, + const struct vfs_rename_how *how) + { ++ struct stat_ex dst_st; ++ int ret; ++ + if (is_readonly(handle, smb_fname_src)) { + errno = EACCES; + return -1; + } + +- return SMB_VFS_NEXT_RENAMEAT( +- handle, srcfsp, smb_fname_src, dstfsp, smb_fname_dst, how); ++ /* Check if destination is WORM-protected (fixes CVE-2026-2340) */ ++ ret = SMB_VFS_FSTATAT(handle->conn, ++ dstfsp, ++ smb_fname_dst, ++ &dst_st, ++ AT_SYMLINK_NOFOLLOW); ++ if (ret == 0) { ++ struct smb_filename dst_with_stat = *smb_fname_dst; ++ dst_with_stat.st = dst_st; ++ if (is_readonly(handle, &dst_with_stat)) { ++ errno = EACCES; ++ return -1; ++ } ++ } ++ ++ return SMB_VFS_NEXT_RENAMEAT(handle, ++ srcfsp, ++ smb_fname_src, ++ dstfsp, ++ smb_fname_dst, ++ how); + } + + static int vfs_worm_fsetxattr(struct vfs_handle_struct *handle, +-- +2.53.0 + + +From 1af2b41cd7ae0d1b65f6ba44a29a2500a0177d24 Mon Sep 17 00:00:00 2001 +From: Douglas Bagnall +Date: Fri, 27 Feb 2026 11:30:40 +1300 +Subject: [PATCH 38/66] CVE-2026-3012: gpo tests: fix test cleanup + +These tests are going to fail soon but as currently written they do +not clean up after themselves, erroring instead of failing and causing +cascading errors in subsequent tests. For now we don't care to make +the other tests less fragile. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16003 + +Signed-off-by: Douglas Bagnall +Reviewed-by: Jennifer Sutton +--- + python/samba/tests/gpo.py | 42 +++++++++++++++++++++++---------------- + 1 file changed, 25 insertions(+), 17 deletions(-) + +diff --git a/python/samba/tests/gpo.py b/python/samba/tests/gpo.py +index 2e4696cd926..0972cd2f63c 100644 +--- a/python/samba/tests/gpo.py ++++ b/python/samba/tests/gpo.py +@@ -6951,6 +6951,7 @@ class GPOTests(tests.TestCase): + confdn = 'CN=Public Key Services,CN=Services,CN=Configuration,%s' % base_dn + ca_cn = '%s-CA' % hostname.replace('.', '-') + certa_dn = 'CN=%s,CN=Certification Authorities,%s' % (ca_cn, confdn) ++ self.addCleanup(ldb.delete, certa_dn) + ldb.add({'dn': certa_dn, + 'objectClass': 'certificationAuthority', + 'authorityRevocationList': ['XXX'], +@@ -6959,6 +6960,7 @@ class GPOTests(tests.TestCase): + }) + # Write the dummy pKIEnrollmentService + enroll_dn = 'CN=%s,CN=Enrollment Services,%s' % (ca_cn, confdn) ++ self.addCleanup(ldb.delete, enroll_dn) + ldb.add({'dn': enroll_dn, + 'objectClass': 'pKIEnrollmentService', + 'cACertificate': dummy_certificate(), +@@ -6967,6 +6969,7 @@ class GPOTests(tests.TestCase): + }) + # Write the dummy pKICertificateTemplate + template_dn = 'CN=Machine,CN=Certificate Templates,%s' % confdn ++ self.addCleanup(ldb.delete, template_dn) + ldb.add({'dn': template_dn, + 'objectClass': 'pKICertificateTemplate', + }) +@@ -7012,11 +7015,6 @@ class GPOTests(tests.TestCase): + self.assertNotIn(b'Workstation', out, + 'Workstation certificate not removed') + +- # Remove the dummy CA, pKIEnrollmentService, and pKICertificateTemplate +- ldb.delete(certa_dn) +- ldb.delete(enroll_dn) +- ldb.delete(template_dn) +- + # Unstage the Registry.pol file + unstage_file(reg_pol) + +@@ -7027,6 +7025,7 @@ class GPOTests(tests.TestCase): + 'MACHINE/REGISTRY.POL') + cache_dir = self.lp.get('cache directory') + store = GPOStorage(os.path.join(cache_dir, 'gpo.tdb')) ++ self.addCleanup(store.log.close) + + machine_creds = Credentials() + machine_creds.guess(self.lp) +@@ -7059,6 +7058,7 @@ class GPOTests(tests.TestCase): + confdn = 'CN=Public Key Services,CN=Services,CN=Configuration,%s' % base_dn + ca_cn = '%s-CA' % hostname.replace('.', '-') + certa_dn = 'CN=%s,CN=Certification Authorities,%s' % (ca_cn, confdn) ++ self.addCleanup(ldb.delete, certa_dn) + ldb.add({'dn': certa_dn, + 'objectClass': 'certificationAuthority', + 'authorityRevocationList': ['XXX'], +@@ -7067,6 +7067,7 @@ class GPOTests(tests.TestCase): + }) + # Write the dummy pKIEnrollmentService + enroll_dn = 'CN=%s,CN=Enrollment Services,%s' % (ca_cn, confdn) ++ self.addCleanup(ldb.delete, enroll_dn) + ldb.add({'dn': enroll_dn, + 'objectClass': 'pKIEnrollmentService', + 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I', +@@ -7075,12 +7076,16 @@ class GPOTests(tests.TestCase): + }) + # Write the dummy pKICertificateTemplate + template_dn = 'CN=Machine,CN=Certificate Templates,%s' % confdn ++ self.addCleanup(ldb.delete, template_dn) + ldb.add({'dn': template_dn, + 'objectClass': 'pKICertificateTemplate', + }) + + with TemporaryDirectory() as dname: +- ext.process_group_policy([], gpos, dname, dname) ++ try: ++ ext.process_group_policy([], gpos, dname, dname) ++ except Exception as e: ++ self.fail(f"process_group_policy() raised {e}") + ca_crt = os.path.join(dname, '%s.crt' % ca_cn) + self.assertTrue(os.path.exists(ca_crt), + 'Root CA certificate was not requested') +@@ -7169,11 +7174,6 @@ class GPOTests(tests.TestCase): + self.assertNotIn(b'Workstation', out, + 'Workstation certificate not removed') + +- # Remove the dummy CA, pKIEnrollmentService, and pKICertificateTemplate +- ldb.delete(certa_dn) +- ldb.delete(enroll_dn) +- ldb.delete(template_dn) +- + # Unstage the Registry.pol file + unstage_file(reg_pol) + +@@ -7626,6 +7626,7 @@ class GPOTests(tests.TestCase): + 'MACHINE/REGISTRY.POL') + cache_dir = self.lp.get('cache directory') + store = GPOStorage(os.path.join(cache_dir, 'gpo.tdb')) ++ self.addCleanup(store.log.close) + + machine_creds = Credentials() + machine_creds.guess(self.lp) +@@ -7667,6 +7668,8 @@ class GPOTests(tests.TestCase): + confdn = 'CN=Public Key Services,CN=Services,CN=Configuration,%s' % base_dn + ca_cn = '%s-CA' % hostname.replace('.', '-') + certa_dn = 'CN=%s,CN=Certification Authorities,%s' % (ca_cn, confdn) ++ self.addCleanup(ldb.delete, certa_dn) ++ + ldb.add({'dn': certa_dn, + 'objectClass': 'certificationAuthority', + 'authorityRevocationList': ['XXX'], +@@ -7675,6 +7678,7 @@ class GPOTests(tests.TestCase): + }) + # Write the dummy pKIEnrollmentService + enroll_dn = 'CN=%s,CN=Enrollment Services,%s' % (ca_cn, confdn) ++ self.addCleanup(ldb.delete, enroll_dn) + ldb.add({'dn': enroll_dn, + 'objectClass': 'pKIEnrollmentService', + 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I', +@@ -7683,12 +7687,21 @@ class GPOTests(tests.TestCase): + }) + # Write the dummy pKICertificateTemplate + template_dn = 'CN=Machine,CN=Certificate Templates,%s' % confdn ++ try: ++ ldb.delete(template_dn) ++ except _ldb.LdbError: ++ pass ++ ++ self.addCleanup(ldb.delete, template_dn) + ldb.add({'dn': template_dn, + 'objectClass': 'pKICertificateTemplate', + }) + + with TemporaryDirectory() as dname: +- ext.process_group_policy([], gpos, dname, dname) ++ try: ++ ext.process_group_policy([], gpos, dname, dname) ++ except Exception as e: ++ self.fail(f"process_group_policy() raised {e}") + ca_list = [ca_cn, 'example0-com-CA', 'example1-com-CA', + 'example2-com-CA'] + for ca in ca_list: +@@ -7751,11 +7764,6 @@ class GPOTests(tests.TestCase): + self.assertNotIn(b'Workstation', out, + 'Workstation certificate not removed') + +- # Remove the dummy CA, pKIEnrollmentService, and pKICertificateTemplate +- ldb.delete(certa_dn) +- ldb.delete(enroll_dn) +- ldb.delete(template_dn) +- + # Unstage the Registry.pol file + unstage_file(reg_pol) + +-- +2.53.0 + + +From eb449819e1acaa88249f398e15a19ede9adf49b9 Mon Sep 17 00:00:00 2001 +From: Douglas Bagnall +Date: Mon, 23 Feb 2026 11:01:57 +1300 +Subject: [PATCH 39/66] CVE-2026-3012: do not fetch certificate over http + +In the case where a certificate was found via HTTP, it was trusted +without verification and put in the global CA store. + +There is no means to check the certificate other than by comparing it +to certificates we may have gathered via LDAP, but in that case there +is no advantage over just using the LDAP-derived certificates. + +Using the LDAP certificates was already the fallback case if HTTP +failed, so we just make it the default. + +The HTTP fetch depends on the NDES service, which is a variant of +Simple Certificate Enrolment Protocol (SCEP, RFC8894), but in fact +Samba implements none of that protocol other than the HTTP fetch. SCEP +is for clients that are not true domain members. Domain members can +access to certificates over LDAP. This patch is not reducing SCEP +client support because Samba never had it. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16003 + +Reported-by: Arad Inbar, DREAM Security Research Team +Reported-by: Nir Somech, DREAM Security Research Team +Reported-by: Ben Grinberg, DREAM Security Research Team + +Signed-off-by: Douglas Bagnall +Reviewed-by: Jennifer Sutton +--- + python/samba/gp/gp_cert_auto_enroll_ext.py | 54 ++++------------------ + selftest/knownfail.d/gpo-auto-enrol | 2 + + 2 files changed, 11 insertions(+), 45 deletions(-) + create mode 100644 selftest/knownfail.d/gpo-auto-enrol + +diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py +index 877659b043e..815436e11e9 100644 +--- a/python/samba/gp/gp_cert_auto_enroll_ext.py ++++ b/python/samba/gp/gp_cert_auto_enroll_ext.py +@@ -16,7 +16,6 @@ + + import os + import operator +-import requests + from samba.gp.gpclass import gp_pol_ext, gp_applier, GPOSTATE + from samba import Ldb + from samba.dcerpc import misc +@@ -195,58 +194,24 @@ def get_supported_templates(server): + return out.strip().split() + + +-def getca(ca, url, trust_dir): +- """Fetch Certificate Chain from the CA.""" ++def getca(ca, trust_dir): ++ """Fetch a certificate from LDAP.""" + root_cert = os.path.join(trust_dir, '%s.crt' % ca['name']) + root_certs = [] +- +- try: +- r = requests.get(url=url, params={'operation': 'GetCACert', +- 'message': 'CAIdentifier'}) +- except requests.exceptions.ConnectionError: +- log.warn('Could not connect to Network Device Enrollment Service.') +- r = None +- if r is None or r.content == b'' or r.headers['Content-Type'] == 'text/html': +- log.warn('Unable to fetch root certificates (requires NDES).') +- if 'cACertificate' in ca: +- log.warn('Installing the server certificate only.') +- der_certificate = base64.b64decode(ca['cACertificate']) +- try: +- cert = load_der_x509_certificate(der_certificate) +- except TypeError: +- cert = load_der_x509_certificate(der_certificate, +- default_backend()) +- cert_data = cert.public_bytes(Encoding.PEM) +- with open(root_cert, 'wb') as w: +- w.write(cert_data) +- root_certs.append(root_cert) +- return root_certs +- +- if r.headers['Content-Type'] == 'application/x-x509-ca-cert': +- # Older versions of load_der_x509_certificate require a backend param ++ if 'cACertificate' in ca: ++ log.warn('Installing the server certificate only.') ++ der_certificate = base64.b64decode(ca['cACertificate']) + try: +- cert = load_der_x509_certificate(r.content) ++ cert = load_der_x509_certificate(der_certificate) + except TypeError: +- cert = load_der_x509_certificate(r.content, default_backend()) ++ cert = load_der_x509_certificate(der_certificate, ++ default_backend()) + cert_data = cert.public_bytes(Encoding.PEM) + with open(root_cert, 'wb') as w: + w.write(cert_data) + root_certs.append(root_cert) +- elif r.headers['Content-Type'] == 'application/x-x509-ca-ra-cert': +- certs = load_der_pkcs7_certificates(r.content) +- for i in range(0, len(certs)): +- cert = certs[i].public_bytes(Encoding.PEM) +- filename, extension = root_cert.rsplit('.', 1) +- dest = '%s.%d.%s' % (filename, i, extension) +- with open(dest, 'wb') as w: +- w.write(cert) +- root_certs.append(dest) +- else: +- log.warn('getca: Wrong (or missing) MIME content type') +- + return root_certs + +- + def find_global_trust_dir(): + """Return the global trust dir using known paths from various Linux distros.""" + for trust_dir in global_trust_dirs: +@@ -266,11 +231,10 @@ def changed(new_data, old_data): + def cert_enroll(ca, ldb, trust_dir, private_dir, auth='Kerberos'): + """Install the root certificate chain.""" + data = dict({'files': [], 'templates': []}, **ca) +- url = 'http://%s/CertSrv/mscep/mscep.dll/pkiclient.exe?' % ca['hostname'] + + log.info("Try to get root or server certificates") + +- root_certs = getca(ca, url, trust_dir) ++ root_certs = getca(ca, trust_dir) + data['files'].extend(root_certs) + global_trust_dir = find_global_trust_dir() + for src in root_certs: +diff --git a/selftest/knownfail.d/gpo-auto-enrol b/selftest/knownfail.d/gpo-auto-enrol +new file mode 100644 +index 00000000000..4bf4b8e3c72 +--- /dev/null ++++ b/selftest/knownfail.d/gpo-auto-enrol +@@ -0,0 +1,2 @@ ++^samba\.tests\.gpo\.samba\.tests\.gpo\.GPOTests\.test_advanced_gp_cert_auto_enroll_ext\(ad_dc:local\) ++^samba\.tests\.gpo\.samba\.tests\.gpo\.GPOTests\.test_gp_cert_auto_enroll_ext\(ad_dc:local\) +-- +2.53.0 + + +From a8fc1666366716af45336f32af48826cefd5435e Mon Sep 17 00:00:00 2001 +From: Douglas Bagnall +Date: Thu, 26 Feb 2026 14:21:01 +1300 +Subject: [PATCH 40/66] CVE-2026-3012: gp_auto_enrol: skip CAs not found in + LDAP + +If a certificate is mentioned in a GPO but is not present as a +cACertificate attribute on a pKIEnrollmentService object, we have no way +of obtaining it, so we might as well forget it. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16003 + +Signed-off-by: Douglas Bagnall +Reviewed-by: Jennifer Sutton +--- + python/samba/gp/gp_cert_auto_enroll_ext.py | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py +index 815436e11e9..de8b310afd9 100644 +--- a/python/samba/gp/gp_cert_auto_enroll_ext.py ++++ b/python/samba/gp/gp_cert_auto_enroll_ext.py +@@ -452,11 +452,21 @@ class gp_cert_auto_enroll_ext(gp_pol_ext, gp_applier): + # This is a basic configuration. + cas = fetch_certification_authorities(ldb) + for _ca in cas: ++ if 'cACertificate' not in _ca: ++ log.warning(f"ignoring CA '{_ca['name']}' with no " ++ "cACertificate in LDAP.") ++ continue ++ + self.apply(guid, _ca, cert_enroll, _ca, ldb, trust_dir, + private_dir) + ca_names.append(_ca['name']) + # If EndPoint.URI starts with "HTTPS//": + elif ca['URL'].lower().startswith('https://'): ++ if 'cACertificate' not in ca: ++ log.warning(f"ignoring CA '{ca['name']}' " ++ f"({ca['URL']}) with no " ++ "cACertificate in LDAP.") ++ continue + self.apply(guid, ca, cert_enroll, ca, ldb, trust_dir, + private_dir, auth=ca['auth']) + ca_names.append(ca['name']) +-- +2.53.0 + + +From cd0a831279bbe890b64898f5a1892bf8c73cfca7 Mon Sep 17 00:00:00 2001 +From: Douglas Bagnall +Date: Fri, 27 Feb 2026 14:46:04 +1300 +Subject: [PATCH 41/66] CVE-2026-3012: gpo tests should use real certificates + +Or at least, more real than a short arbitrary byte string, so that +the certificates can be parsed. + +This shows that certificate enrolment works via LDAP in the situations +where we would have fetched them via HTTP. + +This does not fix the advanced_gp_cert_auto_enroll_ext test which +wants to install certificates it has no access too. This will not be +fixed in the security release. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16003 + +Signed-off-by: Douglas Bagnall +Reviewed-by: Jennifer Sutton +--- + python/samba/tests/gpo.py | 8 ++++---- + selftest/knownfail.d/gpo-auto-enrol | 1 - + 2 files changed, 4 insertions(+), 5 deletions(-) + +diff --git a/python/samba/tests/gpo.py b/python/samba/tests/gpo.py +index 0972cd2f63c..5bdee29b50a 100644 +--- a/python/samba/tests/gpo.py ++++ b/python/samba/tests/gpo.py +@@ -7062,7 +7062,7 @@ class GPOTests(tests.TestCase): + ldb.add({'dn': certa_dn, + 'objectClass': 'certificationAuthority', + 'authorityRevocationList': ['XXX'], +- 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I', ++ 'cACertificate': dummy_certificate(), + 'certificateRevocationList': ['XXX'], + }) + # Write the dummy pKIEnrollmentService +@@ -7070,7 +7070,7 @@ class GPOTests(tests.TestCase): + self.addCleanup(ldb.delete, enroll_dn) + ldb.add({'dn': enroll_dn, + 'objectClass': 'pKIEnrollmentService', +- 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I', ++ 'cACertificate': dummy_certificate(), + 'certificateTemplates': ['Machine'], + 'dNSHostName': hostname, + }) +@@ -7673,7 +7673,7 @@ class GPOTests(tests.TestCase): + ldb.add({'dn': certa_dn, + 'objectClass': 'certificationAuthority', + 'authorityRevocationList': ['XXX'], +- 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I', ++ 'cACertificate': dummy_certificate(), + 'certificateRevocationList': ['XXX'], + }) + # Write the dummy pKIEnrollmentService +@@ -7681,7 +7681,7 @@ class GPOTests(tests.TestCase): + self.addCleanup(ldb.delete, enroll_dn) + ldb.add({'dn': enroll_dn, + 'objectClass': 'pKIEnrollmentService', +- 'cACertificate': b'0\x82\x03u0\x82\x02]\xa0\x03\x02\x01\x02\x02\x10I', ++ 'cACertificate': dummy_certificate(), + 'certificateTemplates': ['Machine'], + 'dNSHostName': hostname, + }) +diff --git a/selftest/knownfail.d/gpo-auto-enrol b/selftest/knownfail.d/gpo-auto-enrol +index 4bf4b8e3c72..4b787a5ac86 100644 +--- a/selftest/knownfail.d/gpo-auto-enrol ++++ b/selftest/knownfail.d/gpo-auto-enrol +@@ -1,2 +1 @@ + ^samba\.tests\.gpo\.samba\.tests\.gpo\.GPOTests\.test_advanced_gp_cert_auto_enroll_ext\(ad_dc:local\) +-^samba\.tests\.gpo\.samba\.tests\.gpo\.GPOTests\.test_gp_cert_auto_enroll_ext\(ad_dc:local\) +-- +2.53.0 + + +From 39970373fe70615e28b0fe6e56169b0e3674b95a Mon Sep 17 00:00:00 2001 +From: Volker Lendecke +Date: Tue, 24 Feb 2026 16:11:15 +0100 +Subject: [PATCH 42/66] CVE-2026-3238 winsserver4: Dissolve direct variable + initialization + +Checks are required before the packet is dereferenced + +Bug: https://bugzilla.samba.org/show_bug.cgi?id=16012 +Signed-off-by: Volker Lendecke +Reviewed-by: Douglas Bagnall +--- + source4/nbt_server/wins/winsserver.c | 27 +++++++++++++++++++++------ + 1 file changed, 21 insertions(+), 6 deletions(-) + +diff --git a/source4/nbt_server/wins/winsserver.c b/source4/nbt_server/wins/winsserver.c +index 6679961dc03..1b7fe5641a6 100644 +--- a/source4/nbt_server/wins/winsserver.c ++++ b/source4/nbt_server/wins/winsserver.c +@@ -460,16 +460,27 @@ static void nbtd_winsserver_register(struct nbt_name_socket *nbtsock, + struct nbtd_interface *iface = talloc_get_type(nbtsock->incoming.private_data, + struct nbtd_interface); + struct wins_server *winssrv = iface->nbtsrv->winssrv; +- struct nbt_name *name = &packet->questions[0].name; ++ struct nbt_name *name = NULL; + struct winsdb_record *rec; + uint8_t rcode = NBT_RCODE_OK; +- uint16_t nb_flags = packet->additional[0].rdata.netbios.addresses[0].nb_flags; +- const char *address = packet->additional[0].rdata.netbios.addresses[0].ipaddr; ++ struct nbt_res_rec *additional = NULL; ++ uint16_t nb_flags; ++ const char *address = NULL; ++ struct nbt_rdata_address *addresses = NULL; + bool mhomed = ((packet->operation & NBT_OPCODE) == NBT_OPCODE_MULTI_HOME_REG); +- enum wrepl_name_type new_type = wrepl_type(nb_flags, name, mhomed); ++ enum wrepl_name_type new_type; + struct winsdb_addr *winsdb_addr = NULL; + bool duplicate_packet; + ++ name = &packet->questions[0].name; ++ additional = packet->additional; ++ ++ addresses = additional[0].rdata.netbios.addresses; ++ ++ nb_flags = addresses[0].nb_flags; ++ address = addresses[0].ipaddr; ++ new_type = wrepl_type(nb_flags, name, mhomed); ++ + /* + * as a special case, the local master browser name is always accepted + * for registration, but never stored, but w2k3 stores it if it's registered +@@ -729,13 +740,15 @@ static void nbtd_winsserver_query(struct loadparm_context *lp_ctx, + struct nbtd_interface *iface = talloc_get_type(nbtsock->incoming.private_data, + struct nbtd_interface); + struct wins_server *winssrv = iface->nbtsrv->winssrv; +- struct nbt_name *name = &packet->questions[0].name; ++ struct nbt_name *name = NULL; + struct winsdb_record *rec; + struct winsdb_record *rec_1b = NULL; + const char **addresses; + const char **addresses_1b = NULL; + uint16_t nb_flags = 0; + ++ name = &packet->questions[0].name; ++ + if (name->type == NBT_NAME_MASTER) { + goto notfound; + } +@@ -871,11 +884,13 @@ static void nbtd_winsserver_release(struct nbt_name_socket *nbtsock, + struct nbtd_interface *iface = talloc_get_type(nbtsock->incoming.private_data, + struct nbtd_interface); + struct wins_server *winssrv = iface->nbtsrv->winssrv; +- struct nbt_name *name = &packet->questions[0].name; ++ struct nbt_name *name = NULL; + struct winsdb_record *rec; + uint32_t modify_flags = 0; + uint8_t ret; + ++ name = &packet->questions[0].name; ++ + if (name->type == NBT_NAME_MASTER) { + goto done; + } +-- +2.53.0 + + +From a9c5255d1e46738e8148152721aa49add737e64b Mon Sep 17 00:00:00 2001 +From: Volker Lendecke +Date: Tue, 24 Feb 2026 16:30:46 +0100 +Subject: [PATCH 43/66] CVE-2026-3238 winsserver4: Validate incoming packets + +Avoid NULL pointer dereferences, leading to a crash in the nbt process +serving wins. + +Thanks to Arad Inbar, Erez Cohen, Nir Somech and Ben Grinberg from +DREAM Security Research Team for pointing out this crash bug out to +the Samba team. + +Bug: https://bugzilla.samba.org/show_bug.cgi?id=16012 +Signed-off-by: Volker Lendecke +Reviewed-by: Douglas Bagnall +--- + source4/nbt_server/wins/winsserver.c | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/source4/nbt_server/wins/winsserver.c b/source4/nbt_server/wins/winsserver.c +index 1b7fe5641a6..c637657f07c 100644 +--- a/source4/nbt_server/wins/winsserver.c ++++ b/source4/nbt_server/wins/winsserver.c +@@ -472,9 +472,16 @@ static void nbtd_winsserver_register(struct nbt_name_socket *nbtsock, + struct winsdb_addr *winsdb_addr = NULL; + bool duplicate_packet; + ++ NBTD_ASSERT_PACKET(packet, src, packet->qdcount > 0); ++ NBTD_ASSERT_PACKET(packet, src, packet->arcount > 0); ++ + name = &packet->questions[0].name; + additional = packet->additional; + ++ NBTD_ASSERT_PACKET(packet, ++ src, ++ additional[0].rdata.netbios.length > 0); ++ + addresses = additional[0].rdata.netbios.addresses; + + nb_flags = addresses[0].nb_flags; +@@ -747,6 +754,8 @@ static void nbtd_winsserver_query(struct loadparm_context *lp_ctx, + const char **addresses_1b = NULL; + uint16_t nb_flags = 0; + ++ NBTD_ASSERT_PACKET(packet, src, packet->qdcount > 0); ++ + name = &packet->questions[0].name; + + if (name->type == NBT_NAME_MASTER) { +@@ -889,6 +898,8 @@ static void nbtd_winsserver_release(struct nbt_name_socket *nbtsock, + uint32_t modify_flags = 0; + uint8_t ret; + ++ NBTD_ASSERT_PACKET(packet, src, packet->qdcount > 0); ++ + name = &packet->questions[0].name; + + if (name->type == NBT_NAME_MASTER) { +-- +2.53.0 + + +From ae5323f5bacb2d461a663cc34ba33c9c0fa05f8e Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Thu, 23 Apr 2026 18:20:15 +0200 +Subject: [PATCH 44/66] CVE-2026-4480/CVE-2026-4408: lib/util: inline + string_sub2() into string_sub() the only caller + +This will simplify further changes. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Douglas Bagnall +--- + lib/util/substitute.c | 20 ++------------------ + 1 file changed, 2 insertions(+), 18 deletions(-) + +diff --git a/lib/util/substitute.c b/lib/util/substitute.c +index b7b5588da86..26362ca77b2 100644 +--- a/lib/util/substitute.c ++++ b/lib/util/substitute.c +@@ -47,10 +47,9 @@ + use of len==0 which was for no length checks to be done. + **/ + +-static void string_sub2(char *s,const char *pattern, const char *insert, size_t len, +- bool remove_unsafe_characters, bool replace_once, +- bool allow_trailing_dollar) ++void string_sub(char *s, const char *pattern, const char *insert, size_t len) + { ++ bool remove_unsafe_characters = true; + char *p; + size_t ls, lp, li, i; + +@@ -79,13 +78,6 @@ static void string_sub2(char *s,const char *pattern, const char *insert, size_t + for (i=0;i +Date: Thu, 23 Apr 2026 18:20:15 +0200 +Subject: [PATCH 45/66] CVE-2026-4480/CVE-2026-4408: lib/util: remove unused + talloc_strdup(insert) from talloc_string_sub2() + +The insert string is not modified, so we do not need to copy it. + +This will simplify further changes. + +Review with: git show --patience + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Douglas Bagnall +--- + lib/util/substitute.c | 57 +++++++++++++++++++------------------------ + 1 file changed, 25 insertions(+), 32 deletions(-) + +diff --git a/lib/util/substitute.c b/lib/util/substitute.c +index 26362ca77b2..4a0c58ab3a7 100644 +--- a/lib/util/substitute.c ++++ b/lib/util/substitute.c +@@ -157,7 +157,7 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src, + bool replace_once, + bool allow_trailing_dollar) + { +- char *p, *in; ++ char *p; + char *s; + char *string; + ssize_t ls,lp,li,ld, i; +@@ -175,22 +175,32 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src, + + s = string; + +- in = talloc_strdup(mem_ctx, insert); +- if (!in) { +- DEBUG(0, ("talloc_string_sub2: ENOMEM\n")); +- talloc_free(string); +- return NULL; +- } + ls = (ssize_t)strlen(s); + lp = (ssize_t)strlen(pattern); + li = (ssize_t)strlen(insert); + ld = li - lp; + +- for (i=0;i 0) { ++ int offset = PTR_DIFF(s,string); ++ string = (char *)talloc_realloc_size(mem_ctx, string, ++ ls + ld + 1); ++ if (!string) { ++ DEBUG(0, ("talloc_string_sub: out of " ++ "memory!\n")); ++ return NULL; ++ } ++ p = string + offset + (p - s); ++ } ++ if (li != lp) { ++ memmove(p+li,p+lp,strlen(p+lp)+1); ++ } ++ for (i=0; i 0) { +- int offset = PTR_DIFF(s,string); +- string = (char *)talloc_realloc_size(mem_ctx, string, +- ls + ld + 1); +- if (!string) { +- DEBUG(0, ("talloc_string_sub: out of " +- "memory!\n")); +- TALLOC_FREE(in); +- return NULL; + } +- p = string + offset + (p - s); +- } +- if (li != lp) { +- memmove(p+li,p+lp,strlen(p+lp)+1); ++ ++ p[i] = insert[i]; + } +- memcpy(p, in, li); + s = p + li; + ls += ld; + +@@ -239,7 +233,6 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src, + break; + } + } +- TALLOC_FREE(in); + return string; + } + +-- +2.53.0 + + +From 1a672e8ba0300ab2067e50fd7bb53a7e3fa7a194 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Thu, 23 Apr 2026 18:20:15 +0200 +Subject: [PATCH 46/66] CVE-2026-4480/CVE-2026-4408: lib/util: factor out a + mask_unsafe_character() helper function + +This moves the logic into a single place and +makes if more flexible to be used with more +values than STRING_SUB_UNSAFE_CHARACTERS. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Douglas Bagnall +--- + lib/util/substitute.c | 109 +++++++++++++++++++++--------------------- + lib/util/substitute.h | 6 ++- + 2 files changed, 60 insertions(+), 55 deletions(-) + +diff --git a/lib/util/substitute.c b/lib/util/substitute.c +index 4a0c58ab3a7..b9fe32e993e 100644 +--- a/lib/util/substitute.c ++++ b/lib/util/substitute.c +@@ -35,6 +35,33 @@ + * @brief Substitute utilities. + **/ + ++static inline ++char mask_unsafe_character(char in, ++ bool is_last, ++ bool allow_trailing_dollar, ++ const char *unsafe_characters, ++ char safe_out) ++{ ++ const char *unsafe = NULL; ++ ++ if (unsafe_characters == NULL) { ++ return in; ++ } ++ ++ /* allow a trailing $ (as in machine accounts) */ ++ if (allow_trailing_dollar && is_last && in == '$') { ++ return in; ++ } ++ ++ unsafe = strchr(unsafe_characters, in); ++ if (unsafe != NULL) { ++ return safe_out; ++ } ++ ++ /* ok */ ++ return in; ++} ++ + /** + Substitute a string for a pattern in another string. Make sure there is + enough room! +@@ -42,14 +69,16 @@ + This routine looks for pattern in s and replaces it with + insert. It may do multiple replacements or just one. + +- Any of " ; ' $ or ` in the insert string are replaced with _ ++ Any of STRING_SUB_UNSAFE_CHARACTERS in the insert string are replaced with _ ++ + if len==0 then the string cannot be extended. This is different from the old + use of len==0 which was for no length checks to be done. + **/ + + void string_sub(char *s, const char *pattern, const char *insert, size_t len) + { +- bool remove_unsafe_characters = true; ++ const char *unsafe_characters = STRING_SUB_UNSAFE_CHARACTERS; ++ char safe_character = '_'; + char *p; + size_t ls, lp, li, i; + +@@ -76,26 +105,18 @@ void string_sub(char *s, const char *pattern, const char *insert, size_t len) + memmove(p+li,p+lp,strlen(p+lp)+1); + } + for (i=0;i + ++#define STRING_SUB_UNSAFE_CHARACTERS "$`\"';%\r\n" ++ + /** + Substitute a string for a pattern in another string. Make sure there is + enough room! +@@ -33,7 +35,9 @@ + This routine looks for pattern in s and replaces it with + insert. It may do multiple replacements. + +- Any of " ; ' $ or ` in the insert string are replaced with _ ++ Any of STRING_SUB_UNSAFE_CHARACTERS (see above) in the ++ insert string are replaced with _ ++ + if len==0 then the string cannot be extended. This is different from the old + use of len==0 which was for no length checks to be done. + **/ +-- +2.53.0 + + +From f311078eae4731ca2af8a14c1d4091e7263ca507 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Thu, 30 Apr 2026 14:48:26 +0200 +Subject: [PATCH 47/66] CVE-2026-4480/CVE-2026-4408: lib/util: split out + realloc_string_sub_raw() + +This will allow realloc_string_sub2() to use it in order +to have the logic in one place only. + +And it will also allow adjacted callers to be +more flexible. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Douglas Bagnall +--- + lib/util/substitute.c | 85 ++++++++++++++++++++++++++++++------------- + lib/util/substitute.h | 18 +++++++++ + 2 files changed, 78 insertions(+), 25 deletions(-) + +diff --git a/lib/util/substitute.c b/lib/util/substitute.c +index b9fe32e993e..465aea86605 100644 +--- a/lib/util/substitute.c ++++ b/lib/util/substitute.c +@@ -171,32 +171,24 @@ _PUBLIC_ void all_string_sub(char *s,const char *pattern,const char *insert, siz + * talloc version of string_sub2. + */ + +-char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src, +- const char *pattern, +- const char *insert, +- bool remove_unsafe_characters, +- bool replace_once, +- bool allow_trailing_dollar) ++bool realloc_string_sub_raw(char **_string, ++ const char *pattern, ++ const char *insert, ++ bool replace_once, ++ bool allow_trailing_dollar, ++ const char *unsafe_characters, ++ char safe_character) + { +- const char *unsafe_characters = STRING_SUB_UNSAFE_CHARACTERS; +- const char safe_character = '_'; +- char *p = NULL, ++ char *p = NULL; + char *s = NULL; + char *string = NULL; + ssize_t ls,lp,li,ld, i; + +- if (!insert || !pattern || !*pattern || !src) { +- return NULL; +- } +- +- string = talloc_strdup(mem_ctx, src); +- if (string == NULL) { +- DEBUG(0, ("talloc_string_sub2: " +- "talloc_strdup failed\n")); +- return NULL; ++ if (!insert || !pattern || !*pattern || !_string|| !*_string) { ++ return false; + } + +- s = string; ++ s = string = *_string; + + ls = (ssize_t)strlen(s); + lp = (ssize_t)strlen(pattern); +@@ -205,14 +197,13 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src, + + while ((p = strstr_m(s,pattern))) { + if (ld > 0) { +- int offset = PTR_DIFF(s,string); +- string = (char *)talloc_realloc_size(mem_ctx, string, +- ls + ld + 1); ++ ptrdiff_t offset = PTR_DIFF(s,string); ++ string = talloc_realloc(NULL, string, char, ls + ld + 1); + if (!string) { +- DEBUG(0, ("talloc_string_sub: out of " +- "memory!\n")); +- return NULL; ++ DBG_ERR("out of memory(realloc)!\n"); ++ return false; + } ++ *_string = string; + p = string + offset + (p - s); + } + if (li != lp) { +@@ -234,6 +225,50 @@ char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src, + break; + } + } ++ return true; ++} ++ ++char *talloc_string_sub2(TALLOC_CTX *mem_ctx, ++ const char *src, ++ const char *pattern, ++ const char *insert, ++ bool remove_unsafe_characters, ++ bool replace_once, ++ bool allow_trailing_dollar) ++{ ++ const char *unsafe_characters = NULL; ++ char safe_character = '\0'; ++ char *string = NULL; ++ bool ok; ++ ++ if (!insert || !pattern || !*pattern || !src) { ++ return NULL; ++ } ++ ++ if (remove_unsafe_characters) { ++ unsafe_characters = STRING_SUB_UNSAFE_CHARACTERS; ++ safe_character = '_'; ++ } ++ ++ string = talloc_strdup(mem_ctx, src); ++ if (string == NULL) { ++ DBG_ERR("out of memory, talloc_strdup(src)!\n"); ++ return NULL; ++ } ++ ++ ok = realloc_string_sub_raw(&string, ++ pattern, ++ insert, ++ replace_once, ++ allow_trailing_dollar, ++ unsafe_characters, ++ safe_character); ++ if (!ok) { ++ TALLOC_FREE(string); ++ DBG_ERR("out of memory, realloc_string_sub_raw()!\n"); ++ return NULL; ++ } ++ + return string; + } + +diff --git a/lib/util/substitute.h b/lib/util/substitute.h +index e1a82859dac..041a649fd18 100644 +--- a/lib/util/substitute.h ++++ b/lib/util/substitute.h +@@ -51,6 +51,24 @@ void string_sub(char *s,const char *pattern, const char *insert, size_t len); + **/ + void all_string_sub(char *s,const char *pattern,const char *insert, size_t len); + ++/* ++ * If unsafe_characters is NULL all characters are allowed, ++ * if unsafe_characters is not NULL all characters caught ++ * by iscntrl() are also replaced by safe_character. ++ * ++ * *_string might be reallocated! ++ * ++ * On error *_string may still be reallocated and ++ * may contain partial replacements. ++ */ ++bool realloc_string_sub_raw(char **_string, ++ const char *pattern, ++ const char *insert, ++ bool replace_once, ++ bool allow_trailing_dollar, ++ const char *unsafe_characters, ++ char safe_character); ++ + char *talloc_string_sub2(TALLOC_CTX *mem_ctx, const char *src, + const char *pattern, + const char *insert, +-- +2.53.0 + + +From 3d28a71755b69b6e0afe5f5d9bbc9aff620b0f4f Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Wed, 6 May 2026 17:23:39 +0200 +Subject: [PATCH 48/66] CVE-2026-4480/CVE-2026-4408: s3:lib: fix potential + memory leak in talloc_sub_basic() + +This makes the code easier to understand... + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Douglas Bagnall +--- + source3/lib/substitute.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/source3/lib/substitute.c b/source3/lib/substitute.c +index 40eb15aee04..5121fcaac1c 100644 +--- a/source3/lib/substitute.c ++++ b/source3/lib/substitute.c +@@ -317,6 +317,7 @@ char *talloc_sub_basic(TALLOC_CTX *mem_ctx, + } + + tmp_ctx = talloc_stackframe(); ++ a_string = talloc_steal(tmp_ctx, a_string); + + for (s = a_string; (p = strchr_m(s, '%')); s = a_string + (p - b)) { + +@@ -478,6 +479,7 @@ error: + TALLOC_FREE(a_string); + + done: ++ a_string = talloc_steal(mem_ctx, a_string); + TALLOC_FREE(tmp_ctx); + return a_string; + } +-- +2.53.0 + + +From c73fc960e67474ded96fdeebf3e2779e2b28799b Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Thu, 23 Apr 2026 21:11:27 +0200 +Subject: [PATCH 49/66] CVE-2026-4480/CVE-2026-4408: s3:lib: let + realloc_string_sub2() use realloc_string_sub_raw() + +We don't need this logic more than once! + +But we leave the strange calling convention of +realloc_string_sub2(), where the caller it +not allowed to use the passed pointer when +NULL is returned... + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Douglas Bagnall +--- + source3/lib/substitute_generic.c | 81 ++++++++++---------------------- + 1 file changed, 24 insertions(+), 57 deletions(-) + +diff --git a/source3/lib/substitute_generic.c b/source3/lib/substitute_generic.c +index 26c5ee761f8..e0639f04eb8 100644 +--- a/source3/lib/substitute_generic.c ++++ b/source3/lib/substitute_generic.c +@@ -37,71 +37,38 @@ char *realloc_string_sub2(char *string, + bool remove_unsafe_characters, + bool allow_trailing_dollar) + { +- char *p, *in; +- char *s; +- ssize_t ls,lp,li,ld, i; ++ const char *unsafe_characters = NULL; ++ char safe_character = '\0'; ++ bool ok; + + if (!insert || !pattern || !*pattern || !string || !*string) + return NULL; + +- s = string; ++ if (remove_unsafe_characters) { ++ unsafe_characters = STRING_SUB_UNSAFE_CHARACTERS; ++ safe_character = '_'; ++ } + +- in = talloc_strdup(talloc_tos(), insert); +- if (!in) { +- DEBUG(0, ("realloc_string_sub: out of memory!\n")); ++ ok = realloc_string_sub_raw(&string, ++ pattern, ++ insert, ++ false, /* replace_once */ ++ allow_trailing_dollar, ++ unsafe_characters, ++ safe_character); ++ if (!ok) { ++ DBG_ERR("out of memory, realloc_string_sub_raw()!\n"); ++ /* ++ * The calling convention of realloc_string_sub2() ++ * is very strange regarding stale string pointers. ++ * ++ * It is assumed the given string was allocated ++ * on talloc_tos(), so we just don't touch ++ * it at all here... ++ */ + return NULL; + } +- ls = (ssize_t)strlen(s); +- lp = (ssize_t)strlen(pattern); +- li = (ssize_t)strlen(insert); +- ld = li - lp; +- for (i=0;i 0) { +- int offset = PTR_DIFF(s,string); +- string = talloc_realloc(NULL, string, char, ls + ld + 1); +- if (!string) { +- DEBUG(0, ("realloc_string_sub: " +- "out of memory!\n")); +- talloc_free(in); +- return NULL; +- } +- p = string + offset + (p - s); +- } +- if (li != lp) { +- memmove(p+li,p+lp,strlen(p+lp)+1); +- } +- memcpy(p, in, li); +- s = p + li; +- ls += ld; +- } +- talloc_free(in); + return string; + } + +-- +2.53.0 + + +From fc4aa4b816bfe1c3c128acf2cbfac652899d4cb6 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Thu, 23 Apr 2026 18:21:08 +0200 +Subject: [PATCH 50/66] CVE-2026-4480/CVE-2026-4408: lib/util: let + mask_unsafe_character() check all control characters + +There's no reason to mask only \r and \n. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Douglas Bagnall +--- + lib/util/substitute.c | 8 +++++++- + lib/util/substitute.h | 6 +++--- + 2 files changed, 10 insertions(+), 4 deletions(-) + +diff --git a/lib/util/substitute.c b/lib/util/substitute.c +index 465aea86605..30989927da7 100644 +--- a/lib/util/substitute.c ++++ b/lib/util/substitute.c +@@ -22,6 +22,7 @@ + */ + + #include "replace.h" ++#include "system/locale.h" + #include "debug.h" + #ifndef SAMBA_UTIL_CORE_ONLY + #include "charset/charset.h" +@@ -53,6 +54,10 @@ char mask_unsafe_character(char in, + return in; + } + ++ if (iscntrl(in)) { ++ return safe_out; ++ } ++ + unsafe = strchr(unsafe_characters, in); + if (unsafe != NULL) { + return safe_out; +@@ -69,7 +74,8 @@ char mask_unsafe_character(char in, + This routine looks for pattern in s and replaces it with + insert. It may do multiple replacements or just one. + +- Any of STRING_SUB_UNSAFE_CHARACTERS in the insert string are replaced with _ ++ Any of STRING_SUB_UNSAFE_CHARACTERS and any character ++ caught by calling iscntrl() in the insert string are replaced with _ + + if len==0 then the string cannot be extended. This is different from the old + use of len==0 which was for no length checks to be done. +diff --git a/lib/util/substitute.h b/lib/util/substitute.h +index 041a649fd18..b183d864671 100644 +--- a/lib/util/substitute.h ++++ b/lib/util/substitute.h +@@ -26,7 +26,7 @@ + + #include + +-#define STRING_SUB_UNSAFE_CHARACTERS "$`\"';%\r\n" ++#define STRING_SUB_UNSAFE_CHARACTERS "$`\"';%" + + /** + Substitute a string for a pattern in another string. Make sure there is +@@ -35,8 +35,8 @@ + This routine looks for pattern in s and replaces it with + insert. It may do multiple replacements. + +- Any of STRING_SUB_UNSAFE_CHARACTERS (see above) in the +- insert string are replaced with _ ++ Any of STRING_SUB_UNSAFE_CHARACTERS (see above) and any character ++ caught by calling iscntrl() in the insert string are replaced with _ + + if len==0 then the string cannot be extended. This is different from the old + use of len==0 which was for no length checks to be done. +-- +2.53.0 + + +From fbe0cdf05b0f8f23a39766158cea9c9886000c67 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Thu, 23 Apr 2026 18:21:08 +0200 +Subject: [PATCH 51/66] CVE-2026-4480/CVE-2026-4408: lib/util: add more unsafe + characters to STRING_SUB_UNSAFE_CHARACTERS + +|&<> are unsafe characters for shell processing. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Douglas Bagnall +--- + lib/util/substitute.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/util/substitute.h b/lib/util/substitute.h +index b183d864671..41f56c73ba2 100644 +--- a/lib/util/substitute.h ++++ b/lib/util/substitute.h +@@ -26,7 +26,7 @@ + + #include + +-#define STRING_SUB_UNSAFE_CHARACTERS "$`\"';%" ++#define STRING_SUB_UNSAFE_CHARACTERS "$`\"';%|&<>" + + /** + Substitute a string for a pattern in another string. Make sure there is +-- +2.53.0 + + +From 37158038bc10998a59ff7c2b699c35e0dfecc83d Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Fri, 8 May 2026 22:33:32 +0200 +Subject: [PATCH 52/66] CVE-2026-4480/CVE-2026-4408: lib/util: let log_escape() + make use of iscntrl() + +using iscntrl() also handles 0x7F (DEL). + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Douglas Bagnall +--- + lib/util/util_str_escape.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/lib/util/util_str_escape.c b/lib/util/util_str_escape.c +index 8f1f34912ee..c6d7a0c9e77 100644 +--- a/lib/util/util_str_escape.c ++++ b/lib/util/util_str_escape.c +@@ -18,6 +18,7 @@ + */ + + #include "replace.h" ++#include "system/locale.h" + #include "lib/util/debug.h" + #include "lib/util/util_str_escape.h" + +@@ -28,7 +29,7 @@ + */ + static size_t encoded_length(unsigned char c) + { +- if (c != '\\' && c > 0x1F) { ++ if (c != '\\' && !iscntrl(c)) { + return 1; + } else { + switch (c) { +@@ -79,7 +80,7 @@ char *log_escape(TALLOC_CTX *frame, const char *in) + c = in; + e = encoded; + while (*c) { +- if (*c != '\\' && (unsigned char)(*c) > 0x1F) { ++ if (*c != '\\' && !iscntrl((unsigned char)(*c))) { + *e++ = *c++; + } else { + switch (*c) { +-- +2.53.0 + + +From e0f1a55382891d113a43184a692f58a7c9832efe Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Thu, 7 May 2026 18:10:50 +0200 +Subject: [PATCH 53/66] CVE-2026-4480/CVE-2026-4408: lib/util: add + talloc_string_sub_{mixed_quoting,unsafe}() helpers + +This is the basic helper function for the security problems. + +talloc_string_sub_mixed_quoting() checks for strange quoting +in smb.conf options. + +And talloc_string_sub_unsafe() tries to autodetect how the unsafe +(client controlled value) and masked and single quote it, +as a fallback for strange quoting a fixed fallback string +is used and the caller should warn the admin and give +hints how to fix the configuration. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 + +Pair-Programmed-With: Douglas Bagnall + +Signed-off-by: Stefan Metzmacher +Signed-off-by: Douglas Bagnall +--- + lib/util/substitute.c | 260 ++++++++++++++++++++++++++++++++++++++++++ + lib/util/substitute.h | 17 +++ + 2 files changed, 277 insertions(+) + +diff --git a/lib/util/substitute.c b/lib/util/substitute.c +index 30989927da7..406d8424be1 100644 +--- a/lib/util/substitute.c ++++ b/lib/util/substitute.c +@@ -25,6 +25,8 @@ + #include "system/locale.h" + #include "debug.h" + #ifndef SAMBA_UTIL_CORE_ONLY ++#include "lib/util/fault.h" ++#include "lib/util/talloc_stack.h" + #include "charset/charset.h" + #else + #include "charset_compat.h" +@@ -297,3 +299,261 @@ char *talloc_all_string_sub(TALLOC_CTX *ctx, + return talloc_string_sub2(ctx, src, pattern, insert, + false, false, false); + } ++ ++#ifndef SAMBA_UTIL_CORE_ONLY ++ ++bool talloc_string_sub_mixed_quoting(const char *full_cmd, char variable_char) ++{ ++ /* ++ * Try to make sure talloc_string_sub_unsafe() ++ * won't return NULL, instead talloc_stackframe_pool() ++ * would panic ++ */ ++ size_t cmd_len = full_cmd != NULL ? strlen(full_cmd) : 0; ++ size_t pool_size = 512 + cmd_len; ++ TALLOC_CTX *frame = talloc_stackframe_pool(pool_size); ++ char *cmd = NULL; ++ bool modified = false; ++ bool masked = false; ++ bool mixed_fallback = false; ++ ++ cmd = talloc_string_sub_unsafe(frame, ++ full_cmd, ++ variable_char, ++ "U", /* unsafe_value */ ++ "'\"%", /* unsafe_characters */ ++ '_', /* safe_character */ ++ "F", /* fallback_value */ ++ &modified, ++ &masked, ++ &mixed_fallback); ++ if (cmd == NULL) { ++ mixed_fallback = false; ++ } ++ TALLOC_FREE(frame); ++ return mixed_fallback; ++} ++ ++char *talloc_string_sub_unsafe(TALLOC_CTX *mem_ctx, ++ const char *orig_cmd, ++ char variable_char, ++ const char *unsafe_value, ++ const char *unsafe_characters, ++ char safe_character, ++ const char *fallback_value, ++ bool *_modified, ++ bool *_masked, ++ bool *_mixed_fallback) ++{ ++ TALLOC_CTX *frame = talloc_stackframe(); ++ const char variable[3] = ++ { '%', variable_char, '\0' }; ++ const char variable_s_quoted[5] = ++ { '\'', '%', variable_char, '\'', '\0' }; ++ const char variable_d_quoted[5] = ++ { '"', '%', variable_char, '"', '\0' }; ++ char *cmd = NULL; ++ char *masked_value = NULL; ++ char *quoted_value = NULL; ++ bool has_s_quotes; ++ bool has_d_quotes; ++ bool has_variable; ++ bool has_variable_s_quoted; ++ bool has_variable_d_quoted; ++ bool modified = false; ++ bool masked = false; ++ bool mixed_fallback = false; ++ bool ok; ++ ++ /* ++ * The unsafe_characters argument should contain ++ * single and double quotes. ++ * Otherwise We can't safely handle this. ++ */ ++ SMB_ASSERT(unsafe_characters != NULL); ++ SMB_ASSERT(strchr(unsafe_characters, '\'') != NULL); ++ SMB_ASSERT(strchr(unsafe_characters, '"') != NULL); ++ SMB_ASSERT(strchr(unsafe_characters, '%') != NULL); ++ ++ cmd = talloc_strdup(mem_ctx, orig_cmd); ++ if (cmd == NULL) { ++ TALLOC_FREE(frame); ++ return NULL; ++ } ++ cmd = talloc_steal(frame, cmd); ++ ++ has_variable = strstr(orig_cmd, variable) != NULL; ++ if (!has_variable) { ++ /* ++ * Nothing to do... ++ */ ++ goto done; ++ } ++ modified = true; ++ ++ /* ++ * Replace all unsafe characters as well as control ++ * characters. ++ * ++ * Note that we start with masked_value = "%u" ++ * and then replace "%u" with unsafe_value, ++ * as a result we have a masked version of ++ * unsafe_value. ++ * ++ * And don't allow option injected like ++ * ++ * '-h value' ++ * '--help value' ++ * ++ */ ++ masked_value = talloc_strdup(frame, variable); ++ if (masked_value == NULL) { ++ goto nomem; ++ } ++ ok = realloc_string_sub_raw(&masked_value, ++ variable, ++ unsafe_value, ++ false, /* replace_once */ ++ false, /* allow_trailing_dollar */ ++ unsafe_characters, ++ safe_character); ++ if (!ok) { ++ goto nomem; ++ } ++ if (masked_value[0] == '-') { ++ masked_value[0] = safe_character; ++ } ++ masked = strcmp(masked_value, unsafe_value) != 0; ++ ++retry: ++ ++ has_s_quotes = strchr(cmd, '\'') != NULL; ++ has_d_quotes = strchr(cmd, '"') != NULL; ++ has_variable = strstr(cmd, variable) != NULL; ++ has_variable_s_quoted = strstr(cmd, variable_s_quoted) != NULL; ++ has_variable_d_quoted = strstr(cmd, variable_d_quoted) != NULL; ++ ++ if (has_variable_s_quoted) { ++ /* ++ * In smb.conf we have something like ++ * ++ * some script = /usr/bin/script '%u' ++ * ++ * It is safe to replace '%u' (or '%J' etc, depending ++ * on variable_char) with '' if ++ * masked_value does not contain single quotes. We ++ * have checked that. ++ */ ++ ++ if (quoted_value == NULL) { ++ quoted_value = talloc_asprintf(frame, "'%s'", ++ masked_value); ++ if (quoted_value == NULL) { ++ goto nomem; ++ } ++ } ++ ++ ok = realloc_string_sub_raw(&cmd, ++ variable_s_quoted, ++ quoted_value, ++ false, /* replace_once */ ++ false, /* allow_trailing_dollar */ ++ NULL, /* unsafe_characters */ ++ '\0'); /* safe_character */ ++ if (!ok) { ++ goto nomem; ++ } ++ ++ goto retry; ++ } ++ ++ if (has_variable_d_quoted && !has_s_quotes) { ++ /* ++ * replace the "%u" ++ * ++ * some script = /usr/bin/script "%u" ++ * ++ * with '%u' and try the '%u' -> 'variable' substitution ++ * again. ++ */ ++ ++ ok = realloc_string_sub_raw(&cmd, ++ variable_d_quoted, ++ variable_s_quoted, ++ false, /* replace_once */ ++ false, /* allow_trailing_dollar */ ++ NULL, /* unsafe_characters */ ++ '\0'); /* safe_character */ ++ if (!ok) { ++ goto nomem; ++ } ++ ++ goto retry; ++ } ++ ++ if (has_variable && !has_s_quotes && !has_d_quotes) { ++ /* ++ * In this case: ++ * ++ * some script = /usr/bin/script %u ++ * ++ * we can safely substitute %u -> '%u' and try the ++ * single quote test again. ++ */ ++ ++ ok = realloc_string_sub_raw(&cmd, ++ variable, ++ variable_s_quoted, ++ false, /* replace_once */ ++ false, /* allow_trailing_dollar */ ++ NULL, /* unsafe_characters */ ++ '\0'); /* safe_character */ ++ if (!ok) { ++ goto nomem; ++ } ++ ++ goto retry; ++ } ++ ++ if (has_variable) { ++ /* ++ * There are single or double quotes, but not tightly ++ * bound around a %u. ++ * ++ * Or there's a mix of single and double quotes. ++ * ++ * We just use a generic fallback value. ++ * and let the caller warn about this ++ * and give the admin a hind to fix the smb.conf ++ * option. ++ */ ++ mixed_fallback = true; ++ ++ ok = realloc_string_sub_raw(&cmd, ++ variable, ++ fallback_value, ++ false, /* replace_once */ ++ false, /* allow_trailing_dollar */ ++ NULL, /* unsafe_characters */ ++ '\0'); /* safe_character */ ++ if (!ok) { ++ goto nomem; ++ } ++ } ++ ++done: ++ *_modified = modified; ++ *_masked = masked; ++ *_mixed_fallback = mixed_fallback; ++ cmd = talloc_steal(mem_ctx, cmd); ++ TALLOC_FREE(frame); ++ return cmd; ++ ++nomem: ++ *_modified = false; ++ *_masked = false; ++ *_mixed_fallback = false; ++ TALLOC_FREE(frame); ++ return NULL; ++} ++#endif /* ! SAMBA_UTIL_CORE_ONLY */ +diff --git a/lib/util/substitute.h b/lib/util/substitute.h +index 41f56c73ba2..b8205055da1 100644 +--- a/lib/util/substitute.h ++++ b/lib/util/substitute.h +@@ -83,4 +83,21 @@ char *talloc_all_string_sub(TALLOC_CTX *ctx, + const char *src, + const char *pattern, + const char *insert); ++ ++#ifndef SAMBA_UTIL_CORE_ONLY ++bool talloc_string_sub_mixed_quoting(const char *full_cmd, char variable_char); ++ ++char *talloc_string_sub_unsafe(TALLOC_CTX *mem_ctx, ++ const char *orig_cmd, ++ char variable_char, ++ const char *unsafe_value, ++ const char *unsafe_characters, ++ char safe_character, ++ const char *fallback_value, ++ bool *_modified, ++ bool *_masked, ++ bool *_mixed_fallback); ++ ++#endif /* ! SAMBA_UTIL_CORE_ONLY */ ++ + #endif /* _SAMBA_SUBSTITUTE_H_ */ +-- +2.53.0 + + +From b0425235ed880fb510aba1fe0fc07ee5d7304337 Mon Sep 17 00:00:00 2001 +From: Douglas Bagnall +Date: Sat, 9 May 2026 22:02:47 +1200 +Subject: [PATCH 54/66] CVE-2026-4480/CVE-2026-4408: lib/util: add + test_string_sub unittests + +This demonstrates the logic of talloc_string_sub_{mixed_quoting,unsafe}() + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 + +Pair-Programmed-With: Stefan Metzmacher + +Signed-off-by: Douglas Bagnall +Signed-off-by: Stefan Metzmacher +--- + lib/util/tests/test_string_sub.c | 1044 ++++++++++++++++++++++++++++++ + lib/util/wscript_build | 6 + + selftest/tests.py | 2 + + 3 files changed, 1052 insertions(+) + create mode 100644 lib/util/tests/test_string_sub.c + +diff --git a/lib/util/tests/test_string_sub.c b/lib/util/tests/test_string_sub.c +new file mode 100644 +index 00000000000..da97c1c936c +--- /dev/null ++++ b/lib/util/tests/test_string_sub.c +@@ -0,0 +1,1044 @@ ++ ++#include ++#include ++#include ++#include ++#include ++#include "replace.h" ++#include ++#include "talloc.h" ++ ++#include "../substitute.h" ++ ++/* set _DEBUG_VERBOSE to print more. */ ++#define _DEBUG_VERBOSE ++ ++#ifdef _DEBUG_VERBOSE ++#define debug_message(...) print_message(__VA_ARGS__) ++#else ++#define debug_message(...) /* debug_message */ ++#endif ++ ++ ++static int setup_talloc_context(void **state) ++{ ++ TALLOC_CTX *mem_ctx = talloc_new(NULL); ++ *state = mem_ctx; ++ return 0; ++} ++ ++static int teardown_talloc_context(void **state) ++{ ++ TALLOC_CTX *mem_ctx = *state; ++ TALLOC_FREE(mem_ctx); ++ return 0; ++} ++ ++struct cmd_expansion { ++ const char *lp_cmd; ++ const char *username; ++ const char *result_cmd; ++ bool modified; ++ bool masked; ++ bool mixed_fallback; ++}; ++ ++static void _test_talloc_string_sub_unsafe(void **state, ++ struct cmd_expansion expansions[], ++ size_t n_expansions, ++ const char *unsafe_characters) ++{ ++ TALLOC_CTX *mem_ctx = *state; ++ size_t i; ++ ++ for (i = 0; i < n_expansions; i++) { ++ struct cmd_expansion t = expansions[i]; ++ char *result_cmd = NULL; ++ bool masked; ++ bool mixed_fallback; ++ bool modified; ++ bool flags_correct; ++ bool mixed; ++ int cmp; ++ ++ mixed = talloc_string_sub_mixed_quoting(t.lp_cmd, 'u'); ++ ++ result_cmd = talloc_string_sub_unsafe(mem_ctx, ++ t.lp_cmd, ++ 'u', ++ t.username, ++ unsafe_characters, ++ '_', ++ "FallbackUsername", ++ &modified, ++ &masked, ++ &mixed_fallback); ++ assert_ptr_not_equal(result_cmd, NULL); ++ assert_ptr_not_equal(t.result_cmd, NULL); ++ ++ cmp = strcmp(t.result_cmd, result_cmd); ++ flags_correct = (modified == t.modified && ++ masked == t.masked && ++ mixed_fallback == t.mixed_fallback); ++ ++ if (cmp == 0) { ++ debug_message("[%zu] «%s» «%s» -> «%s»; AS EXPECTED\n", ++ i, t.lp_cmd, ++ t.username, ++ result_cmd); ++ } else { ++ debug_message("[%zu] «%s» «%s»; " ++ "expected [%zu] «%s» got [%zu] «%s»\033[1;31m BAD! \033[0m\n", ++ i, t.lp_cmd, ++ t.username, ++ strlen(t.result_cmd), t.result_cmd, ++ strlen(result_cmd), result_cmd); ++ } ++ assert_int_equal(cmp, 0); ++ if (!flags_correct) { ++ debug_message("[%zu] ", i); ++#define _FLAG(x) debug_message((t. x == x) ? "%s: %s √; ": \ ++ "%s \033[1;31m expected %s \033[0m; ", \ ++ #x, t.x ? "true": "false"); ++ _FLAG(modified); ++ _FLAG(masked); ++ _FLAG(mixed_fallback); ++ debug_message("\n"); ++ } ++ assert_int_equal(flags_correct, true); ++ if (mixed_fallback != mixed) { ++ debug_message("[%zu] %s mixed \033[1;31m expected %s \033[0m; ", ++ i, t.lp_cmd, ++ mixed_fallback ? "true": "false"); ++ } ++ assert_int_equal(mixed_fallback, mixed); ++#undef _FLAG ++ } ++ debug_message("ALL correct\n"); ++} ++ ++static void test_talloc_string_sub_unsafe(void **state) ++{ ++ const char *unsafe_characters = STRING_SUB_UNSAFE_CHARACTERS; ++ ++ static struct cmd_expansion expansions[] = { ++ { ++ "/bin/echo \"bob'", ++ "bob", ++ "/bin/echo \"bob'", ++ false, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo '%u'", ++ "bob", ++ "/bin/echo 'bob'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo %u", ++ "bob", ++ "/bin/echo 'bob'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo %u", ++ "bob'", ++ "/bin/echo 'bob_'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo %u", ++ "bob'''", ++ "/bin/echo 'bob___'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo %u", ++ "bob\'", ++ "/bin/echo 'bob_'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo '%u", ++ "bob bob bob", ++ "/bin/echo 'FallbackUsername", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo \"%u\"", ++ " ", ++ "/bin/echo ' '", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo \"--uu=%u\"", ++ "bob", ++ "/bin/echo \"--uu=FallbackUsername\"", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo \"--uu=%u\"", ++ "bob !0", ++ "/bin/echo \"--uu=FallbackUsername\"", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo %u", ++ "!0", ++ "/bin/echo '!0'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo \"--uu=%u\"", ++ "bob \\", ++ "/bin/echo \"--uu=FallbackUsername\"", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo --uu='%u'", ++ "bob >> x", ++ "/bin/echo --uu='bob __ x'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo '--uu=%u\"", ++ "bob", ++ "/bin/echo '--uu=FallbackUsername\"", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo --uu='%u'", ++ "bob", ++ "/bin/echo --uu='bob'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo --uu'=%u'", ++ "bob", ++ "/bin/echo --uu'=FallbackUsername'", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo --uu'=%u'", ++ "`ls`", ++ "/bin/echo --uu'=FallbackUsername'", ++ true, ++ true, ++ true, ++ }, ++ { ++ "/bin/echo --uu='%u'", ++ "u%u%u%u%u", ++ "/bin/echo --uu='u_u_u_u_u'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo --uu='%u'", ++ "$(ls)", ++ "/bin/echo --uu='_(ls)'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo --uu='%u'", ++ "`ls`", ++ "/bin/echo --uu='_ls_'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo --uu='1' %u", ++ "`ls`", ++ "/bin/echo --uu='1' FallbackUsername", ++ true, ++ true, ++ true, ++ }, ++ { ++ "/bin/echo --uu=\"'%u'\"", ++ "bob", ++ "/bin/echo --uu=\"'bob'\"", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo --uu='%u' --yy='%u' '%u' %u", ++ "bob", ++ "/bin/echo --uu='bob' --yy='bob' 'bob' FallbackUsername", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo --uu=%u%u%u'' %user 50%u", ++ "bob", ++ "/bin/echo --uu=FallbackUsernameFallbackUsernameFallbackUsername'' FallbackUsernameser 50FallbackUsername", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo %u", ++ "!!", ++ "/bin/echo '!!'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo %u", ++ ">xxx", ++ "/bin/echo '_xxx'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo %u", ++ "3", ++ "/bin/echo '3'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo '%u'", ++ "3$", ++ "/bin/echo '3_'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo '%u'", ++ "comp$", ++ "/bin/echo 'comp_'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo '%u'", ++ "3$3", ++ "/bin/echo '3_3'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo '%u'", ++ "q $3", ++ "/bin/echo 'q _3'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo '%u", ++ "q $3", ++ "/bin/echo 'FallbackUsername", ++ true, ++ true, ++ true, ++ }, ++ { ++ "/bin/echo -s '%u' %u", ++ "āāā", ++ "/bin/echo -s 'āāā' FallbackUsername", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo -s '%u' %u", ++ "-āāā", ++ "/bin/echo -s '_āāā' FallbackUsername", ++ true, ++ true, ++ true, ++ }, ++ { ++ "/bin/echo -s %u", ++ "āāā", ++ "/bin/echo -s 'āāā'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo -s %u", ++ "a -a", ++ "/bin/echo -s 'a -a'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo -s=%u %u", ++ "ā -a", ++ "/bin/echo -s='ā -a' 'ā -a'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo -s=\"%u %u\"", ++ "ā -a", ++ "/bin/echo -s=\"FallbackUsername FallbackUsername\"", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo -m='fridge' %u", ++ "ā -ß", ++ "/bin/echo -m='fridge' FallbackUsername", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo -m='fridge' %u", ++ "-ā -a", ++ "/bin/echo -m='fridge' FallbackUsername", ++ true, ++ true, ++ true, ++ }, ++ { ++ "/bin/echo %u", ++ "-n", ++ "/bin/echo '_n'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo %u", ++ "o'clock", ++ "/bin/echo 'o_clock'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo \"bob'", ++ "bob", ++ "/bin/echo \"bob'", ++ false, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo \"%u\"", ++ "%u", ++ "/bin/echo '_u'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo \"$(ls)\"", ++ "%u", ++ "/bin/echo \"$(ls)\"", ++ false, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo %u", ++ "\\", ++ "/bin/echo '\\'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo '%u'", ++ "\\", ++ "/bin/echo '\\'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo \"%u\"", ++ "\\", ++ "/bin/echo '\\'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo \"%u\" %u", ++ "\\", ++ "/bin/echo '\\' FallbackUsername", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo '%u' \"%u\" %u", ++ "\\", ++ "/bin/echo '\\' \"FallbackUsername\" FallbackUsername", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo '%u' \"%u\"", ++ "bob", ++ "/bin/echo 'bob' \"FallbackUsername\"", ++ true, ++ false, ++ true, ++ }, ++ }; ++ ++ _test_talloc_string_sub_unsafe(state, ++ expansions, ++ ARRAY_SIZE(expansions), ++ unsafe_characters); ++} ++ ++static void test_talloc_string_sub_unsafe_minimal_unsafe_chars(void **state) ++{ ++ const char *unsafe_characters = "\"'%"; ++ ++ static struct cmd_expansion expansions[] = { ++ { ++ "/bin/echo \"bob'", ++ "bob", ++ "/bin/echo \"bob'", ++ false, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo '%u'", ++ "bob", ++ "/bin/echo 'bob'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo %u", ++ "bob", ++ "/bin/echo 'bob'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo %u", ++ "bob'", ++ "/bin/echo 'bob_'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo %u", ++ "bob'''", ++ "/bin/echo 'bob___'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo %u", ++ "bob\'", ++ "/bin/echo 'bob_'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo '%u", ++ "bob bob bob", ++ "/bin/echo 'FallbackUsername", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo \"%u\"", ++ " ", ++ "/bin/echo ' '", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo \"--uu=%u\"", ++ "bob", ++ "/bin/echo \"--uu=FallbackUsername\"", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo \"--uu=%u\"", ++ "bob !0", ++ "/bin/echo \"--uu=FallbackUsername\"", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo %u", ++ "!0", ++ "/bin/echo '!0'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo \"--uu=%u\"", ++ "bob \\", ++ "/bin/echo \"--uu=FallbackUsername\"", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo --uu='%u'", ++ "bob >> x", ++ "/bin/echo --uu='bob >> x'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo '--uu=%u\"", ++ "bob", ++ "/bin/echo '--uu=FallbackUsername\"", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo --uu='%u'", ++ "bob", ++ "/bin/echo --uu='bob'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo --uu'=%u'", ++ "bob", ++ "/bin/echo --uu'=FallbackUsername'", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo --uu'=%u'", ++ "`ls`", ++ "/bin/echo --uu'=FallbackUsername'", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo --uu='%u'", ++ "u%u%u%u%u", ++ "/bin/echo --uu='u_u_u_u_u'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo --uu='%u'", ++ "$(ls)", ++ "/bin/echo --uu='$(ls)'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo --uu='%u'", ++ "`ls`", ++ "/bin/echo --uu='`ls`'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo --uu='1' %u", ++ "`ls`", ++ "/bin/echo --uu='1' FallbackUsername", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo --uu=\"'%u'\"", ++ "bob", ++ "/bin/echo --uu=\"'bob'\"", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo --uu='%u' --yy='%u' '%u' %u", ++ "bob", ++ "/bin/echo --uu='bob' --yy='bob' 'bob' FallbackUsername", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo --uu=%u%u%u'' %user 50%u", ++ "bob", ++ "/bin/echo --uu=FallbackUsernameFallbackUsernameFallbackUsername'' FallbackUsernameser 50FallbackUsername", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo %u", ++ "!!", ++ "/bin/echo '!!'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo %u", ++ ">xxx", ++ "/bin/echo '>xxx'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo %u", ++ "3", ++ "/bin/echo '3'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo '%u'", ++ "3$", ++ "/bin/echo '3$'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo '%u'", ++ "comp$", ++ "/bin/echo 'comp$'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo '%u'", ++ "3$3", ++ "/bin/echo '3$3'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo '%u'", ++ "q $3", ++ "/bin/echo 'q $3'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo '%u", ++ "q $3", ++ "/bin/echo 'FallbackUsername", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo -s '%u' %u", ++ "āāā", ++ "/bin/echo -s 'āāā' FallbackUsername", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo -s '%u' %u", ++ "-āāā", ++ "/bin/echo -s '_āāā' FallbackUsername", ++ true, ++ true, ++ true, ++ }, ++ { ++ "/bin/echo -s %u", ++ "āāā", ++ "/bin/echo -s 'āāā'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo -s %u", ++ "a -a", ++ "/bin/echo -s 'a -a'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo -s=%u %u", ++ "ā -a", ++ "/bin/echo -s='ā -a' 'ā -a'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo -s=\"%u %u\"", ++ "ā -a", ++ "/bin/echo -s=\"FallbackUsername FallbackUsername\"", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo -m='fridge' %u", ++ "ā -ß", ++ "/bin/echo -m='fridge' FallbackUsername", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo -m='fridge' %u", ++ "-ā -a", ++ "/bin/echo -m='fridge' FallbackUsername", ++ true, ++ true, ++ true, ++ }, ++ { ++ "/bin/echo %u", ++ "-n", ++ "/bin/echo '_n'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo %u", ++ "o'clock", ++ "/bin/echo 'o_clock'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo \"bob'", ++ "bob", ++ "/bin/echo \"bob'", ++ false, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo \"%u\"", ++ "%u", ++ "/bin/echo '_u'", ++ true, ++ true, ++ false, ++ }, ++ { ++ "/bin/echo \"$(ls)\"", ++ "%u", ++ "/bin/echo \"$(ls)\"", ++ false, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo %u", ++ "\\", ++ "/bin/echo '\\'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo '%u'", ++ "\\", ++ "/bin/echo '\\'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo \"%u\"", ++ "\\", ++ "/bin/echo '\\'", ++ true, ++ false, ++ false, ++ }, ++ { ++ "/bin/echo \"%u\" %u", ++ "\\", ++ "/bin/echo '\\' FallbackUsername", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo '%u' \"%u\" %u", ++ "\\", ++ "/bin/echo '\\' \"FallbackUsername\" FallbackUsername", ++ true, ++ false, ++ true, ++ }, ++ { ++ "/bin/echo '%u' \"%u\"", ++ "bob", ++ "/bin/echo 'bob' \"FallbackUsername\"", ++ true, ++ false, ++ true, ++ }, ++ }; ++ ++ _test_talloc_string_sub_unsafe(state, ++ expansions, ++ ARRAY_SIZE(expansions), ++ unsafe_characters); ++} ++ ++static void test_talloc_string_sub_unsafe_all_mixes(void **state) ++{ ++ const char *unsafe_characters = STRING_SUB_UNSAFE_CHARACTERS; ++ size_t i; ++ ++ for (i = 0; i < 32; i++) { ++ char in[100] = { 0, }; ++ char out[100] = { 0, }; ++ struct cmd_expansion expansions[] = { ++ { ++ in, ++ "bob", ++ out, ++ true, ++ false, ++ false, ++ }, ++ }; ++ bool vsq = i & 1; ++ bool vdq = i & 2; ++ bool v = i & 4; ++ bool sq = i & 8; ++ bool dq = i & 16; ++ char *inp = in; ++ char *outp = out; ++ if (vsq) { ++ inp = stpcpy(inp, "'%u' "); ++ outp = stpcpy(outp, "'bob' "); ++ debug_message("vsq "); ++ } ++ if (vdq) { ++ inp = stpcpy(inp, "\"%u\" "); ++ outp = stpcpy(outp, (vsq || sq) ? "\"FallbackUsername\" " : "'bob' "); ++ debug_message("vdq "); ++ if (vsq || sq) { ++ expansions[0].mixed_fallback = true; ++ } ++ } ++ if (v) { ++ inp = stpcpy(inp, "%u "); ++ outp = stpcpy(outp, (vsq || vdq || sq || dq) ? "FallbackUsername " : "'bob' "); ++ debug_message("v "); ++ if (vsq || vdq || sq || dq) { ++ expansions[0].mixed_fallback = true; ++ } ++ } ++ if (sq) { ++ inp = stpcpy(inp, "' "); ++ outp = stpcpy(outp, "' "); ++ debug_message("sq "); ++ } ++ if (dq) { ++ inp = stpcpy(inp, "\" "); ++ outp = stpcpy(outp, "\" "); ++ debug_message("dq "); ++ } ++ debug_message("(i: %zu)\n", i); ++ *inp = '\0'; ++ *outp = '\0'; ++ expansions[0].modified = strcmp(in, out) != 0; ++ ++ _test_talloc_string_sub_unsafe(state, ++ expansions, ++ ARRAY_SIZE(expansions), ++ unsafe_characters); ++ } ++} ++ ++ ++int main(void) ++{ ++ const struct CMUnitTest tests[] = { ++ cmocka_unit_test(test_talloc_string_sub_unsafe), ++ cmocka_unit_test(test_talloc_string_sub_unsafe_minimal_unsafe_chars), ++ cmocka_unit_test(test_talloc_string_sub_unsafe_all_mixes), ++ }; ++ if (!isatty(1)) { ++ cmocka_set_message_output(CM_OUTPUT_SUBUNIT); ++ } ++ return cmocka_run_group_tests(tests, ++ setup_talloc_context, ++ teardown_talloc_context); ++} +diff --git a/lib/util/wscript_build b/lib/util/wscript_build +index 9dff0e8925d..c9c04f1aaed 100644 +--- a/lib/util/wscript_build ++++ b/lib/util/wscript_build +@@ -420,3 +420,9 @@ else: + deps='cmocka replace talloc stable_sort', + local_include=False, + for_selftest=True) ++ ++ bld.SAMBA3_BINARY('test_string_sub', ++ source='tests/test_string_sub.c', ++ deps='''cmocka replace talloc samba-util ++ ''', ++ for_selftest=True) +diff --git a/selftest/tests.py b/selftest/tests.py +index 104fa65f672..c92676f66eb 100644 +--- a/selftest/tests.py ++++ b/selftest/tests.py +@@ -569,6 +569,8 @@ plantestsuite("samba.unittests.sys_rw", "none", + [os.path.join(bindir(), "default/lib/util/test_sys_rw")]) + plantestsuite("samba.unittests.stable_sort", "none", + [os.path.join(bindir(), "default/lib/util/test_stable_sort")]) ++plantestsuite("samba.unittests.test_string_sub", "none", ++ [os.path.join(bindir(), "test_string_sub")]) + plantestsuite("samba.unittests.ntlm_check", "none", + [os.path.join(bindir(), "default/libcli/auth/test_ntlm_check")]) + plantestsuite("samba.unittests.gnutls", "none", +-- +2.53.0 + + +From 8ee5805b49b6f5f413e627c8b19d10559bbb2aa2 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Sun, 15 Mar 2026 19:15:14 +0100 +Subject: [PATCH 55/66] CVE-2026-4480: s3:printing: mask and/or single quote + jobname passed as %J to "print command" + +Fix an unauthenticated remote code execution vulnerability with +printing set to anything *but* cups and iprint, for example "lprng", +so that "print command" is executed upon job submission. If the +client-controlled job name is handed to the "print command" via %J, +rpcd_spoolssd passes this to the shell without escaping critical +characters. + +Using single quotes (directly) around %J, '%J' would avoid the +problem, we now try to autodetect if we can use '%J' implicitly +or we fallback to a fixed "__CVE-2026-4480_FallbackJobname__" +string instead of the client provided jobname. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Douglas Bagnall +--- + source3/printing/print_generic.c | 107 +++++++++++++++++++++++++++---- + 1 file changed, 94 insertions(+), 13 deletions(-) + +diff --git a/source3/printing/print_generic.c b/source3/printing/print_generic.c +index 7c7a14de045..2f642af3f4b 100644 +--- a/source3/printing/print_generic.c ++++ b/source3/printing/print_generic.c +@@ -19,6 +19,7 @@ + + #include "includes.h" + #include "lib/util/util_file.h" ++#include "lib/util/util_str_escape.h" + #include "printing.h" + #include "smbd/proto.h" + #include "source3/lib/substitute.h" +@@ -207,6 +208,52 @@ static int generic_queue_get(const char *printer_name, + return qcount; + } + ++static const char *replace_print_cmd_J(TALLOC_CTX *mem_ctx, ++ const char *orig_cmd, ++ const char *unsafe_jobname, ++ const char *fallback_jobname) ++{ ++ char *cmd = NULL; ++ bool modified = false; ++ bool masked = false; ++ bool mixed_fallback = false; ++ ++ /* ++ * This replaces unsafe characters with '_'. ++ * We also mask forward and backslash here. ++ * ++ * Then it replaces %J with an single quoted ++ * version of the masked jobname or it falls ++ * back to fallback_jobname is the print command ++ * uses strange mixed quoting. ++ */ ++ ++#define JOBNAME_UNSAFE_CHARACTERS \ ++ STRING_SUB_UNSAFE_CHARACTERS "/\\" ++ ++ cmd = talloc_string_sub_unsafe(mem_ctx, ++ orig_cmd, ++ 'J', ++ unsafe_jobname, ++ JOBNAME_UNSAFE_CHARACTERS, ++ '_', ++ fallback_jobname, ++ &modified, ++ &masked, ++ &mixed_fallback); ++ if (cmd == NULL) { ++ return NULL; ++ } ++ ++ /* ++ * The caller already checked talloc_string_sub_mixed_quoting() ++ * and warned the admin, so we don't check mixed_fallback ++ * here ++ */ ++ ++ return cmd; ++} ++ + /**************************************************************************** + Submit a file for printing - called from print_job_end() + ****************************************************************************/ +@@ -222,11 +269,12 @@ static int generic_job_submit(int snum, struct printjob *pjob, + char *print_directory = NULL; + char *wd = NULL; + char *p = NULL; +- char *jobname = NULL; ++ const char *print_cmd = NULL; + TALLOC_CTX *ctx = talloc_tos(); + fstring job_page_count, job_size; + print_queue_struct *q = NULL; + print_status_struct status; ++ const char *jobname = "No Document Name"; + + /* we print from the directory path to give the best chance of + parsing the lpq output */ +@@ -255,24 +303,48 @@ static int generic_job_submit(int snum, struct printjob *pjob, + return -1; + } + +- jobname = talloc_strdup(ctx, pjob->jobname); +- if (!jobname) { +- ret = -1; +- goto out; ++ if (pjob->jobname[0] != '\0') { ++ jobname = pjob->jobname; + } +- jobname = talloc_string_sub(ctx, jobname, "'", "_"); +- if (!jobname) { +- ret = -1; +- goto out; ++ ++ print_cmd = lp_print_command(snum); ++ if (print_cmd != NULL) { ++ const char *invalid_jobname = "__CVE-2026-4480_FallbackJobname__"; ++ ++ if (talloc_string_sub_mixed_quoting(print_cmd, 'J')) { ++ /* ++ * The admin used a strange mixture of ++ * single and double quotes, fallback ++ * to InvalidDocumentName and warn about ++ * it, so that the admin can adjust to ++ * the use single quotes directly around %J, ++ * e.g. '%J'. ++ */ ++ jobname = invalid_jobname; ++ D_WARNING("CVE-2026-4480: printer %s " ++ "strange quoting in 'print command', " ++ "falling back to jobname=%s, " ++ "use testparm to fix the configuration\n", ++ lp_printername(talloc_tos(), lp_sub, snum), ++ invalid_jobname); ++ } ++ ++ print_cmd = replace_print_cmd_J(ctx, ++ print_cmd, ++ jobname, ++ invalid_jobname); ++ if (!print_cmd) { ++ ret = -1; ++ goto out; ++ } + } + fstr_sprintf(job_page_count, "%d", pjob->page_count); + fstr_sprintf(job_size, "%zu", pjob->size); + + /* send it to the system spooler */ + ret = print_run_command(snum, lp_printername(talloc_tos(), lp_sub, snum), True, +- lp_print_command(snum), NULL, ++ print_cmd, NULL, + "%s", p, +- "%J", jobname, + "%f", p, + "%z", job_size, + "%c", job_page_count, +@@ -293,17 +365,26 @@ static int generic_job_submit(int snum, struct printjob *pjob, + int i; + for (i = 0; i < ret; i++) { + if (strcmp(q[i].fs_file, p) == 0) { ++ char *le_jobname = ++ log_escape(talloc_tos(), jobname); ++ + pjob->sysjob = q[i].sysjob; + DEBUG(5, ("new job %u (%s) matches sysjob %d\n", +- pjob->jobid, jobname, pjob->sysjob)); ++ pjob->jobid, le_jobname, pjob->sysjob)); ++ ++ TALLOC_FREE(le_jobname); + break; + } + } + ret = 0; + } + if (pjob->sysjob == -1) { ++ char *le_jobname = log_escape(talloc_tos(), jobname); ++ + DEBUG(2, ("failed to get sysjob for job %u (%s), tracking as " +- "Unix job\n", pjob->jobid, jobname)); ++ "Unix job\n", pjob->jobid, le_jobname)); ++ ++ TALLOC_FREE(le_jobname); + } + + +-- +2.53.0 + + +From 27a1372c85d05f02fd1e7f975798314b95ee75d3 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Fri, 8 May 2026 23:27:35 +0200 +Subject: [PATCH 56/66] CVE-2026-4480: s3:testparm: warn about 'print command' + %J usage + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Douglas Bagnall +--- + source3/utils/testparm.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/source3/utils/testparm.c b/source3/utils/testparm.c +index 306924ac7c8..c758313d466 100644 +--- a/source3/utils/testparm.c ++++ b/source3/utils/testparm.c +@@ -928,6 +928,14 @@ static void do_per_share_checks(int s) + "parameter is ignored when using CUPS libraries.\n\n", + lp_servicename(talloc_tos(), lp_sub, s)); + } ++ if (talloc_string_sub_mixed_quoting(lp_print_command(s), 'J')) { ++ fprintf(stderr, ++ "WARNING: Service %s defines a 'print command' " ++ "with mixed quoting and %%J.\n" ++ "CVE-2026-4480 changed the way %%J substitution works.\n" ++ "You should use single quotes (directly) around '%%J'.\n\n", ++ lp_servicename(talloc_tos(), lp_sub, s)); ++ } + + vfs_objects = lp_vfs_objects(s); + if (vfs_objects && str_list_check(vfs_objects, "fruit")) { +-- +2.53.0 + + +From 498d3a2eba50e0d4eff50c99cd54a8a9af4e7639 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Mon, 11 May 2026 14:11:34 +0200 +Subject: [PATCH 57/66] CVE-2026-4480: docs-xml/smbdotconf: clarify '%J' in + 'print command' + +Admins should use '%J'. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16033 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Douglas Bagnall +--- + docs-xml/smbdotconf/printing/printcommand.xml | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/docs-xml/smbdotconf/printing/printcommand.xml b/docs-xml/smbdotconf/printing/printcommand.xml +index c84e45f404d..d708287932a 100644 +--- a/docs-xml/smbdotconf/printing/printcommand.xml ++++ b/docs-xml/smbdotconf/printing/printcommand.xml +@@ -21,8 +21,11 @@ + %p - the appropriate printer + name + +- %J - the job +- name as transmitted by the client. ++ %J - the job name as transmitted by the client, ++ but with dangerous characters being replaced by _. ++ You should use single quotes (directly) around %J, e.g. '%J', ++ see CVE-2026-4480 for more details. ++ + + %c - The number of printed pages + of the spooled job (if known). +-- +2.53.0 + + +From 47e15af9f5d77208473b29bac5422665b79191f8 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Thu, 23 Apr 2026 18:56:21 +0200 +Subject: [PATCH 58/66] CVE-2026-4408: lib/util: introduce + strstr_for_invalid_account_characters() + +This splits out the logic from samaccountname_bad_chars_check() +in source4/dsdb/samdb/ldb_modules/samldb.c, this will be used +in other places soon. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Douglas Bagnall +--- + lib/util/samba_util.h | 9 +++++++++ + lib/util/util_str.c | 38 ++++++++++++++++++++++++++++++++++++++ + 2 files changed, 47 insertions(+) + +diff --git a/lib/util/samba_util.h b/lib/util/samba_util.h +index 03dee5c6137..ea741b51c58 100644 +--- a/lib/util/samba_util.h ++++ b/lib/util/samba_util.h +@@ -303,6 +303,15 @@ _PUBLIC_ bool set_boolean(const char *boolean_string, bool *boolean); + */ + _PUBLIC_ bool conv_str_bool(const char * str, bool * val); + ++/** ++ * Returns a pointer to the first invalid character in name. ++ * ++ * Passing a NULL pointer as name is not allowed! ++ * ++ * This returns NULL for a valid account name. ++ **/ ++_PUBLIC_ const char *strstr_for_invalid_account_characters(const char *name); ++ + /** + * Convert a size specification like 16K into an integral number of bytes. + **/ +diff --git a/lib/util/util_str.c b/lib/util/util_str.c +index 19acff4a983..c5987461fe6 100644 +--- a/lib/util/util_str.c ++++ b/lib/util/util_str.c +@@ -267,3 +267,41 @@ _PUBLIC_ bool set_boolean(const char *boolean_string, bool *boolean) + } + return false; + } ++ ++_PUBLIC_ const char *strstr_for_invalid_account_characters(const char *name) ++{ ++ /* ++ * Return a pointer to the first invalid character in the ++ * sAMAccountName, or NULL if the whole name is valid. ++ * ++ * The rules here are based on ++ * ++ * https://social.technet.microsoft.com/wiki/contents/articles/11216.active-directory-requirements-for-creating-objects.aspx ++ */ ++ size_t i; ++ ++ for (i = 0; name[i] != '\0'; i++) { ++ uint8_t c = name[i]; ++ const char *p = NULL; ++ ++ if (iscntrl(c)) { ++ return &name[i]; ++ } ++ ++ p = strchr("\"[]:;|=+*?<>/\\,", c); ++ if (p != NULL) { ++ return &name[i]; ++ } ++ } ++ ++ if (i == 0) { ++ return &name[i]; ++ } ++ ++ if (name[i - 1] == '.') { ++ i -= 1; ++ return &name[i]; ++ } ++ ++ return NULL; ++} +-- +2.53.0 + + +From cd1dc2df42eda36bf66e8d7d6f69e365e5d7d8a3 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Mon, 11 May 2026 20:21:36 +0200 +Subject: [PATCH 59/66] CVE-2026-4408: s3:samr-server: only allow + _samr_ValidatePassword as DC + +This is only supported with 'rpc start on demand helpers = no', +as it needs ncacn_ip_tcp, but we better also restrict it to DCs. + +Maybe only FreeIPA needs it as NT4 didn't support ncacn_ip_tcp. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Douglas Bagnall +--- + source3/rpc_server/samr/srv_samr_nt.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/source3/rpc_server/samr/srv_samr_nt.c b/source3/rpc_server/samr/srv_samr_nt.c +index e0d0875bd5d..3937dbe3f32 100644 +--- a/source3/rpc_server/samr/srv_samr_nt.c ++++ b/source3/rpc_server/samr/srv_samr_nt.c +@@ -7500,6 +7500,14 @@ NTSTATUS _samr_ValidatePassword(struct pipes_struct *p, + return NT_STATUS_ACCESS_DENIED; + } + ++ if (lp_server_role() <= ROLE_DOMAIN_MEMBER) { ++ /* ++ * We only want this on DCs ++ */ ++ p->fault_state = DCERPC_FAULT_ACCESS_DENIED; ++ return NT_STATUS_ACCESS_DENIED; ++ } ++ + if (r->in.level < 1 || r->in.level > 3) { + return NT_STATUS_INVALID_INFO_CLASS; + } +-- +2.53.0 + + +From 77c8330fb3b01c3d23f9aac611397334ef6f0007 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Wed, 18 Mar 2026 12:24:47 +0100 +Subject: [PATCH 60/66] CVE-2026-4408: s3:samr-server: deny, mask and/or single + quote username to 'check password script' + +We pass this on to the check password script, prevent remote command +execution. + +We now try to autodetect if we could implicitly use '%u' for the +replacement and fallback to a fixed fallback username. + +Admins should make use of SAMBA_CPS_ACCOUNT_NAME +instead of passing '%u' to 'check password script' + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 + +Pair-Programmed-With: Douglas Bagnall + +Signed-off-by: Stefan Metzmacher +Signed-off-by: Douglas Bagnall +--- + source3/rpc_server/samr/srv_samr_chgpasswd.c | 110 +++++++++++++++++-- + 1 file changed, 101 insertions(+), 9 deletions(-) + +diff --git a/source3/rpc_server/samr/srv_samr_chgpasswd.c b/source3/rpc_server/samr/srv_samr_chgpasswd.c +index 6c0c0da0cfc..9afb8799aea 100644 +--- a/source3/rpc_server/samr/srv_samr_chgpasswd.c ++++ b/source3/rpc_server/samr/srv_samr_chgpasswd.c +@@ -54,6 +54,7 @@ + #include "passdb.h" + #include "auth.h" + #include "lib/util/sys_rw.h" ++#include "lib/util/util_str_escape.h" + #include "librpc/rpc/dcerpc_samr.h" + + #include "lib/crypto/gnutls_helpers.h" +@@ -1008,27 +1009,118 @@ static bool check_passwd_history(struct samu *sampass, const char *plaintext) + /*********************************************************** + ************************************************************/ + ++static NTSTATUS check_password_complexity_internal(TALLOC_CTX *tosctx, ++ const char *orig_cmd, ++ const char *username, ++ char **cmd_out) ++{ ++ const char *fallback_username = "__CVE-2026-4408_FallbackUsername__"; ++ const char *inv = NULL; ++ char *cmd = NULL; ++ bool modified = false; ++ bool masked = false; ++ bool mixed_fallback = false; ++ ++ *cmd_out = NULL; ++ ++ if (username == NULL) { ++ return NT_STATUS_INVALID_USER_PRINCIPAL_NAME; ++ } ++ ++ /* ++ * This catches invalid characters in account names ++ * which might be problematic passing to a shell script. ++ */ ++ inv = strstr_for_invalid_account_characters(username); ++ if (inv != NULL) { ++ char *le_username = log_escape(tosctx, username); ++ ++ DBG_WARNING("username '%s' has invalid or dangerous characters\n", ++ le_username); ++ ++ TALLOC_FREE(le_username); ++ ++ return NT_STATUS_INVALID_USER_PRINCIPAL_NAME; ++ } ++ ++ /* ++ * This masks the remaining unsafe characters which ++ * are not already caught by strstr_for_invalid_account_characters() ++ * with '_'. ++ * ++ * Then it replaces %u with an single quoted ++ * and/or shell escaped version of the masked username. ++ */ ++ cmd = talloc_string_sub_unsafe(tosctx, ++ orig_cmd, ++ 'u', ++ username, ++ STRING_SUB_UNSAFE_CHARACTERS, ++ '_', ++ fallback_username, ++ &modified, ++ &masked, ++ &mixed_fallback); ++ if (cmd == NULL) { ++ return NT_STATUS_NO_MEMORY; ++ } ++ ++ /* ++ * Now warn about unexpected values ++ */ ++ ++ if (mixed_fallback) { ++ D_WARNING("CVE-2026-4408: " ++ "strange quoting in 'check password script', " ++ "falling back to replace %%u with %s, " ++ "use testparm to fix the configuration\n", ++ fallback_username); ++ D_WARNING("CVE-2026-4408: " ++ "You should use '%%u', or SAMBA_CPS_ACCOUNT_NAME " ++ "inside of 'check password script'.\n"); ++ } else if (masked) { ++ char *le_username = log_escape(tosctx, username); ++ ++ D_WARNING("CVE-2026-4408: " ++ "replaced %%u with masked value instead of: %s\n", ++ le_username); ++ D_WARNING("CVE-2026-4408: " ++ "You should use SAMBA_CPS_ACCOUNT_NAME inside " ++ "'check password script' instead of %%u.\n"); ++ ++ TALLOC_FREE(le_username); ++ } ++ ++ *cmd_out = cmd; ++ return NT_STATUS_OK; ++} ++ ++ + NTSTATUS check_password_complexity(const char *username, + const char *fullname, + const char *password, + enum samPwdChangeReason *samr_reject_reason) + { ++ int check_ret; ++ NTSTATUS status; + TALLOC_CTX *tosctx = talloc_tos(); + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); +- int check_ret; +- char *cmd; ++ const char *orig_cmd = NULL; ++ char *cmd = NULL; + +- /* Use external script to check password complexity */ +- if ((lp_check_password_script(tosctx, lp_sub) == NULL) +- || (*(lp_check_password_script(tosctx, lp_sub)) == '\0')){ ++ orig_cmd = lp_check_password_script(tosctx, lp_sub); ++ if (orig_cmd == NULL || orig_cmd[0] == '\0') { + return NT_STATUS_OK; + } + +- cmd = talloc_string_sub(tosctx, lp_check_password_script(tosctx, lp_sub), "%u", +- username); +- if (!cmd) { +- return NT_STATUS_PASSWORD_RESTRICTION; ++ /* note we don't use 'fullname' or 'password' here */ ++ status = check_password_complexity_internal(tosctx, ++ orig_cmd, ++ username, ++ &cmd); ++ if (!NT_STATUS_IS_OK(status)) { ++ return status; + } + + check_ret = setenv("SAMBA_CPS_ACCOUNT_NAME", username, 1); +-- +2.53.0 + + +From 5f898b13ad4361966c8d92a40f50b1a3c6a093ba Mon Sep 17 00:00:00 2001 +From: Douglas Bagnall +Date: Sat, 2 May 2026 22:12:38 +1200 +Subject: [PATCH 61/66] CVE-2026-4408: s3:samr-server: make + check_password_complexity_internal() non-static, for easier testing + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Douglas Bagnall +--- + source3/rpc_server/samr/srv_samr_chgpasswd.c | 8 ++++---- + source3/rpc_server/samr/srv_samr_util.h | 5 +++++ + 2 files changed, 9 insertions(+), 4 deletions(-) + +diff --git a/source3/rpc_server/samr/srv_samr_chgpasswd.c b/source3/rpc_server/samr/srv_samr_chgpasswd.c +index 9afb8799aea..3f48da47a5b 100644 +--- a/source3/rpc_server/samr/srv_samr_chgpasswd.c ++++ b/source3/rpc_server/samr/srv_samr_chgpasswd.c +@@ -1009,10 +1009,10 @@ static bool check_passwd_history(struct samu *sampass, const char *plaintext) + /*********************************************************** + ************************************************************/ + +-static NTSTATUS check_password_complexity_internal(TALLOC_CTX *tosctx, +- const char *orig_cmd, +- const char *username, +- char **cmd_out) ++NTSTATUS check_password_complexity_internal(TALLOC_CTX *tosctx, ++ const char *orig_cmd, ++ const char *username, ++ char **cmd_out) + { + const char *fallback_username = "__CVE-2026-4408_FallbackUsername__"; + const char *inv = NULL; +diff --git a/source3/rpc_server/samr/srv_samr_util.h b/source3/rpc_server/samr/srv_samr_util.h +index 5e839ac77c0..a3a22012858 100644 +--- a/source3/rpc_server/samr/srv_samr_util.h ++++ b/source3/rpc_server/samr/srv_samr_util.h +@@ -79,6 +79,11 @@ NTSTATUS pass_oem_change(char *user, const char *rhost, + uchar password_encrypted_with_nt_hash[516], + const uchar old_nt_hash_encrypted[16], + enum samPwdChangeReason *reject_reason); ++ ++NTSTATUS check_password_complexity_internal(TALLOC_CTX *mem_ctx, ++ const char *_orig_cmd, ++ const char *username, ++ char **cmd_out); + NTSTATUS check_password_complexity(const char *username, + const char *fullname, + const char *password, +-- +2.53.0 + + +From 1054c2ffdc707cee9778dbc0ff4083ae60d9b359 Mon Sep 17 00:00:00 2001 +From: Douglas Bagnall +Date: Sat, 2 May 2026 22:14:43 +1200 +Subject: [PATCH 62/66] CVE-2026-4408: s3:torture: tests for password + complexity scripts + +This tries to demonstrate the new logic for %u in +'check password script'. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 + +Pair-Programmed-With: Stefan Metzmacher + +Signed-off-by: Douglas Bagnall +Signed-off-by: Stefan Metzmacher +--- + selftest/tests.py | 2 + + source3/torture/test_rpc_samr.c | 358 ++++++++++++++++++++++++++++++++ + source3/torture/wscript_build | 6 + + 3 files changed, 366 insertions(+) + create mode 100644 source3/torture/test_rpc_samr.c + +diff --git a/selftest/tests.py b/selftest/tests.py +index c92676f66eb..84a4baa6e19 100644 +--- a/selftest/tests.py ++++ b/selftest/tests.py +@@ -585,6 +585,8 @@ plantestsuite("samba.unittests.test_oLschema2ldif", "none", + [os.path.join(bindir(), "default/source4/utils/oLschema2ldif/test_oLschema2ldif")]) + plantestsuite("samba.unittests.auth.sam", "none", + [os.path.join(bindir(), "test_auth_sam")]) ++plantestsuite("samba.unittests.test_rpc_samr", "none", ++ [os.path.join(bindir(), "test_rpc_samr")]) + if have_heimdal_support and not using_system_gssapi: + plantestsuite("samba.unittests.auth.heimdal_gensec_unwrap_des", "none", + [valgrindify(os.path.join(bindir(), "test_heimdal_gensec_unwrap_des"))]) +diff --git a/source3/torture/test_rpc_samr.c b/source3/torture/test_rpc_samr.c +new file mode 100644 +index 00000000000..8d4f3985246 +--- /dev/null ++++ b/source3/torture/test_rpc_samr.c +@@ -0,0 +1,358 @@ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include "includes.h" ++#include "talloc.h" ++#include "libcli/util/ntstatus.h" ++#include "../librpc/gen_ndr/samr.h" ++#include "rpc_server/samr/srv_samr_util.h" ++ ++/* set SAMR_DEBUG_VERBOSE to true to print more. */ ++#define SAMR_DEBUG_VERBOSE true ++ ++#if SAMR_DEBUG_VERBOSE ++#define debug_message(...) print_message(__VA_ARGS__) ++#else ++#define debug_message(...) /* debug_message */ ++#endif ++ ++static int setup_talloc_context(void **state) ++{ ++ TALLOC_CTX *mem_ctx = talloc_new(NULL); ++ *state = mem_ctx; ++ return 0; ++} ++ ++static int teardown_talloc_context(void **state) ++{ ++ TALLOC_CTX *mem_ctx = *state; ++ TALLOC_FREE(mem_ctx); ++ return 0; ++} ++ ++struct cmd_expansion { ++ const char *lp_cmd; ++ const char *username; ++ const char *result_cmd; ++ NTSTATUS result_code; ++}; ++ ++static struct cmd_expansion expansions[] = { ++ { ++ "/bin/echo '%u'", ++ "bob", ++ "/bin/echo 'bob'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo %u", ++ "bob", ++ "/bin/echo 'bob'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo %u", ++ "bob'", ++ "/bin/echo 'bob_'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo %u", ++ "bob\'", ++ "/bin/echo 'bob_'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo %u", ++ "bob'''", ++ "/bin/echo 'bob___'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo %u", ++ "bob*", ++ NULL, ++ NT_STATUS_INVALID_USER_PRINCIPAL_NAME ++ }, ++ { ++ "/bin/echo %u", ++ "bob\"", ++ NULL, ++ NT_STATUS_INVALID_USER_PRINCIPAL_NAME ++ }, ++ { ++ "/bin/echo '%u", ++ "bob bob bob", ++ "/bin/echo '__CVE-2026-4408_FallbackUsername__", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo \"%u\"", ++ " ", ++ "/bin/echo ' '", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo \"--uu=%u\"", ++ "bob", ++ "/bin/echo \"--uu=__CVE-2026-4408_FallbackUsername__\"", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo \"--uu=%u\"", ++ "bob !0", ++ "/bin/echo \"--uu=__CVE-2026-4408_FallbackUsername__\"", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo %u", ++ "!0", ++ "/bin/echo '!0'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo \"--uu=%u\"", ++ "bob \\", ++ NULL, ++ NT_STATUS_INVALID_USER_PRINCIPAL_NAME ++ }, ++ { ++ "/bin/echo --uu='%u'", ++ "bob >> x", ++ NULL, ++ NT_STATUS_INVALID_USER_PRINCIPAL_NAME ++ }, ++ { ++ "/bin/echo '--uu=%u\"", ++ "bob", ++ "/bin/echo '--uu=__CVE-2026-4408_FallbackUsername__\"", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo --uu='%u'", ++ "bob", ++ "/bin/echo --uu='bob'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo --uu'=%u'", ++ "bob", ++ "/bin/echo --uu'=__CVE-2026-4408_FallbackUsername__'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo --uu'=%u'", ++ "`ls`", ++ "/bin/echo --uu'=__CVE-2026-4408_FallbackUsername__'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo --uu'=%u'", ++ "$(ls)", ++ "/bin/echo --uu'=__CVE-2026-4408_FallbackUsername__'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo --uu='%u'", ++ "$(ls)", ++ "/bin/echo --uu='_(ls)'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo --uu=\"'%u'\"", ++ "bob", ++ "/bin/echo --uu=\"'bob'\"", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo --uu='%u' --yy='%u' '%u' %u", ++ "bob", ++ "/bin/echo --uu='bob' --yy='bob' 'bob' __CVE-2026-4408_FallbackUsername__", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo --uu=%u%u'' %user 50%u", ++ "bob", ++ "/bin/echo --uu=__CVE-2026-4408_FallbackUsername____CVE-2026-4408_FallbackUsername__'' __CVE-2026-4408_FallbackUsername__ser 50__CVE-2026-4408_FallbackUsername__", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo %u", ++ "!!", ++ "/bin/echo '!!'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo %u", ++ ">xxx", ++ NULL, ++ NT_STATUS_INVALID_USER_PRINCIPAL_NAME ++ }, ++ { ++ "/bin/echo %u", ++ "\\", ++ NULL, ++ NT_STATUS_INVALID_USER_PRINCIPAL_NAME ++ }, ++ { ++ "/bin/echo %u", ++ "3", ++ "/bin/echo '3'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo '%u'", ++ "3$", ++ "/bin/echo '3_'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo '%u'", ++ "comp$", ++ "/bin/echo 'comp_'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo '%u'", ++ "3$3", ++ "/bin/echo '3_3'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo '%u'", ++ "q $3", ++ "/bin/echo 'q _3'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo -s '%u' %u", ++ "āāā", ++ "/bin/echo -s 'āāā' __CVE-2026-4408_FallbackUsername__", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo -s '%u' %u", ++ "-āāā", ++ "/bin/echo -s '_āāā' __CVE-2026-4408_FallbackUsername__", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo -s %u", ++ "āāā", ++ "/bin/echo -s 'āāā'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo -s %u", ++ "a -a", ++ "/bin/echo -s 'a -a'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo -s=%u %u", ++ "ā -a", ++ "/bin/echo -s='ā -a' 'ā -a'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo -s=\"%u %u\"", ++ "ā -a", ++ "/bin/echo -s=\"__CVE-2026-4408_FallbackUsername__ __CVE-2026-4408_FallbackUsername__\"", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo -m='fridge' %u", ++ "ā -x -ß", ++ "/bin/echo -m='fridge' __CVE-2026-4408_FallbackUsername__", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo -m='fridge' %u", ++ "-ā -a", ++ "/bin/echo -m='fridge' __CVE-2026-4408_FallbackUsername__", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo %u", ++ "-n", ++ "/bin/echo '_n'", ++ NT_STATUS_OK ++ }, ++ { ++ "/bin/echo %u", ++ "o'clock", ++ "/bin/echo 'o_clock'", ++ NT_STATUS_OK ++ }, ++}; ++ ++static void test_expansions(void **state) ++{ ++ TALLOC_CTX *mem_ctx = *state; ++ size_t i; ++ ++ for (i = 0; i < ARRAY_SIZE(expansions); i++) { ++ struct cmd_expansion t = expansions[i]; ++ char *result_cmd = NULL; ++ NTSTATUS status; ++ ++ status = check_password_complexity_internal(mem_ctx, ++ t.lp_cmd, ++ t.username, ++ &result_cmd); ++ if (NT_STATUS_IS_OK(t.result_code) && NT_STATUS_IS_OK(status)) { ++ int cmp; ++ ++ cmp = strcmp(t.result_cmd, result_cmd); ++ if (cmp == 0) { ++ debug_message("[%zu] «%s» «%s» -> «%s», nstatus %s; AS EXPECTED\n", ++ i, t.lp_cmd, ++ t.username, ++ result_cmd, ++ nt_errstr(status)); ++ } else { ++ debug_message("[%zu] «%s» «%s», nstatus %s; " ++ "expected «%s» got «%s»\033[1;31m BAD! \033[0m\n", ++ i, t.lp_cmd, ++ t.username, ++ nt_errstr(status), ++ t.result_cmd, ++ result_cmd); ++ } ++ assert_int_equal(cmp, 0); ++ } else if (NT_STATUS_EQUAL(status, t.result_code)) { ++ debug_message("[%zu] «%s» «%s», nstatus %s FAILED AS EXPECTED\n", ++ i, t.lp_cmd, ++ t.username, ++ nt_errstr(status)); ++ } else { ++ debug_message("[%zu] «%s» «%s» -> «%s», nstatus %s; " ++ "EXPECTED result «%s» ntstatus %s; \033[1;31m BAD! \033[0m\n", ++ i, t.lp_cmd, ++ t.username, ++ result_cmd, ++ nt_errstr(status), ++ t.result_cmd, ++ nt_errstr(t.result_code)); ++ assert_int_equal(true, false); ++ } ++ } ++ debug_message("ALL correct\n"); ++} ++ ++int main(void) ++{ ++ const struct CMUnitTest tests[] = { ++ cmocka_unit_test(test_expansions), ++ }; ++ if (!isatty(1)) { ++ cmocka_set_message_output(CM_OUTPUT_SUBUNIT); ++ } ++ return cmocka_run_group_tests(tests, ++ setup_talloc_context, ++ teardown_talloc_context); ++} +diff --git a/source3/torture/wscript_build b/source3/torture/wscript_build +index 1d2520099e3..d04008b3df1 100644 +--- a/source3/torture/wscript_build ++++ b/source3/torture/wscript_build +@@ -133,3 +133,9 @@ bld.SAMBA3_BINARY('vfstest', + SMBREADLINE + ''', + for_selftest=True) ++ ++bld.SAMBA3_BINARY('test_rpc_samr', ++ source='test_rpc_samr.c', ++ deps='''RPC_SERVICE cmocka ++ ''', ++ for_selftest=True) +-- +2.53.0 + + +From cd0f499a2478ce5676aa1aefe378d50f11026517 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Fri, 8 May 2026 23:27:35 +0200 +Subject: [PATCH 63/66] CVE-2026-4408: s3:testparm: warn about 'check password + script' %u usage + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Douglas Bagnall +--- + source3/utils/testparm.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/source3/utils/testparm.c b/source3/utils/testparm.c +index c758313d466..49dc39b2cab 100644 +--- a/source3/utils/testparm.c ++++ b/source3/utils/testparm.c +@@ -359,6 +359,7 @@ static int do_global_checks(void) + const char **lp_ptr = NULL; + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); ++ const char *check_pw_script = NULL; + int ival; + + fprintf(stderr, "\n"); +@@ -831,6 +832,17 @@ static int do_global_checks(void) + #endif + } + ++ check_pw_script = lp_check_password_script(talloc_tos(), lp_sub); ++ if (talloc_string_sub_mixed_quoting(check_pw_script, 'u')) { ++ fprintf(stderr, ++ "WARNING: You are using 'check password script' " ++ "with mixed quoting and %%u.\n" ++ "CVE-2026-4408 changed the way %%u substitution works. \n" ++ "You should use the SAMBA_CPS_ACCOUNT_NAME " ++ "environment variable exported to the script, or\n" ++ "at least use single quotes (directly) around '%%u'.\n\n"); ++ } ++ + return ret; + } + +-- +2.53.0 + + +From 7c6d55ab1924e93a7c67ab563a7162260cf394eb Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Mon, 11 May 2026 13:52:52 +0200 +Subject: [PATCH 64/66] CVE-2026-4408: docs-xml/smbdotconf: clarify '%u' in + 'check password script' + +Admins should use SAMBA_CPS_ACCOUNT_NAME. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16034 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Douglas Bagnall +--- + docs-xml/smbdotconf/security/checkpasswordscript.xml | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/docs-xml/smbdotconf/security/checkpasswordscript.xml b/docs-xml/smbdotconf/security/checkpasswordscript.xml +index 18aa2c6d290..dd162d89f08 100644 +--- a/docs-xml/smbdotconf/security/checkpasswordscript.xml ++++ b/docs-xml/smbdotconf/security/checkpasswordscript.xml +@@ -20,8 +20,8 @@ + + + +- SAMBA_CPS_ACCOUNT_NAME is always present and contains the sAMAccountName of user, +- the is the same as the %u substitutions in the none AD DC case. ++ SAMBA_CPS_ACCOUNT_NAME is always present and contains the sAMAccountName of user. ++ It is the same as the '%u' substitutions in the non AD DC case. + + + +@@ -33,6 +33,12 @@ + + + ++ Even on a non AD DC SAMBA_CPS_ACCOUNT_NAME is the preferred way to access the ++ account name, as it contains the raw value provided by the client. If that's not ++ possible you should use single quotes (directly) around %u, e.g. /path/to/somescript '%u', ++ see CVE-2026-4408 for more details. ++ ++ + Note: In the example directory is a sample program called crackcheck + that uses cracklib to check the password quality. + +-- +2.53.0 + + +From fbf23d3f6e19dea6331c446e80cebf3018c7e332 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Fri, 17 Apr 2026 10:45:58 +0200 +Subject: [PATCH 65/66] third_party/ngtcp2: import v1.22.1 for CVE-2026-40170 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +For CVE-2026-40170 see: +https://github.com/ngtcp2/ngtcp2/security/advisories/GHSA-f523-465f-8c8f + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16059 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Andreas Schneider +(cherry picked from commit 706dc118b3bdbe03ada53939c1634ab13c91455a) + +Autobuild-User(v4-23-test): Björn Jacke +Autobuild-Date(v4-23-test): Mon Apr 27 12:59:57 UTC 2026 on atb-devel-224 +--- + third_party/ngtcp2/crypto/CMakeLists.txt | 6 + + third_party/ngtcp2/crypto/Makefile.am | 4 + + .../ngtcp2/crypto/boringssl/boringssl.c | 31 +- + .../libngtcp2_crypto_boringssl.pc.in | 1 + + third_party/ngtcp2/crypto/gnutls/gnutls.c | 117 +- + .../gnutls/libngtcp2_crypto_gnutls.pc.in | 1 + + .../ngtcp2/crypto/includes/CMakeLists.txt | 2 +- + .../ngtcp2/crypto/includes/Makefile.am | 4 + + .../crypto/includes/ngtcp2/ngtcp2_crypto.h | 122 +- + third_party/ngtcp2/crypto/ossl/CMakeLists.txt | 18 +- + .../crypto/ossl/libngtcp2_crypto_ossl.pc.in | 1 + + third_party/ngtcp2/crypto/ossl/ossl.c | 162 +- + .../picotls/libngtcp2_crypto_picotls.pc.in | 1 + + third_party/ngtcp2/crypto/picotls/picotls.c | 39 +- + third_party/ngtcp2/crypto/quictls/.gitignore | 1 + + .../ngtcp2/crypto/quictls/CMakeLists.txt | 32 +- + third_party/ngtcp2/crypto/quictls/Makefile.am | 16 +- + .../quictls/libngtcp2_crypto_libressl.pc.in | 34 + + .../quictls/libngtcp2_crypto_quictls.pc.in | 1 + + third_party/ngtcp2/crypto/quictls/quictls.c | 80 +- + third_party/ngtcp2/crypto/shared.c | 259 ++- + third_party/ngtcp2/crypto/shared.h | 20 - + .../wolfssl/libngtcp2_crypto_wolfssl.pc.in | 1 + + third_party/ngtcp2/crypto/wolfssl/wolfssl.c | 64 +- + third_party/ngtcp2/lib/CMakeLists.txt | 21 +- + third_party/ngtcp2/lib/Makefile.am | 10 +- + third_party/ngtcp2/lib/config.cmake.in | 3 + + .../ngtcp2/lib/includes/ngtcp2/ngtcp2.h | 748 ++++++- + third_party/ngtcp2/lib/ngtcp2_acktr.c | 36 +- + third_party/ngtcp2/lib/ngtcp2_acktr.h | 28 +- + third_party/ngtcp2/lib/ngtcp2_addr.c | 19 +- + third_party/ngtcp2/lib/ngtcp2_addr.h | 8 +- + third_party/ngtcp2/lib/ngtcp2_balloc.c | 14 +- + third_party/ngtcp2/lib/ngtcp2_bbr.c | 474 +++-- + third_party/ngtcp2/lib/ngtcp2_bbr.h | 26 +- + third_party/ngtcp2/lib/ngtcp2_buf.c | 14 +- + third_party/ngtcp2/lib/ngtcp2_buf.h | 11 +- + third_party/ngtcp2/lib/ngtcp2_callbacks.c | 75 + + third_party/ngtcp2/lib/ngtcp2_callbacks.h | 73 + + third_party/ngtcp2/lib/ngtcp2_cc.c | 283 ++- + third_party/ngtcp2/lib/ngtcp2_cc.h | 78 +- + third_party/ngtcp2/lib/ngtcp2_cid.c | 41 +- + third_party/ngtcp2/lib/ngtcp2_cid.h | 34 +- + third_party/ngtcp2/lib/ngtcp2_conn.c | 1875 ++++++++++++----- + third_party/ngtcp2/lib/ngtcp2_conn.h | 126 +- + third_party/ngtcp2/lib/ngtcp2_conn_info.c | 50 + + third_party/ngtcp2/lib/ngtcp2_conn_info.h | 45 + + third_party/ngtcp2/lib/ngtcp2_conn_stat.h | 39 + + third_party/ngtcp2/lib/ngtcp2_conv.c | 34 +- + third_party/ngtcp2/lib/ngtcp2_crypto.c | 24 +- + third_party/ngtcp2/lib/ngtcp2_crypto.h | 4 +- + third_party/ngtcp2/lib/ngtcp2_dcidtr.c | 17 +- + third_party/ngtcp2/lib/ngtcp2_dcidtr.h | 15 +- + third_party/ngtcp2/lib/ngtcp2_frame_chain.c | 73 +- + third_party/ngtcp2/lib/ngtcp2_frame_chain.h | 68 +- + third_party/ngtcp2/lib/ngtcp2_gaptr.c | 20 +- + third_party/ngtcp2/lib/ngtcp2_ksl.c | 361 ++-- + third_party/ngtcp2/lib/ngtcp2_ksl.h | 117 +- + third_party/ngtcp2/lib/ngtcp2_log.c | 604 +++--- + third_party/ngtcp2/lib/ngtcp2_log.h | 69 +- + third_party/ngtcp2/lib/ngtcp2_macro.h | 7 + + third_party/ngtcp2/lib/ngtcp2_map.c | 271 ++- + third_party/ngtcp2/lib/ngtcp2_map.h | 16 +- + third_party/ngtcp2/lib/ngtcp2_net.h | 8 +- + third_party/ngtcp2/lib/ngtcp2_objalloc.h | 4 +- + third_party/ngtcp2/lib/ngtcp2_path.c | 6 - + third_party/ngtcp2/lib/ngtcp2_path.h | 8 - + third_party/ngtcp2/lib/ngtcp2_pcg.c | 88 + + third_party/ngtcp2/lib/ngtcp2_pcg.h | 54 + + third_party/ngtcp2/lib/ngtcp2_pkt.c | 398 +++- + third_party/ngtcp2/lib/ngtcp2_pkt.h | 181 +- + third_party/ngtcp2/lib/ngtcp2_ppe.c | 4 +- + third_party/ngtcp2/lib/ngtcp2_pv.c | 19 +- + third_party/ngtcp2/lib/ngtcp2_pv.h | 24 +- + third_party/ngtcp2/lib/ngtcp2_qlog.c | 185 +- + third_party/ngtcp2/lib/ngtcp2_qlog.h | 2 +- + third_party/ngtcp2/lib/ngtcp2_range.c | 22 +- + third_party/ngtcp2/lib/ngtcp2_ratelim.c | 84 + + third_party/ngtcp2/lib/ngtcp2_ratelim.h | 59 + + third_party/ngtcp2/lib/ngtcp2_ringbuf.c | 14 +- + third_party/ngtcp2/lib/ngtcp2_ringbuf.h | 4 +- + third_party/ngtcp2/lib/ngtcp2_rob.c | 50 +- + third_party/ngtcp2/lib/ngtcp2_rob.h | 8 +- + third_party/ngtcp2/lib/ngtcp2_rst.c | 48 +- + third_party/ngtcp2/lib/ngtcp2_rst.h | 7 +- + third_party/ngtcp2/lib/ngtcp2_rtb.c | 210 +- + third_party/ngtcp2/lib/ngtcp2_rtb.h | 22 +- + third_party/ngtcp2/lib/ngtcp2_settings.c | 7 + + third_party/ngtcp2/lib/ngtcp2_settings.h | 7 + + third_party/ngtcp2/lib/ngtcp2_str.c | 187 +- + third_party/ngtcp2/lib/ngtcp2_str.h | 35 +- + third_party/ngtcp2/lib/ngtcp2_strm.c | 210 +- + third_party/ngtcp2/lib/ngtcp2_strm.h | 40 +- + .../ngtcp2/lib/ngtcp2_transport_params.c | 21 +- + .../ngtcp2/lib/ngtcp2_transport_params.h | 14 +- + third_party/ngtcp2/lib/ngtcp2_vec.c | 39 +- + third_party/ngtcp2/lib/ngtcp2_vec.h | 31 +- + third_party/ngtcp2/lib/ngtcp2_window_filter.c | 2 +- + third_party/ngtcp2/wscript | 4 + + 99 files changed, 5992 insertions(+), 2962 deletions(-) + create mode 100644 third_party/ngtcp2/crypto/quictls/libngtcp2_crypto_libressl.pc.in + create mode 100644 third_party/ngtcp2/lib/ngtcp2_callbacks.c + create mode 100644 third_party/ngtcp2/lib/ngtcp2_callbacks.h + create mode 100644 third_party/ngtcp2/lib/ngtcp2_conn_info.c + create mode 100644 third_party/ngtcp2/lib/ngtcp2_conn_info.h + create mode 100644 third_party/ngtcp2/lib/ngtcp2_pcg.c + create mode 100644 third_party/ngtcp2/lib/ngtcp2_pcg.h + create mode 100644 third_party/ngtcp2/lib/ngtcp2_ratelim.c + create mode 100644 third_party/ngtcp2/lib/ngtcp2_ratelim.h + +diff --git a/third_party/ngtcp2/crypto/CMakeLists.txt b/third_party/ngtcp2/crypto/CMakeLists.txt +index c947837b31e..5e428fa812e 100644 +--- a/third_party/ngtcp2/crypto/CMakeLists.txt ++++ b/third_party/ngtcp2/crypto/CMakeLists.txt +@@ -31,6 +31,12 @@ elseif(ENABLE_OPENSSL) + message(WARNING "libngtcp2_crypto_quictls library is disabled due to lack of good quictls") + endif() + ++if(HAVE_LIBRESSL) ++ add_subdirectory(quictls) ++elseif(ENABLE_OPENSSL) ++ message(WARNING "libngtcp2_crypto_libressl library is disabled due to lack of good LibreSSL") ++endif() ++ + if(HAVE_GNUTLS) + add_subdirectory(gnutls) + elseif(ENABLE_GNUTLS) +diff --git a/third_party/ngtcp2/crypto/Makefile.am b/third_party/ngtcp2/crypto/Makefile.am +index 8d2f7600f17..27224b9dcc1 100644 +--- a/third_party/ngtcp2/crypto/Makefile.am ++++ b/third_party/ngtcp2/crypto/Makefile.am +@@ -30,6 +30,10 @@ if HAVE_QUICTLS + SUBDIRS += quictls + endif + ++if HAVE_LIBRESSL ++SUBDIRS += quictls ++endif ++ + if HAVE_GNUTLS + SUBDIRS += gnutls + endif +diff --git a/third_party/ngtcp2/crypto/boringssl/boringssl.c b/third_party/ngtcp2/crypto/boringssl/boringssl.c +index 283063f738e..e43faa76850 100644 +--- a/third_party/ngtcp2/crypto/boringssl/boringssl.c ++++ b/third_party/ngtcp2/crypto/boringssl/boringssl.c +@@ -39,6 +39,7 @@ + #include + #include + ++#include "ngtcp2_macro.h" + #include "shared.h" + + typedef enum ngtcp2_crypto_boringssl_cipher_type { +@@ -401,7 +402,7 @@ int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample) { +- static const uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00"; ++ static const uint8_t PLAINTEXT[16] = {0}; + ngtcp2_crypto_boringssl_cipher_ctx *ctx = hp_ctx->native_handle; + uint32_t counter; + +@@ -419,7 +420,7 @@ int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + #else /* !defined(WORDS_BIGENDIAN) */ + memcpy(&counter, sample, sizeof(counter)); + #endif /* !defined(WORDS_BIGENDIAN) */ +- CRYPTO_chacha_20(dest, PLAINTEXT, sizeof(PLAINTEXT) - 1, ctx->key, ++ CRYPTO_chacha_20(dest, PLAINTEXT, sizeof(PLAINTEXT), ctx->key, + sample + sizeof(counter), counter); + return 0; + default: +@@ -435,7 +436,8 @@ int ngtcp2_crypto_read_write_crypto_data( + int rv; + int err; + +- if (SSL_provide_quic_data( ++ if (datalen && ++ SSL_provide_quic_data( + ssl, + ngtcp2_crypto_boringssl_from_ngtcp2_encryption_level(encryption_level), + data, datalen) != 1) { +@@ -464,6 +466,16 @@ int ngtcp2_crypto_read_write_crypto_data( + } + + goto retry; ++ case SSL_ERROR_WANT_X509_LOOKUP: ++ case SSL_ERROR_WANT_PRIVATE_KEY_OPERATION: ++ case SSL_ERROR_WANT_CERTIFICATE_VERIFY: ++ /* It might be better to return this error, but ngtcp2 does ++ not need to know whether handshake has been interrupted or ++ not. We expect that necessary plumbing should be done by ++ application when handshake is interrupted (e.g., via ++ SSL_PRIVATE_KEY_METHOD). If it does not work, we will ++ reconsider this. */ ++ return 0; + default: + return -1; + } +@@ -567,6 +579,19 @@ int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, uint8_t *data, + return 0; + } + ++int ngtcp2_crypto_get_path_challenge_data2_cb(ngtcp2_conn *conn, ++ ngtcp2_path_challenge_data *data, ++ void *user_data) { ++ (void)conn; ++ (void)user_data; ++ ++ if (RAND_bytes(data->data, NGTCP2_PATH_CHALLENGE_DATALEN) != 1) { ++ return NGTCP2_ERR_CALLBACK_FAILURE; ++ } ++ ++ return 0; ++} ++ + int ngtcp2_crypto_random(uint8_t *data, size_t datalen) { + if (RAND_bytes(data, datalen) != 1) { + return -1; +diff --git a/third_party/ngtcp2/crypto/boringssl/libngtcp2_crypto_boringssl.pc.in b/third_party/ngtcp2/crypto/boringssl/libngtcp2_crypto_boringssl.pc.in +index 737970a8c6a..cf6ebbca5c2 100644 +--- a/third_party/ngtcp2/crypto/boringssl/libngtcp2_crypto_boringssl.pc.in ++++ b/third_party/ngtcp2/crypto/boringssl/libngtcp2_crypto_boringssl.pc.in +@@ -31,3 +31,4 @@ URL: https://github.com/ngtcp2/ngtcp2 + Version: @VERSION@ + Libs: -L${libdir} -lngtcp2_crypto_boringssl + Cflags: -I${includedir} ++Requires.private: libngtcp2 +diff --git a/third_party/ngtcp2/crypto/gnutls/gnutls.c b/third_party/ngtcp2/crypto/gnutls/gnutls.c +index 4e6990f87ef..cf7e6ca8934 100644 +--- a/third_party/ngtcp2/crypto/gnutls/gnutls.c ++++ b/third_party/ngtcp2/crypto/gnutls/gnutls.c +@@ -35,6 +35,7 @@ + #include + #include + ++#include "ngtcp2_macro.h" + #include "shared.h" + + ngtcp2_crypto_aead *ngtcp2_crypto_aead_aes_128_gcm(ngtcp2_crypto_aead *aead) { +@@ -219,14 +220,15 @@ int ngtcp2_crypto_aead_ctx_encrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx, + gnutls_cipher_algorithm_t cipher = + (gnutls_cipher_algorithm_t)(intptr_t)aead->native_handle; + gnutls_aead_cipher_hd_t hd; +- gnutls_datum_t _key; + + (void)noncelen; + +- _key.data = (void *)key; +- _key.size = (unsigned int)ngtcp2_crypto_aead_keylen(aead); +- +- if (gnutls_aead_cipher_init(&hd, cipher, &_key) != 0) { ++ if (gnutls_aead_cipher_init( ++ &hd, cipher, ++ &(gnutls_datum_t){ ++ .data = (uint8_t *)key, ++ .size = (unsigned int)ngtcp2_crypto_aead_keylen(aead), ++ }) != 0) { + return -1; + } + +@@ -241,14 +243,15 @@ int ngtcp2_crypto_aead_ctx_decrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx, + gnutls_cipher_algorithm_t cipher = + (gnutls_cipher_algorithm_t)(intptr_t)aead->native_handle; + gnutls_aead_cipher_hd_t hd; +- gnutls_datum_t _key; + + (void)noncelen; + +- _key.data = (void *)key; +- _key.size = (unsigned int)ngtcp2_crypto_aead_keylen(aead); +- +- if (gnutls_aead_cipher_init(&hd, cipher, &_key) != 0) { ++ if (gnutls_aead_cipher_init( ++ &hd, cipher, ++ &(gnutls_datum_t){ ++ .data = (uint8_t *)key, ++ .size = (unsigned int)ngtcp2_crypto_aead_keylen(aead), ++ }) != 0) { + return -1; + } + +@@ -266,15 +269,17 @@ void ngtcp2_crypto_aead_ctx_free(ngtcp2_crypto_aead_ctx *aead_ctx) { + int ngtcp2_crypto_cipher_ctx_encrypt_init(ngtcp2_crypto_cipher_ctx *cipher_ctx, + const ngtcp2_crypto_cipher *cipher, + const uint8_t *key) { +- gnutls_cipher_algorithm_t _cipher = ++ gnutls_cipher_algorithm_t cph = + (gnutls_cipher_algorithm_t)(intptr_t)cipher->native_handle; + gnutls_cipher_hd_t hd; +- gnutls_datum_t _key; + +- _key.data = (void *)key; +- _key.size = (unsigned int)gnutls_cipher_get_key_size(_cipher); +- +- if (gnutls_cipher_init(&hd, _cipher, &_key, NULL) != 0) { ++ if (gnutls_cipher_init( ++ &hd, cph, ++ &(gnutls_datum_t){ ++ .data = (uint8_t *)key, ++ .size = (unsigned int)gnutls_cipher_get_key_size(cph), ++ }, ++ NULL) != 0) { + return -1; + } + +@@ -294,10 +299,17 @@ int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md, + const uint8_t *salt, size_t saltlen) { + gnutls_mac_algorithm_t prf = + (gnutls_mac_algorithm_t)(intptr_t)md->native_handle; +- gnutls_datum_t _secret = {(void *)secret, (unsigned int)secretlen}; +- gnutls_datum_t _salt = {(void *)salt, (unsigned int)saltlen}; + +- if (gnutls_hkdf_extract(prf, &_secret, &_salt, dest) != 0) { ++ if (gnutls_hkdf_extract(prf, ++ &(gnutls_datum_t){ ++ .data = (uint8_t *)secret, ++ .size = (unsigned int)secretlen, ++ }, ++ &(gnutls_datum_t){ ++ .data = (uint8_t *)salt, ++ .size = (unsigned int)saltlen, ++ }, ++ dest) != 0) { + return -1; + } + +@@ -310,10 +322,17 @@ int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen, + size_t infolen) { + gnutls_mac_algorithm_t prf = + (gnutls_mac_algorithm_t)(intptr_t)md->native_handle; +- gnutls_datum_t _secret = {(void *)secret, (unsigned int)secretlen}; +- gnutls_datum_t _info = {(void *)info, (unsigned int)infolen}; + +- if (gnutls_hkdf_expand(prf, &_secret, &_info, dest, destlen) != 0) { ++ if (gnutls_hkdf_expand(prf, ++ &(gnutls_datum_t){ ++ .data = (uint8_t *)secret, ++ .size = (unsigned int)secretlen, ++ }, ++ &(gnutls_datum_t){ ++ .data = (uint8_t *)info, ++ .size = (unsigned int)infolen, ++ }, ++ dest, destlen) != 0) { + return -1; + } + +@@ -328,18 +347,32 @@ int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen, + (gnutls_mac_algorithm_t)(intptr_t)md->native_handle; + size_t keylen = ngtcp2_crypto_md_hashlen(md); + uint8_t key[64]; +- gnutls_datum_t _secret = {(void *)secret, (unsigned int)secretlen}; +- gnutls_datum_t _key = {(void *)key, (unsigned int)keylen}; +- gnutls_datum_t _salt = {(void *)salt, (unsigned int)saltlen}; +- gnutls_datum_t _info = {(void *)info, (unsigned int)infolen}; + + assert(keylen <= sizeof(key)); + +- if (gnutls_hkdf_extract(prf, &_secret, &_salt, key) != 0) { ++ if (gnutls_hkdf_extract(prf, ++ &(gnutls_datum_t){ ++ .data = (uint8_t *)secret, ++ .size = (unsigned int)secretlen, ++ }, ++ &(gnutls_datum_t){ ++ .data = (uint8_t *)salt, ++ .size = (unsigned int)saltlen, ++ }, ++ key) != 0) { + return -1; + } + +- if (gnutls_hkdf_expand(prf, &_key, &_info, dest, destlen) != 0) { ++ if (gnutls_hkdf_expand(prf, ++ &(gnutls_datum_t){ ++ .data = (uint8_t *)key, ++ .size = (unsigned int)keylen, ++ }, ++ &(gnutls_datum_t){ ++ .data = (uint8_t *)info, ++ .size = (unsigned int)infolen, ++ }, ++ dest, destlen) != 0) { + return -1; + } + +@@ -402,13 +435,11 @@ int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + switch (cipher) { + case GNUTLS_CIPHER_AES_128_CBC: + case GNUTLS_CIPHER_AES_256_CBC: { +- uint8_t iv[16]; +- uint8_t buf[16]; +- + /* Emulate one block AES-ECB by invalidating the effect of IV */ +- memset(iv, 0, sizeof(iv)); ++ static const uint8_t iv[16] = {0}; ++ uint8_t buf[16]; + +- gnutls_cipher_set_iv(hd, iv, sizeof(iv)); ++ gnutls_cipher_set_iv(hd, (uint8_t *)iv, sizeof(iv)); + + if (gnutls_cipher_encrypt2(hd, sample, 16, buf, sizeof(buf)) != 0) { + return -1; +@@ -418,14 +449,14 @@ int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + } break; + + case GNUTLS_CIPHER_CHACHA20_32: { +- static const uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00"; ++ static const uint8_t PLAINTEXT[16] = {0}; + uint8_t buf[5 + 16]; + size_t buflen = sizeof(buf); + + gnutls_cipher_set_iv(hd, (void *)sample, 16); + +- if (gnutls_cipher_encrypt2(hd, PLAINTEXT, sizeof(PLAINTEXT) - 1, buf, +- buflen) != 0) { ++ if (gnutls_cipher_encrypt2(hd, PLAINTEXT, sizeof(PLAINTEXT), buf, buflen) != ++ 0) { + return -1; + } + +@@ -542,6 +573,20 @@ int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, uint8_t *data, + return 0; + } + ++int ngtcp2_crypto_get_path_challenge_data2_cb(ngtcp2_conn *conn, ++ ngtcp2_path_challenge_data *data, ++ void *user_data) { ++ (void)conn; ++ (void)user_data; ++ ++ if (gnutls_rnd(GNUTLS_RND_RANDOM, data->data, ++ NGTCP2_PATH_CHALLENGE_DATALEN) != 0) { ++ return NGTCP2_ERR_CALLBACK_FAILURE; ++ } ++ ++ return 0; ++} ++ + int ngtcp2_crypto_random(uint8_t *data, size_t datalen) { + if (gnutls_rnd(GNUTLS_RND_RANDOM, data, datalen) != 0) { + return -1; +diff --git a/third_party/ngtcp2/crypto/gnutls/libngtcp2_crypto_gnutls.pc.in b/third_party/ngtcp2/crypto/gnutls/libngtcp2_crypto_gnutls.pc.in +index 890e89d45f8..3be801a7e85 100644 +--- a/third_party/ngtcp2/crypto/gnutls/libngtcp2_crypto_gnutls.pc.in ++++ b/third_party/ngtcp2/crypto/gnutls/libngtcp2_crypto_gnutls.pc.in +@@ -31,3 +31,4 @@ URL: https://github.com/ngtcp2/ngtcp2 + Version: @VERSION@ + Libs: -L${libdir} -lngtcp2_crypto_gnutls + Cflags: -I${includedir} ++Requires.private: libngtcp2, gnutls +diff --git a/third_party/ngtcp2/crypto/includes/CMakeLists.txt b/third_party/ngtcp2/crypto/includes/CMakeLists.txt +index 8415158c7e4..888c4991bee 100644 +--- a/third_party/ngtcp2/crypto/includes/CMakeLists.txt ++++ b/third_party/ngtcp2/crypto/includes/CMakeLists.txt +@@ -25,7 +25,7 @@ install(FILES + ngtcp2/ngtcp2_crypto.h + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ngtcp2") + +-if(HAVE_QUICTLS) ++if(HAVE_QUICTLS OR HAVE_LIBRESSL) + install(FILES + ngtcp2/ngtcp2_crypto_quictls.h + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ngtcp2") +diff --git a/third_party/ngtcp2/crypto/includes/Makefile.am b/third_party/ngtcp2/crypto/includes/Makefile.am +index 4aebf426a3a..63b22af85a9 100644 +--- a/third_party/ngtcp2/crypto/includes/Makefile.am ++++ b/third_party/ngtcp2/crypto/includes/Makefile.am +@@ -28,6 +28,10 @@ if HAVE_QUICTLS + nobase_include_HEADERS += ngtcp2/ngtcp2_crypto_quictls.h + endif + ++if HAVE_LIBRESSL ++nobase_include_HEADERS += ngtcp2/ngtcp2_crypto_quictls.h ++endif ++ + if HAVE_GNUTLS + nobase_include_HEADERS += ngtcp2/ngtcp2_crypto_gnutls.h + endif +diff --git a/third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto.h b/third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto.h +index 003ec6b4c3f..28eeeb5ead8 100644 +--- a/third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto.h ++++ b/third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto.h +@@ -524,11 +524,14 @@ NGTCP2_EXTERN int ngtcp2_crypto_recv_client_initial_cb(ngtcp2_conn *conn, + * completes. It is allowed to call this function with |datalen| == + * 0. In this case, no additional read operation is done. + * ++ * This function is implemented per TLS backend. See ++ * :ref:`tls-integration` for more details. ++ * + * This function returns 0 if it succeeds, or a negative error code. + * The generic error code is -1 if a specific error code is not + * suitable. The error codes less than -10000 are specific to +- * underlying TLS implementation. For quictls, the error codes are +- * defined in *ngtcp2_crypto_quictls.h*. ++ * underlying TLS implementation. Refer to the implementation ++ * specific header files for error codes. + */ + NGTCP2_EXTERN int + ngtcp2_crypto_read_write_crypto_data(ngtcp2_conn *conn, +@@ -542,11 +545,22 @@ ngtcp2_crypto_read_write_crypto_data(ngtcp2_conn *conn, + * `ngtcp2_crypto_read_write_crypto_data`. It can be directly passed + * to :member:`ngtcp2_callbacks.recv_crypto_data` field. + * ++ * For quictls and OpenSSL, the following error codes are treated as ++ * success: ++ * ++ * - -10001 (e.g., :macro:`NGTCP2_CRYPTO_QUICTLS_ERR_TLS_WANT_X509_LOOKUP`) ++ * - -10002 (e.g., :macro:`NGTCP2_CRYPTO_QUICTLS_ERR_TLS_WANT_CLIENT_HELLO_CB`) ++ * ++ * To continue the interrupted handshake, call ++ * `ngtcp2_conn_continue_handshake`. ++ * ++ * See :ref:`tls-integration` for more details. ++ * + * If this function is used, the TLS implementation specific error + * codes described in `ngtcp2_crypto_read_write_crypto_data` are +- * treated as if it returns -1. Do not use this function if an +- * application wishes to use the TLS implementation specific error +- * codes. ++ * treated as if it returns -1 except for those that are listed above. ++ * Do not use this function if an application wishes to use the TLS ++ * implementation specific error codes. + */ + NGTCP2_EXTERN int ngtcp2_crypto_recv_crypto_data_cb( + ngtcp2_conn *conn, ngtcp2_encryption_level encryption_level, uint64_t offset, +@@ -583,7 +597,7 @@ NGTCP2_EXTERN int ngtcp2_crypto_generate_stateless_reset_token( + * :macro:`NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY` is the magic byte for + * Retry token generated by `ngtcp2_crypto_generate_retry_token`. + */ +-#define NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY 0xb6 ++#define NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY 0xB6 + + /** + * @macro +@@ -591,7 +605,7 @@ NGTCP2_EXTERN int ngtcp2_crypto_generate_stateless_reset_token( + * :macro:`NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY2` is the magic byte for + * Retry token generated by `ngtcp2_crypto_generate_retry_token2`. + */ +-#define NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY2 0xb7 ++#define NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY2 0xB7 + + /** + * @macro +@@ -627,7 +641,10 @@ NGTCP2_EXTERN int ngtcp2_crypto_generate_stateless_reset_token( + * @macro + * + * :macro:`NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN` is the maximum length +- * of a token generated by `ngtcp2_crypto_generate_regular_token`. ++ * of a token generated by `ngtcp2_crypto_generate_regular_token`. ++ * `ngtcp2_crypto_generate_regular_token2` generates a token of length ++ * at most :macro:`NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN` bytes + the ++ * length of the provided opaque data. + */ + #define NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN \ + (/* magic = */ 1 + sizeof(ngtcp2_tstamp) + /* aead tag = */ 16 + \ +@@ -787,6 +804,77 @@ NGTCP2_EXTERN int ngtcp2_crypto_verify_regular_token( + size_t secretlen, const ngtcp2_sockaddr *remote_addr, + ngtcp2_socklen remote_addrlen, ngtcp2_duration timeout, ngtcp2_tstamp ts); + ++/** ++ * @function ++ * ++ * `ngtcp2_crypto_generate_regular_token2` generates a token in the ++ * buffer pointed by |token| that is sent with NEW_TOKEN frame. The ++ * buffer pointed by |token| must have at least ++ * :macro:`NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN` + |datalen| bytes long. ++ * The successfully generated token starts with ++ * :macro:`NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR`. |secret| of length ++ * |secretlen| is a keying material to generate keys to encrypt the ++ * token. |remote_addr| of length |remote_addrlen| is an address of ++ * client. |ts| is the timestamp when the token is generated. |data| ++ * of length |datalen| is an opaque data embedded in the token. ++ * |datalen| must be less than or equal to 256. ++ * ++ * Calling this function with |datalen| = 0 is equivalent to calling ++ * `ngtcp2_crypto_generate_regular_token`. ++ * ++ * To get the opaque data after successful verification, use ++ * `ngtcp2_crypto_verify_regular_token2`. ++ * `ngtcp2_crypto_verify_regular_token` can verify the token with ++ * |datalen| > 0, but it discards the opaque data. ++ * ++ * This function returns the length of generated token if it succeeds, ++ * or -1. ++ */ ++NGTCP2_EXTERN ngtcp2_ssize ngtcp2_crypto_generate_regular_token2( ++ uint8_t *token, const uint8_t *secret, size_t secretlen, ++ const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen, ++ const void *data, size_t datalen, ngtcp2_tstamp ts); ++ ++/** ++ * @function ++ * ++ * `ngtcp2_crypto_verify_regular_token2` verifies a regular token ++ * stored in the buffer pointed by |token| of length |tokenlen|. ++ * |secret| of length |secretlen| is a keying material to generate ++ * keys to decrypt the token. |remote_addr| of length ++ * |remote_addrlen| is an address of client. |timeout| is the period ++ * during which the token is valid. |ts| is the current timestamp. ++ * |data| is the pointer to the buffer of length at least ++ * |max_datalen| bytes. If the token is verified successfully, the ++ * opaque data embedded in the token is copied to the buffer pointed ++ * by |data|. ++ * ++ * If |tokenlen| is less than ++ * :macro:`NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN`, this function returns ++ * :macro:`NGTCP2_CRYPTO_ERR_UNREADABLE_TOKEN`. ++ * ++ * If the length of opaque data is larger than |max_datalen|, the ++ * verification still succeeds, but nothing is written to the buffer ++ * pointed by |data|, and this function returns 0. In other words, ++ * the opaque data is discarded. ++ * ++ * This function returns the number of the opaque data written to the ++ * buffer pointed by |data| if it succeeds, or one of the following ++ * negative error codes: ++ * ++ * :macro:`NGTCP2_CRYPTO_ERR_UNREADABLE_TOKEN` ++ * A token is badly formatted; or verifying the integrity ++ * protection failed. ++ * :macro:`NGTCP2_CRYPTO_ERR_VERIFY_TOKEN` ++ * A token validity has expired. ++ * :macro:`NGTCP2_CRYPTO_ERR_INTERNAL` ++ * Internal error occurred. ++ */ ++NGTCP2_EXTERN ngtcp2_ssize ngtcp2_crypto_verify_regular_token2( ++ void *data, size_t max_datalen, const uint8_t *token, size_t tokenlen, ++ const uint8_t *secret, size_t secretlen, const ngtcp2_sockaddr *remote_addr, ++ ngtcp2_socklen remote_addrlen, ngtcp2_duration timeout, ngtcp2_tstamp ts); ++ + /** + * @function + * +@@ -904,11 +992,29 @@ NGTCP2_EXTERN void ngtcp2_crypto_delete_crypto_cipher_ctx_cb( + * + * This function can be directly passed to + * :member:`ngtcp2_callbacks.get_path_challenge_data` field. ++ * ++ * Deprecated since v1.22.0. Use ++ * `ngtcp2_crypto_get_path_challenge_data2_cb` instead. + */ + NGTCP2_EXTERN int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, + uint8_t *data, + void *user_data); + ++/** ++ * @function ++ * ++ * `ngtcp2_crypto_get_path_challenge_data2_cb` writes unpredictable ++ * sequence of :macro:`NGTCP2_PATH_CHALLENGE_DATALEN` bytes to |data| ++ * which is sent with PATH_CHALLENGE frame. ++ * ++ * This function can be directly passed to ++ * :member:`ngtcp2_callbacks.get_path_challenge_data2` field. ++ * ++ * This function has been available since v1.22.0. ++ */ ++NGTCP2_EXTERN int ngtcp2_crypto_get_path_challenge_data2_cb( ++ ngtcp2_conn *conn, ngtcp2_path_challenge_data *data, void *user_data); ++ + /** + * @function + * +diff --git a/third_party/ngtcp2/crypto/ossl/CMakeLists.txt b/third_party/ngtcp2/crypto/ossl/CMakeLists.txt +index da2ef2df763..1a108ada0a4 100644 +--- a/third_party/ngtcp2/crypto/ossl/CMakeLists.txt ++++ b/third_party/ngtcp2/crypto/ossl/CMakeLists.txt +@@ -29,14 +29,11 @@ set(ngtcp2_crypto_ossl_SOURCES + ) + + set(ngtcp2_crypto_ossl_INCLUDE_DIRS +- "${CMAKE_CURRENT_SOURCE_DIR}/../../lib/includes" +- "${CMAKE_CURRENT_BINARY_DIR}/../../lib/includes" +- "${CMAKE_CURRENT_SOURCE_DIR}/../../lib" +- "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto/includes" +- "${CMAKE_CURRENT_BINARY_DIR}/../../crypto/includes" +- "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto" +- "${CMAKE_CURRENT_BINARY_DIR}/../../crypto" +- "${OPENSSL_INCLUDE_DIRS}" ++ "$" ++ "$" ++ "$" ++ "$" ++ "$" + ) + + foreach(name libngtcp2_crypto_ossl.pc) +@@ -55,9 +52,10 @@ if(ENABLE_SHARED_LIB) + ) + target_include_directories(ngtcp2_crypto_ossl PUBLIC + ${ngtcp2_crypto_ossl_INCLUDE_DIRS}) +- target_link_libraries(ngtcp2_crypto_ossl ngtcp2 ${OPENSSL_LIBRARIES}) ++ target_link_libraries(ngtcp2_crypto_ossl PUBLIC ngtcp2 OpenSSL::SSL) + + install(TARGETS ngtcp2_crypto_ossl ++ EXPORT "${PROJECT_NAME}Targets" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") +@@ -77,8 +75,10 @@ if(ENABLE_STATIC_LIB) + "-DNGTCP2_STATICLIB") + target_include_directories(ngtcp2_crypto_ossl_static PUBLIC + ${ngtcp2_crypto_ossl_INCLUDE_DIRS}) ++ target_link_libraries(ngtcp2_crypto_ossl_static PUBLIC ngtcp2_static OpenSSL::SSL) + + install(TARGETS ngtcp2_crypto_ossl_static ++ EXPORT "${PROJECT_NAME}Targets" + DESTINATION "${CMAKE_INSTALL_LIBDIR}") + endif() + +diff --git a/third_party/ngtcp2/crypto/ossl/libngtcp2_crypto_ossl.pc.in b/third_party/ngtcp2/crypto/ossl/libngtcp2_crypto_ossl.pc.in +index 1397b4bfb42..39c254a309b 100644 +--- a/third_party/ngtcp2/crypto/ossl/libngtcp2_crypto_ossl.pc.in ++++ b/third_party/ngtcp2/crypto/ossl/libngtcp2_crypto_ossl.pc.in +@@ -31,3 +31,4 @@ URL: https://github.com/ngtcp2/ngtcp2 + Version: @VERSION@ + Libs: -L${libdir} -lngtcp2_crypto_ossl + Cflags: -I${includedir} ++Requires.private: libngtcp2, openssl +diff --git a/third_party/ngtcp2/crypto/ossl/ossl.c b/third_party/ngtcp2/crypto/ossl/ossl.c +index 061d6acd21d..fa393c8ece5 100644 +--- a/third_party/ngtcp2/crypto/ossl/ossl.c ++++ b/third_party/ngtcp2/crypto/ossl/ossl.c +@@ -41,70 +41,40 @@ + #include "ngtcp2_macro.h" + #include "shared.h" + +-static int crypto_initialized; ++#if defined(OPENSSL_NO_CHACHA) || defined(OPENSSL_NO_POLY1305) ++# define NGTCP2_NO_CHACHA_POLY1305 ++#endif /* defined(OPENSSL_NO_CHACHA) || \ ++ defined(OPENSSL_NO_POLY1305) */ ++ + static EVP_CIPHER *crypto_aes_128_gcm; + static EVP_CIPHER *crypto_aes_256_gcm; +-static EVP_CIPHER *crypto_chacha20_poly1305; + static EVP_CIPHER *crypto_aes_128_ccm; +-static EVP_CIPHER *crypto_aes_128_ctr; +-static EVP_CIPHER *crypto_aes_256_ctr; ++static EVP_CIPHER *crypto_aes_128_ecb; ++static EVP_CIPHER *crypto_aes_256_ecb; ++#ifndef NGTCP2_NO_CHACHA_POLY1305 ++static EVP_CIPHER *crypto_chacha20_poly1305; + static EVP_CIPHER *crypto_chacha20; ++#endif /* !defined(NGTCP2_NO_CHACHA_POLY1305) */ + static EVP_MD *crypto_sha256; + static EVP_MD *crypto_sha384; + static EVP_KDF *crypto_hkdf; + + int ngtcp2_crypto_ossl_init(void) { ++ /* We do not care whether the pre-fetch succeeds or not. If it ++ fails, it returns NULL, which is still the default value, and our ++ code should still work with it. */ + crypto_aes_128_gcm = EVP_CIPHER_fetch(NULL, "AES-128-GCM", NULL); +- if (crypto_aes_128_gcm == NULL) { +- return -1; +- } +- + crypto_aes_256_gcm = EVP_CIPHER_fetch(NULL, "AES-256-GCM", NULL); +- if (crypto_aes_256_gcm == NULL) { +- return -1; +- } +- +- crypto_chacha20_poly1305 = EVP_CIPHER_fetch(NULL, "ChaCha20-Poly1305", NULL); +- if (crypto_chacha20_poly1305 == NULL) { +- return -1; +- } +- + crypto_aes_128_ccm = EVP_CIPHER_fetch(NULL, "AES-128-CCM", NULL); +- if (crypto_aes_128_ccm == NULL) { +- return -1; +- } +- +- crypto_aes_128_ctr = EVP_CIPHER_fetch(NULL, "AES-128-CTR", NULL); +- if (crypto_aes_128_ctr == NULL) { +- return -1; +- } +- +- crypto_aes_256_ctr = EVP_CIPHER_fetch(NULL, "AES-256-CTR", NULL); +- if (crypto_aes_256_ctr == NULL) { +- return -1; +- } +- ++ crypto_aes_128_ecb = EVP_CIPHER_fetch(NULL, "AES-128-ECB", NULL); ++ crypto_aes_256_ecb = EVP_CIPHER_fetch(NULL, "AES-256-ECB", NULL); ++#ifndef NGTCP2_NO_CHACHA_POLY1305 ++ crypto_chacha20_poly1305 = EVP_CIPHER_fetch(NULL, "ChaCha20-Poly1305", NULL); + crypto_chacha20 = EVP_CIPHER_fetch(NULL, "ChaCha20", NULL); +- if (crypto_chacha20 == NULL) { +- return -1; +- } +- ++#endif /* !defined(NGTCP2_NO_CHACHA_POLY1305) */ + crypto_sha256 = EVP_MD_fetch(NULL, "sha256", NULL); +- if (crypto_sha256 == NULL) { +- return -1; +- } +- + crypto_sha384 = EVP_MD_fetch(NULL, "sha384", NULL); +- if (crypto_sha384 == NULL) { +- return -1; +- } +- + crypto_hkdf = EVP_KDF_fetch(NULL, "hkdf", NULL); +- if (crypto_hkdf == NULL) { +- return -1; +- } +- +- crypto_initialized = 1; + + return 0; + } +@@ -125,6 +95,7 @@ static const EVP_CIPHER *crypto_aead_aes_256_gcm(void) { + return EVP_aes_256_gcm(); + } + ++#ifndef NGTCP2_NO_CHACHA_POLY1305 + static const EVP_CIPHER *crypto_aead_chacha20_poly1305(void) { + if (crypto_chacha20_poly1305) { + return crypto_chacha20_poly1305; +@@ -132,6 +103,7 @@ static const EVP_CIPHER *crypto_aead_chacha20_poly1305(void) { + + return EVP_chacha20_poly1305(); + } ++#endif /* !defined(NGTCP2_NO_CHACHA_POLY1305) */ + + static const EVP_CIPHER *crypto_aead_aes_128_ccm(void) { + if (crypto_aes_128_ccm) { +@@ -141,22 +113,23 @@ static const EVP_CIPHER *crypto_aead_aes_128_ccm(void) { + return EVP_aes_128_ccm(); + } + +-static const EVP_CIPHER *crypto_cipher_aes_128_ctr(void) { +- if (crypto_aes_128_ctr) { +- return crypto_aes_128_ctr; ++static const EVP_CIPHER *crypto_cipher_aes_128_ecb(void) { ++ if (crypto_aes_128_ecb) { ++ return crypto_aes_128_ecb; + } + +- return EVP_aes_128_ctr(); ++ return EVP_aes_128_ecb(); + } + +-static const EVP_CIPHER *crypto_cipher_aes_256_ctr(void) { +- if (crypto_aes_256_ctr) { +- return crypto_aes_256_ctr; ++static const EVP_CIPHER *crypto_cipher_aes_256_ecb(void) { ++ if (crypto_aes_256_ecb) { ++ return crypto_aes_256_ecb; + } + +- return EVP_aes_256_ctr(); ++ return EVP_aes_256_ecb(); + } + ++#ifndef NGTCP2_NO_CHACHA_POLY1305 + static const EVP_CIPHER *crypto_cipher_chacha20(void) { + if (crypto_chacha20) { + return crypto_chacha20; +@@ -164,6 +137,7 @@ static const EVP_CIPHER *crypto_cipher_chacha20(void) { + + return EVP_chacha20(); + } ++#endif /* !defined(NGTCP2_NO_CHACHA_POLY1305) */ + + static const EVP_MD *crypto_md_sha256(void) { + if (crypto_sha256) { +@@ -189,13 +163,21 @@ static EVP_KDF *crypto_kdf_hkdf(void) { + return EVP_KDF_fetch(NULL, "hkdf", NULL); + } + ++static void crypto_kdf_hkdf_free(EVP_KDF *kdf) { ++ if (kdf && crypto_hkdf != kdf) { ++ EVP_KDF_free(kdf); ++ } ++} ++ + static size_t crypto_aead_max_overhead(const EVP_CIPHER *aead) { + switch (EVP_CIPHER_nid(aead)) { + case NID_aes_128_gcm: + case NID_aes_256_gcm: + return EVP_GCM_TLS_TAG_LEN; ++#ifndef NGTCP2_NO_CHACHA_POLY1305 + case NID_chacha20_poly1305: + return EVP_CHACHAPOLY_TLS_TAG_LEN; ++#endif /* !defined(NGTCP2_NO_CHACHA_POLY1305) */ + case NID_aes_128_ccm: + return EVP_CCM_TLS_TAG_LEN; + default: +@@ -216,7 +198,7 @@ ngtcp2_crypto_md *ngtcp2_crypto_md_sha256(ngtcp2_crypto_md *md) { + ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_initial(ngtcp2_crypto_ctx *ctx) { + ngtcp2_crypto_aead_init(&ctx->aead, (void *)crypto_aead_aes_128_gcm()); + ctx->md.native_handle = (void *)crypto_md_sha256(); +- ctx->hp.native_handle = (void *)crypto_cipher_aes_128_ctr(); ++ ctx->hp.native_handle = (void *)crypto_cipher_aes_128_ecb(); + ctx->max_encryption = 0; + ctx->max_decryption_failure = 0; + return ctx; +@@ -239,8 +221,10 @@ static const EVP_CIPHER *crypto_cipher_id_get_aead(uint32_t cipher_id) { + return crypto_aead_aes_128_gcm(); + case TLS1_3_CK_AES_256_GCM_SHA384: + return crypto_aead_aes_256_gcm(); ++#ifndef NGTCP2_NO_CHACHA_POLY1305 + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: + return crypto_aead_chacha20_poly1305(); ++#endif /* !defined(NGTCP2_NO_CHACHA_POLY1305) */ + case TLS1_3_CK_AES_128_CCM_SHA256: + return crypto_aead_aes_128_ccm(); + default: +@@ -253,8 +237,10 @@ static uint64_t crypto_cipher_id_get_aead_max_encryption(uint32_t cipher_id) { + case TLS1_3_CK_AES_128_GCM_SHA256: + case TLS1_3_CK_AES_256_GCM_SHA384: + return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_GCM; ++#ifndef NGTCP2_NO_CHACHA_POLY1305 + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: + return NGTCP2_CRYPTO_MAX_ENCRYPTION_CHACHA20_POLY1305; ++#endif /* !defined(NGTCP2_NO_CHACHA_POLY1305) */ + case TLS1_3_CK_AES_128_CCM_SHA256: + return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_CCM; + default: +@@ -268,8 +254,10 @@ crypto_cipher_id_get_aead_max_decryption_failure(uint32_t cipher_id) { + case TLS1_3_CK_AES_128_GCM_SHA256: + case TLS1_3_CK_AES_256_GCM_SHA384: + return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_GCM; ++#ifndef NGTCP2_NO_CHACHA_POLY1305 + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: + return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_CHACHA20_POLY1305; ++#endif /* !defined(NGTCP2_NO_CHACHA_POLY1305) */ + case TLS1_3_CK_AES_128_CCM_SHA256: + return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_CCM; + default: +@@ -281,11 +269,13 @@ static const EVP_CIPHER *crypto_cipher_id_get_hp(uint32_t cipher_id) { + switch (cipher_id) { + case TLS1_3_CK_AES_128_GCM_SHA256: + case TLS1_3_CK_AES_128_CCM_SHA256: +- return crypto_cipher_aes_128_ctr(); ++ return crypto_cipher_aes_128_ecb(); + case TLS1_3_CK_AES_256_GCM_SHA384: +- return crypto_cipher_aes_256_ctr(); ++ return crypto_cipher_aes_256_ecb(); ++#ifndef NGTCP2_NO_CHACHA_POLY1305 + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: + return crypto_cipher_chacha20(); ++#endif /* !defined(NGTCP2_NO_CHACHA_POLY1305) */ + default: + return NULL; + } +@@ -294,7 +284,9 @@ static const EVP_CIPHER *crypto_cipher_id_get_hp(uint32_t cipher_id) { + static const EVP_MD *crypto_cipher_id_get_md(uint32_t cipher_id) { + switch (cipher_id) { + case TLS1_3_CK_AES_128_GCM_SHA256: ++#ifndef NGTCP2_NO_CHACHA_POLY1305 + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: ++#endif /* !defined(NGTCP2_NO_CHACHA_POLY1305) */ + case TLS1_3_CK_AES_128_CCM_SHA256: + return crypto_md_sha256(); + case TLS1_3_CK_AES_256_GCM_SHA384: +@@ -308,7 +300,9 @@ static int supported_cipher_id(uint32_t cipher_id) { + switch (cipher_id) { + case TLS1_3_CK_AES_128_GCM_SHA256: + case TLS1_3_CK_AES_256_GCM_SHA384: ++#ifndef NGTCP2_NO_CHACHA_POLY1305 + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: ++#endif /* !defined(NGTCP2_NO_CHACHA_POLY1305) */ + case TLS1_3_CK_AES_128_CCM_SHA256: + return 1; + default: +@@ -697,9 +691,7 @@ int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md, + }; + int rv = 0; + +- if (!crypto_initialized) { +- EVP_KDF_free(kdf); +- } ++ crypto_kdf_hkdf_free(kdf); + + if (EVP_KDF_derive(kctx, dest, (size_t)EVP_MD_size(prf), params) <= 0) { + rv = -1; +@@ -730,9 +722,7 @@ int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen, + }; + int rv = 0; + +- if (!crypto_initialized) { +- EVP_KDF_free(kdf); +- } ++ crypto_kdf_hkdf_free(kdf); + + if (EVP_KDF_derive(kctx, dest, destlen, params) <= 0) { + rv = -1; +@@ -763,9 +753,7 @@ int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen, + }; + int rv = 0; + +- if (!crypto_initialized) { +- EVP_KDF_free(kdf); +- } ++ crypto_kdf_hkdf_free(kdf); + + if (EVP_KDF_derive(kctx, dest, destlen, params) <= 0) { + rv = -1; +@@ -850,16 +838,31 @@ int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample) { +- static const uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00"; ++ static const uint8_t PLAINTEXT[16] = {0}; + EVP_CIPHER_CTX *actx = hp_ctx->native_handle; + int len; + + (void)hp; + +- if (!EVP_EncryptInit_ex(actx, NULL, NULL, NULL, sample) || +- !EVP_EncryptUpdate(actx, dest, &len, PLAINTEXT, sizeof(PLAINTEXT) - 1) || +- !EVP_EncryptFinal_ex(actx, dest + sizeof(PLAINTEXT) - 1, &len)) { +- return -1; ++ switch (EVP_CIPHER_CTX_nid(actx)) { ++ case NID_aes_128_ecb: ++ case NID_aes_256_ecb: ++ if (!EVP_EncryptUpdate(actx, dest, &len, sample, NGTCP2_HP_SAMPLELEN)) { ++ return -1; ++ } ++ ++ break; ++ case NID_chacha20: ++ if (!EVP_EncryptInit_ex(actx, NULL, NULL, NULL, sample) || ++ !EVP_EncryptUpdate(actx, dest, &len, PLAINTEXT, sizeof(PLAINTEXT)) || ++ !EVP_EncryptFinal_ex(actx, dest + sizeof(PLAINTEXT), &len)) { ++ return -1; ++ } ++ ++ break; ++ default: ++ assert(0); ++ abort(); + } + + return 0; +@@ -994,6 +997,19 @@ int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, uint8_t *data, + return 0; + } + ++int ngtcp2_crypto_get_path_challenge_data2_cb(ngtcp2_conn *conn, ++ ngtcp2_path_challenge_data *data, ++ void *user_data) { ++ (void)conn; ++ (void)user_data; ++ ++ if (RAND_bytes(data->data, NGTCP2_PATH_CHALLENGE_DATALEN) != 1) { ++ return NGTCP2_ERR_CALLBACK_FAILURE; ++ } ++ ++ return 0; ++} ++ + int ngtcp2_crypto_random(uint8_t *data, size_t datalen) { + if (RAND_bytes(data, (int)datalen) != 1) { + return -1; +diff --git a/third_party/ngtcp2/crypto/picotls/libngtcp2_crypto_picotls.pc.in b/third_party/ngtcp2/crypto/picotls/libngtcp2_crypto_picotls.pc.in +index 1cb24f19388..3cef316f48a 100644 +--- a/third_party/ngtcp2/crypto/picotls/libngtcp2_crypto_picotls.pc.in ++++ b/third_party/ngtcp2/crypto/picotls/libngtcp2_crypto_picotls.pc.in +@@ -31,3 +31,4 @@ URL: https://github.com/ngtcp2/ngtcp2 + Version: @VERSION@ + Libs: -L${libdir} -lngtcp2_crypto_picotls + Cflags: -I${includedir} ++Requires.private: libngtcp2, openssl +diff --git a/third_party/ngtcp2/crypto/picotls/picotls.c b/third_party/ngtcp2/crypto/picotls/picotls.c +index 98ebf9e876c..bd29a541277 100644 +--- a/third_party/ngtcp2/crypto/picotls/picotls.c ++++ b/third_party/ngtcp2/crypto/picotls/picotls.c +@@ -35,6 +35,7 @@ + #include + #include + ++#include "ngtcp2_macro.h" + #include "shared.h" + + ngtcp2_crypto_aead *ngtcp2_crypto_aead_aes_128_gcm(ngtcp2_crypto_aead *aead) { +@@ -49,7 +50,7 @@ ngtcp2_crypto_md *ngtcp2_crypto_md_sha256(ngtcp2_crypto_md *md) { + ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_initial(ngtcp2_crypto_ctx *ctx) { + ngtcp2_crypto_aead_init(&ctx->aead, (void *)&ptls_openssl_aes128gcm); + ctx->md.native_handle = (void *)&ptls_openssl_sha256; +- ctx->hp.native_handle = (void *)&ptls_openssl_aes128ctr; ++ ctx->hp.native_handle = (void *)&ptls_openssl_aes128ecb; + ctx->max_encryption = 0; + ctx->max_decryption_failure = 0; + return ctx; +@@ -103,11 +104,11 @@ crypto_cipher_suite_get_aead_max_decryption_failure(ptls_cipher_suite_t *cs) { + static const ptls_cipher_algorithm_t * + crypto_cipher_suite_get_hp(ptls_cipher_suite_t *cs) { + if (cs->aead == &ptls_openssl_aes128gcm) { +- return &ptls_openssl_aes128ctr; ++ return &ptls_openssl_aes128ecb; + } + + if (cs->aead == &ptls_openssl_aes256gcm) { +- return &ptls_openssl_aes256ctr; ++ return &ptls_openssl_aes256ecb; + } + + #ifdef PTLS_OPENSSL_HAVE_CHACHA20_POLY1305 +@@ -237,6 +238,11 @@ int ngtcp2_crypto_cipher_ctx_encrypt_init(ngtcp2_crypto_cipher_ctx *cipher_ctx, + return -1; + } + ++ if (cipher->native_handle == &ptls_openssl_aes128ecb || ++ cipher->native_handle == &ptls_openssl_aes256ecb) { ++ ptls_cipher_init(actx, NULL); ++ } ++ + cipher_ctx->native_handle = actx; + + return 0; +@@ -351,13 +357,20 @@ int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample) { ++ static const uint8_t PLAINTEXT[16] = {0}; + ptls_cipher_context_t *actx = hp_ctx->native_handle; +- static const uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00"; + + (void)hp; + ++ if (hp->native_handle == &ptls_openssl_aes128ecb || ++ hp->native_handle == &ptls_openssl_aes256ecb) { ++ ptls_cipher_encrypt(actx, dest, sample, NGTCP2_HP_SAMPLELEN); ++ ++ return 0; ++ } ++ + ptls_cipher_init(actx, sample); +- ptls_cipher_encrypt(actx, dest, PLAINTEXT, sizeof(PLAINTEXT) - 1); ++ ptls_cipher_encrypt(actx, dest, PLAINTEXT, sizeof(PLAINTEXT)); + + return 0; + } +@@ -376,7 +389,7 @@ int ngtcp2_crypto_read_write_crypto_data( + + ptls_buffer_init(&sendbuf, (void *)"", 0); + +- assert(epoch == ptls_get_read_epoch(cptls->ptls)); ++ assert(datalen == 0 || epoch == ptls_get_read_epoch(cptls->ptls)); + + rv = ptls_handle_message(cptls->ptls, &sendbuf, epoch_offsets, epoch, data, + datalen, &cptls->handshake_properties); +@@ -492,6 +505,17 @@ int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, uint8_t *data, + return 0; + } + ++int ngtcp2_crypto_get_path_challenge_data2_cb(ngtcp2_conn *conn, ++ ngtcp2_path_challenge_data *data, ++ void *user_data) { ++ (void)conn; ++ (void)user_data; ++ ++ ptls_openssl_random_bytes(data->data, NGTCP2_PATH_CHALLENGE_DATALEN); ++ ++ return 0; ++} ++ + int ngtcp2_crypto_random(uint8_t *data, size_t datalen) { + ptls_openssl_random_bytes(data, datalen); + +@@ -499,8 +523,7 @@ int ngtcp2_crypto_random(uint8_t *data, size_t datalen) { + } + + void ngtcp2_crypto_picotls_ctx_init(ngtcp2_crypto_picotls_ctx *cptls) { +- cptls->ptls = NULL; +- memset(&cptls->handshake_properties, 0, sizeof(cptls->handshake_properties)); ++ *cptls = (ngtcp2_crypto_picotls_ctx){0}; + } + + static int set_additional_extensions(ptls_handshake_properties_t *hsprops, +diff --git a/third_party/ngtcp2/crypto/quictls/.gitignore b/third_party/ngtcp2/crypto/quictls/.gitignore +index 6f4bcf87cbd..e6f94f30474 100644 +--- a/third_party/ngtcp2/crypto/quictls/.gitignore ++++ b/third_party/ngtcp2/crypto/quictls/.gitignore +@@ -1 +1,2 @@ ++/libngtcp2_crypto_libressl.pc + /libngtcp2_crypto_quictls.pc +diff --git a/third_party/ngtcp2/crypto/quictls/CMakeLists.txt b/third_party/ngtcp2/crypto/quictls/CMakeLists.txt +index c1296b2cad1..b94e1f0c647 100644 +--- a/third_party/ngtcp2/crypto/quictls/CMakeLists.txt ++++ b/third_party/ngtcp2/crypto/quictls/CMakeLists.txt +@@ -39,25 +39,31 @@ set(ngtcp2_crypto_quictls_INCLUDE_DIRS + "${OPENSSL_INCLUDE_DIRS}" + ) + +-foreach(name libngtcp2_crypto_quictls.pc) ++if(HAVE_LIBRESSL) ++ set(ngtcp2_crypto_lib "ngtcp2_crypto_libressl") ++else() ++ set(ngtcp2_crypto_lib "ngtcp2_crypto_quictls") ++endif() ++ ++foreach(name lib${ngtcp2_crypto_lib}.pc) + configure_file("${name}.in" "${name}" @ONLY) + endforeach() + + # Public shared library + if(ENABLE_SHARED_LIB) +- add_library(ngtcp2_crypto_quictls SHARED ${ngtcp2_crypto_quictls_SOURCES}) +- set_target_properties(ngtcp2_crypto_quictls PROPERTIES ++ add_library(${ngtcp2_crypto_lib} SHARED ${ngtcp2_crypto_quictls_SOURCES}) ++ set_target_properties(${ngtcp2_crypto_lib} PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}" + VERSION ${CRYPTO_QUICTLS_LT_VERSION} + SOVERSION ${CRYPTO_QUICTLS_LT_SOVERSION} + C_VISIBILITY_PRESET hidden + POSITION_INDEPENDENT_CODE ON + ) +- target_include_directories(ngtcp2_crypto_quictls PUBLIC ++ target_include_directories(${ngtcp2_crypto_lib} PUBLIC + ${ngtcp2_crypto_quictls_INCLUDE_DIRS}) +- target_link_libraries(ngtcp2_crypto_quictls ngtcp2 ${OPENSSL_LIBRARIES}) ++ target_link_libraries(${ngtcp2_crypto_lib} ngtcp2 ${OPENSSL_LIBRARIES}) + +- install(TARGETS ngtcp2_crypto_quictls ++ install(TARGETS ${ngtcp2_crypto_lib} + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") +@@ -65,22 +71,22 @@ endif() + + if(ENABLE_STATIC_LIB) + # Public static library +- add_library(ngtcp2_crypto_quictls_static STATIC ${ngtcp2_crypto_quictls_SOURCES}) +- set_target_properties(ngtcp2_crypto_quictls_static PROPERTIES ++ add_library(${ngtcp2_crypto_lib}_static STATIC ${ngtcp2_crypto_quictls_SOURCES}) ++ set_target_properties(${ngtcp2_crypto_lib}_static PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}" + VERSION ${CRYPTO_QUICTLS_LT_VERSION} + SOVERSION ${CRYPTO_QUICTLS_LT_SOVERSION} +- ARCHIVE_OUTPUT_NAME ngtcp2_crypto_quictls${STATIC_LIB_SUFFIX} ++ ARCHIVE_OUTPUT_NAME ${ngtcp2_crypto_lib}${STATIC_LIB_SUFFIX} + C_VISIBILITY_PRESET hidden + ) +- target_compile_definitions(ngtcp2_crypto_quictls_static PUBLIC ++ target_compile_definitions(${ngtcp2_crypto_lib}_static PUBLIC + "-DNGTCP2_STATICLIB") +- target_include_directories(ngtcp2_crypto_quictls_static PUBLIC ++ target_include_directories(${ngtcp2_crypto_lib}_static PUBLIC + ${ngtcp2_crypto_quictls_INCLUDE_DIRS}) + +- install(TARGETS ngtcp2_crypto_quictls_static ++ install(TARGETS ${ngtcp2_crypto_lib}_static + DESTINATION "${CMAKE_INSTALL_LIBDIR}") + endif() + +-install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libngtcp2_crypto_quictls.pc" ++install(FILES "${CMAKE_CURRENT_BINARY_DIR}/lib${ngtcp2_crypto_lib}.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") +diff --git a/third_party/ngtcp2/crypto/quictls/Makefile.am b/third_party/ngtcp2/crypto/quictls/Makefile.am +index d15dcbb8796..93670f8b828 100644 +--- a/third_party/ngtcp2/crypto/quictls/Makefile.am ++++ b/third_party/ngtcp2/crypto/quictls/Makefile.am +@@ -31,9 +31,18 @@ AM_CPPFLAGS = -I$(top_srcdir)/lib/includes -I$(top_builddir)/lib/includes \ + AM_LDFLAGS = ${LIBTOOL_LDFLAGS} + + pkgconfigdir = $(libdir)/pkgconfig +-pkgconfig_DATA = libngtcp2_crypto_quictls.pc +-DISTCLEANFILES = $(pkgconfig_DATA) + ++if HAVE_LIBRESSL ++pkgconfig_DATA = libngtcp2_crypto_libressl.pc ++lib_LTLIBRARIES = libngtcp2_crypto_libressl.la ++ ++libngtcp2_crypto_libressl_la_SOURCES = quictls.c ../shared.c ../shared.h ++libngtcp2_crypto_libressl_la_LDFLAGS = -no-undefined \ ++ -version-info $(CRYPTO_LIBRESSL_LT_CURRENT):$(CRYPTO_LIBRESSL_LT_REVISION):$(CRYPTO_LIBRESSL_LT_AGE) ++libngtcp2_crypto_libressl_la_LIBADD = $(top_builddir)/lib/libngtcp2.la \ ++ @OPENSSL_LIBS@ ++else ++pkgconfig_DATA = libngtcp2_crypto_quictls.pc + lib_LTLIBRARIES = libngtcp2_crypto_quictls.la + + libngtcp2_crypto_quictls_la_SOURCES = quictls.c ../shared.c ../shared.h +@@ -41,3 +50,6 @@ libngtcp2_crypto_quictls_la_LDFLAGS = -no-undefined \ + -version-info $(CRYPTO_QUICTLS_LT_CURRENT):$(CRYPTO_QUICTLS_LT_REVISION):$(CRYPTO_QUICTLS_LT_AGE) + libngtcp2_crypto_quictls_la_LIBADD = $(top_builddir)/lib/libngtcp2.la \ + @OPENSSL_LIBS@ ++endif ++ ++DISTCLEANFILES = $(pkgconfig_DATA) +diff --git a/third_party/ngtcp2/crypto/quictls/libngtcp2_crypto_libressl.pc.in b/third_party/ngtcp2/crypto/quictls/libngtcp2_crypto_libressl.pc.in +new file mode 100644 +index 00000000000..903287916ce +--- /dev/null ++++ b/third_party/ngtcp2/crypto/quictls/libngtcp2_crypto_libressl.pc.in +@@ -0,0 +1,34 @@ ++# ngtcp2 ++ ++# Copyright (c) 2019 ngtcp2 contributors ++ ++# Permission is hereby granted, free of charge, to any person obtaining ++# a copy of this software and associated documentation files (the ++# "Software"), to deal in the Software without restriction, including ++# without limitation the rights to use, copy, modify, merge, publish, ++# distribute, sublicense, and/or sell copies of the Software, and to ++# permit persons to whom the Software is furnished to do so, subject to ++# the following conditions: ++ ++# The above copyright notice and this permission notice shall be ++# included in all copies or substantial portions of the Software. ++ ++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++prefix=@prefix@ ++exec_prefix=@exec_prefix@ ++libdir=@libdir@ ++includedir=@includedir@ ++ ++Name: libngtcp2_crypto_libressl ++Description: ngtcp2 LibreSSL crypto library ++URL: https://github.com/ngtcp2/ngtcp2 ++Version: @VERSION@ ++Libs: -L${libdir} -lngtcp2_crypto_libressl ++Cflags: -I${includedir} ++Requires.private: libngtcp2, openssl +diff --git a/third_party/ngtcp2/crypto/quictls/libngtcp2_crypto_quictls.pc.in b/third_party/ngtcp2/crypto/quictls/libngtcp2_crypto_quictls.pc.in +index 991d2a7e79b..6887c1743f2 100644 +--- a/third_party/ngtcp2/crypto/quictls/libngtcp2_crypto_quictls.pc.in ++++ b/third_party/ngtcp2/crypto/quictls/libngtcp2_crypto_quictls.pc.in +@@ -31,3 +31,4 @@ URL: https://github.com/ngtcp2/ngtcp2 + Version: @VERSION@ + Libs: -L${libdir} -lngtcp2_crypto_quictls + Cflags: -I${includedir} ++Requires.private: libngtcp2, openssl +diff --git a/third_party/ngtcp2/crypto/quictls/quictls.c b/third_party/ngtcp2/crypto/quictls/quictls.c +index 592e5a86535..1076d97bfc0 100644 +--- a/third_party/ngtcp2/crypto/quictls/quictls.c ++++ b/third_party/ngtcp2/crypto/quictls/quictls.c +@@ -40,6 +40,7 @@ + # include + #endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */ + ++#include "ngtcp2_macro.h" + #include "shared.h" + + #if OPENSSL_VERSION_NUMBER >= 0x30000000L +@@ -48,8 +49,8 @@ static EVP_CIPHER *crypto_aes_128_gcm; + static EVP_CIPHER *crypto_aes_256_gcm; + static EVP_CIPHER *crypto_chacha20_poly1305; + static EVP_CIPHER *crypto_aes_128_ccm; +-static EVP_CIPHER *crypto_aes_128_ctr; +-static EVP_CIPHER *crypto_aes_256_ctr; ++static EVP_CIPHER *crypto_aes_128_ecb; ++static EVP_CIPHER *crypto_aes_256_ecb; + static EVP_CIPHER *crypto_chacha20; + static EVP_MD *crypto_sha256; + static EVP_MD *crypto_sha384; +@@ -76,13 +77,13 @@ int ngtcp2_crypto_quictls_init(void) { + return -1; + } + +- crypto_aes_128_ctr = EVP_CIPHER_fetch(NULL, "AES-128-CTR", NULL); +- if (crypto_aes_128_ctr == NULL) { ++ crypto_aes_128_ecb = EVP_CIPHER_fetch(NULL, "AES-128-ECB", NULL); ++ if (crypto_aes_128_ecb == NULL) { + return -1; + } + +- crypto_aes_256_ctr = EVP_CIPHER_fetch(NULL, "AES-256-CTR", NULL); +- if (crypto_aes_256_ctr == NULL) { ++ crypto_aes_256_ecb = EVP_CIPHER_fetch(NULL, "AES-256-ECB", NULL); ++ if (crypto_aes_256_ecb == NULL) { + return -1; + } + +@@ -143,20 +144,20 @@ static const EVP_CIPHER *crypto_aead_aes_128_ccm(void) { + return EVP_aes_128_ccm(); + } + +-static const EVP_CIPHER *crypto_cipher_aes_128_ctr(void) { +- if (crypto_aes_128_ctr) { +- return crypto_aes_128_ctr; ++static const EVP_CIPHER *crypto_cipher_aes_128_ecb(void) { ++ if (crypto_aes_128_ecb) { ++ return crypto_aes_128_ecb; + } + +- return EVP_aes_128_ctr(); ++ return EVP_aes_128_ecb(); + } + +-static const EVP_CIPHER *crypto_cipher_aes_256_ctr(void) { +- if (crypto_aes_256_ctr) { +- return crypto_aes_256_ctr; ++static const EVP_CIPHER *crypto_cipher_aes_256_ecb(void) { ++ if (crypto_aes_256_ecb) { ++ return crypto_aes_256_ecb; + } + +- return EVP_aes_256_ctr(); ++ return EVP_aes_256_ecb(); + } + + static const EVP_CIPHER *crypto_cipher_chacha20(void) { +@@ -195,8 +196,8 @@ static EVP_KDF *crypto_kdf_hkdf(void) { + # define crypto_aead_aes_256_gcm EVP_aes_256_gcm + # define crypto_aead_chacha20_poly1305 EVP_chacha20_poly1305 + # define crypto_aead_aes_128_ccm EVP_aes_128_ccm +-# define crypto_cipher_aes_128_ctr EVP_aes_128_ctr +-# define crypto_cipher_aes_256_ctr EVP_aes_256_ctr ++# define crypto_cipher_aes_128_ecb EVP_aes_128_ecb ++# define crypto_cipher_aes_256_ecb EVP_aes_256_ecb + # define crypto_cipher_chacha20 EVP_chacha20 + # define crypto_md_sha256 EVP_sha256 + # define crypto_md_sha384 EVP_sha384 +@@ -231,7 +232,7 @@ ngtcp2_crypto_md *ngtcp2_crypto_md_sha256(ngtcp2_crypto_md *md) { + ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_initial(ngtcp2_crypto_ctx *ctx) { + ngtcp2_crypto_aead_init(&ctx->aead, (void *)crypto_aead_aes_128_gcm()); + ctx->md.native_handle = (void *)crypto_md_sha256(); +- ctx->hp.native_handle = (void *)crypto_cipher_aes_128_ctr(); ++ ctx->hp.native_handle = (void *)crypto_cipher_aes_128_ecb(); + ctx->max_encryption = 0; + ctx->max_decryption_failure = 0; + return ctx; +@@ -296,9 +297,9 @@ static const EVP_CIPHER *crypto_cipher_id_get_hp(uint32_t cipher_id) { + switch (cipher_id) { + case TLS1_3_CK_AES_128_GCM_SHA256: + case TLS1_3_CK_AES_128_CCM_SHA256: +- return crypto_cipher_aes_128_ctr(); ++ return crypto_cipher_aes_128_ecb(); + case TLS1_3_CK_AES_256_GCM_SHA384: +- return crypto_cipher_aes_256_ctr(); ++ return crypto_cipher_aes_256_ecb(); + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: + return crypto_cipher_chacha20(); + default: +@@ -778,16 +779,31 @@ int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample) { +- static const uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00"; ++ static const uint8_t PLAINTEXT[16] = {0}; + EVP_CIPHER_CTX *actx = hp_ctx->native_handle; + int len; + + (void)hp; + +- if (!EVP_EncryptInit_ex(actx, NULL, NULL, NULL, sample) || +- !EVP_EncryptUpdate(actx, dest, &len, PLAINTEXT, sizeof(PLAINTEXT) - 1) || +- !EVP_EncryptFinal_ex(actx, dest + sizeof(PLAINTEXT) - 1, &len)) { +- return -1; ++ switch (EVP_CIPHER_CTX_nid(actx)) { ++ case NID_aes_128_ecb: ++ case NID_aes_256_ecb: ++ if (!EVP_EncryptUpdate(actx, dest, &len, sample, NGTCP2_HP_SAMPLELEN)) { ++ return -1; ++ } ++ ++ break; ++ case NID_chacha20: ++ if (!EVP_EncryptInit_ex(actx, NULL, NULL, NULL, sample) || ++ !EVP_EncryptUpdate(actx, dest, &len, PLAINTEXT, sizeof(PLAINTEXT)) || ++ !EVP_EncryptFinal_ex(actx, dest + sizeof(PLAINTEXT), &len)) { ++ return -1; ++ } ++ ++ break; ++ default: ++ assert(0); ++ abort(); + } + + return 0; +@@ -800,7 +816,8 @@ int ngtcp2_crypto_read_write_crypto_data( + int rv; + int err; + +- if (SSL_provide_quic_data( ++ if (datalen && ++ SSL_provide_quic_data( + ssl, + ngtcp2_crypto_quictls_from_ngtcp2_encryption_level(encryption_level), + data, datalen) != 1) { +@@ -920,6 +937,19 @@ int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, uint8_t *data, + return 0; + } + ++int ngtcp2_crypto_get_path_challenge_data2_cb(ngtcp2_conn *conn, ++ ngtcp2_path_challenge_data *data, ++ void *user_data) { ++ (void)conn; ++ (void)user_data; ++ ++ if (RAND_bytes(data->data, NGTCP2_PATH_CHALLENGE_DATALEN) != 1) { ++ return NGTCP2_ERR_CALLBACK_FAILURE; ++ } ++ ++ return 0; ++} ++ + int ngtcp2_crypto_random(uint8_t *data, size_t datalen) { + if (RAND_bytes(data, (int)datalen) != 1) { + return -1; +diff --git a/third_party/ngtcp2/crypto/shared.c b/third_party/ngtcp2/crypto/shared.c +index 98cd4de7e80..f1bb9026721 100644 +--- a/third_party/ngtcp2/crypto/shared.c ++++ b/third_party/ngtcp2/crypto/shared.c +@@ -37,6 +37,22 @@ + #include "ngtcp2_macro.h" + #include "ngtcp2_net.h" + ++/* ++ * NGTCP2_INITIAL_SALT_V1 is a salt value which is used to derive ++ * initial secret. It is used for QUIC v1. ++ */ ++static const uint8_t NGTCP2_INITIAL_SALT_V1[] = { ++ 0x38, 0x76, 0x2C, 0xF7, 0xF5, 0x59, 0x34, 0xB3, 0x4D, 0x17, ++ 0x9A, 0xE6, 0xA4, 0xC8, 0x0C, 0xAD, 0xCC, 0xBB, 0x7F, 0x0A}; ++ ++/* ++ * NGTCP2_INITIAL_SALT_V2 is a salt value which is used to derive ++ * initial secret. It is used for QUIC v2. ++ */ ++static const uint8_t NGTCP2_INITIAL_SALT_V2[] = { ++ 0x0D, 0xED, 0xE3, 0xDE, 0xF7, 0x00, 0xA6, 0xDB, 0x81, 0x93, ++ 0x81, 0xBE, 0x6E, 0x26, 0x9D, 0xCB, 0xF9, 0xBD, 0x2E, 0xD9}; ++ + ngtcp2_crypto_md *ngtcp2_crypto_md_init(ngtcp2_crypto_md *md, + void *md_native_handle) { + md->native_handle = md_native_handle; +@@ -53,9 +69,9 @@ int ngtcp2_crypto_hkdf_expand_label(uint8_t *dest, size_t destlen, + + *p++ = (uint8_t)(destlen / 256); + *p++ = (uint8_t)(destlen % 256); +- *p++ = (uint8_t)(sizeof(LABEL) - 1 + labellen); +- memcpy(p, LABEL, sizeof(LABEL) - 1); +- p += sizeof(LABEL) - 1; ++ *p++ = (uint8_t)(ngtcp2_strlen_lit(LABEL) + labellen); ++ memcpy(p, LABEL, ngtcp2_strlen_lit(LABEL)); ++ p += ngtcp2_strlen_lit(LABEL); + memcpy(p, label, labellen); + p += labellen; + *p++ = 0; +@@ -87,12 +103,12 @@ int ngtcp2_crypto_derive_initial_secrets(uint8_t *rx_secret, uint8_t *tx_secret, + switch (version) { + case NGTCP2_PROTO_VER_V1: + default: +- salt = (const uint8_t *)NGTCP2_INITIAL_SALT_V1; +- saltlen = sizeof(NGTCP2_INITIAL_SALT_V1) - 1; ++ salt = NGTCP2_INITIAL_SALT_V1; ++ saltlen = sizeof(NGTCP2_INITIAL_SALT_V1); + break; + case NGTCP2_PROTO_VER_V2: +- salt = (const uint8_t *)NGTCP2_INITIAL_SALT_V2; +- saltlen = sizeof(NGTCP2_INITIAL_SALT_V2) - 1; ++ salt = NGTCP2_INITIAL_SALT_V2; ++ saltlen = sizeof(NGTCP2_INITIAL_SALT_V2); + break; + } + +@@ -111,10 +127,12 @@ int ngtcp2_crypto_derive_initial_secrets(uint8_t *rx_secret, uint8_t *tx_secret, + + if (ngtcp2_crypto_hkdf_expand_label( + client_secret, NGTCP2_CRYPTO_INITIAL_SECRETLEN, &ctx.md, initial_secret, +- NGTCP2_CRYPTO_INITIAL_SECRETLEN, CLABEL, sizeof(CLABEL) - 1) != 0 || ++ NGTCP2_CRYPTO_INITIAL_SECRETLEN, CLABEL, ++ ngtcp2_strlen_lit(CLABEL)) != 0 || + ngtcp2_crypto_hkdf_expand_label( + server_secret, NGTCP2_CRYPTO_INITIAL_SECRETLEN, &ctx.md, initial_secret, +- NGTCP2_CRYPTO_INITIAL_SECRETLEN, SLABEL, sizeof(SLABEL) - 1) != 0) { ++ NGTCP2_CRYPTO_INITIAL_SECRETLEN, SLABEL, ++ ngtcp2_strlen_lit(SLABEL)) != 0) { + return -1; + } + +@@ -148,19 +166,19 @@ int ngtcp2_crypto_derive_packet_protection_key( + switch (version) { + case NGTCP2_PROTO_VER_V2: + key_label = KEY_LABEL_V2; +- key_labellen = sizeof(KEY_LABEL_V2) - 1; ++ key_labellen = ngtcp2_strlen_lit(KEY_LABEL_V2); + iv_label = IV_LABEL_V2; +- iv_labellen = sizeof(IV_LABEL_V2) - 1; ++ iv_labellen = ngtcp2_strlen_lit(IV_LABEL_V2); + hp_key_label = HP_KEY_LABEL_V2; +- hp_key_labellen = sizeof(HP_KEY_LABEL_V2) - 1; ++ hp_key_labellen = ngtcp2_strlen_lit(HP_KEY_LABEL_V2); + break; + default: + key_label = KEY_LABEL_V1; +- key_labellen = sizeof(KEY_LABEL_V1) - 1; ++ key_labellen = ngtcp2_strlen_lit(KEY_LABEL_V1); + iv_label = IV_LABEL_V1; +- iv_labellen = sizeof(IV_LABEL_V1) - 1; ++ iv_labellen = ngtcp2_strlen_lit(IV_LABEL_V1); + hp_key_label = HP_KEY_LABEL_V1; +- hp_key_labellen = sizeof(HP_KEY_LABEL_V1) - 1; ++ hp_key_labellen = ngtcp2_strlen_lit(HP_KEY_LABEL_V1); + } + + if (ngtcp2_crypto_hkdf_expand_label(key, keylen, md, secret, secretlen, +@@ -194,11 +212,11 @@ int ngtcp2_crypto_update_traffic_secret(uint8_t *dest, uint32_t version, + switch (version) { + case NGTCP2_PROTO_VER_V2: + label = LABEL_V2; +- labellen = sizeof(LABEL_V2) - 1; ++ labellen = ngtcp2_strlen_lit(LABEL_V2); + break; + default: + label = LABEL; +- labellen = sizeof(LABEL) - 1; ++ labellen = ngtcp2_strlen_lit(LABEL); + } + + if (ngtcp2_crypto_hkdf_expand_label(dest, secretlen, md, secret, secretlen, +@@ -461,7 +479,9 @@ int ngtcp2_crypto_derive_and_install_tx_key(ngtcp2_conn *conn, uint8_t *key, + + if (ngtcp2_conn_is_server(conn) && + crypto_set_local_transport_params(conn, tls) != 0) { +- goto fail; ++ /* Just return -1 because aead_ctx and hp_ctx are now owned by ++ conn. */ ++ return -1; + } + + break; +@@ -590,11 +610,11 @@ int ngtcp2_crypto_derive_and_install_initial_key( + case NGTCP2_PROTO_VER_V1: + default: + retry_key = (const uint8_t *)NGTCP2_RETRY_KEY_V1; +- retry_noncelen = sizeof(NGTCP2_RETRY_NONCE_V1) - 1; ++ retry_noncelen = ngtcp2_strlen_lit(NGTCP2_RETRY_NONCE_V1); + break; + case NGTCP2_PROTO_VER_V2: + retry_key = (const uint8_t *)NGTCP2_RETRY_KEY_V2; +- retry_noncelen = sizeof(NGTCP2_RETRY_NONCE_V2) - 1; ++ retry_noncelen = ngtcp2_strlen_lit(NGTCP2_RETRY_NONCE_V2); + break; + } + +@@ -843,7 +863,7 @@ int ngtcp2_crypto_generate_stateless_reset_token(uint8_t *token, + if (ngtcp2_crypto_hkdf(token, NGTCP2_STATELESS_RESET_TOKENLEN, + ngtcp2_crypto_md_sha256(&md), secret, secretlen, + cid->data, cid->datalen, info, +- sizeof(info) - 1) != 0) { ++ ngtcp2_strlen_lit(info)) != 0) { + return -1; + } + +@@ -863,8 +883,8 @@ static int crypto_derive_token_key(uint8_t *key, size_t keylen, uint8_t *iv, + uint8_t *p; + + assert(ngtcp2_crypto_md_hashlen(md) == sizeof(intsecret)); +- assert(info_prefixlen + sizeof(key_info_suffix) - 1 <= sizeof(info)); +- assert(info_prefixlen + sizeof(iv_info_suffix) - 1 <= sizeof(info)); ++ assert(info_prefixlen + ngtcp2_strlen_lit(key_info_suffix) <= sizeof(info)); ++ assert(info_prefixlen + ngtcp2_strlen_lit(iv_info_suffix) <= sizeof(info)); + + if (ngtcp2_crypto_hkdf_extract(intsecret, md, secret, secretlen, salt, + saltlen) != 0) { +@@ -874,8 +894,8 @@ static int crypto_derive_token_key(uint8_t *key, size_t keylen, uint8_t *iv, + memcpy(info, info_prefix, info_prefixlen); + p = info + info_prefixlen; + +- memcpy(p, key_info_suffix, sizeof(key_info_suffix) - 1); +- p += sizeof(key_info_suffix) - 1; ++ memcpy(p, key_info_suffix, ngtcp2_strlen_lit(key_info_suffix)); ++ p += ngtcp2_strlen_lit(key_info_suffix); + + if (ngtcp2_crypto_hkdf_expand(key, keylen, md, intsecret, sizeof(intsecret), + info, (size_t)(p - info)) != 0) { +@@ -884,8 +904,8 @@ static int crypto_derive_token_key(uint8_t *key, size_t keylen, uint8_t *iv, + + p = info + info_prefixlen; + +- memcpy(p, iv_info_suffix, sizeof(iv_info_suffix) - 1); +- p += sizeof(iv_info_suffix) - 1; ++ memcpy(p, iv_info_suffix, ngtcp2_strlen_lit(iv_info_suffix)); ++ p += ngtcp2_strlen_lit(iv_info_suffix); + + if (ngtcp2_crypto_hkdf_expand(iv, ivlen, md, intsecret, sizeof(intsecret), + info, (size_t)(p - info)) != 0) { +@@ -918,8 +938,8 @@ ngtcp2_ssize ngtcp2_crypto_generate_retry_token( + uint8_t *token, const uint8_t *secret, size_t secretlen, uint32_t version, + const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen, + const ngtcp2_cid *retry_scid, const ngtcp2_cid *odcid, ngtcp2_tstamp ts) { +- uint8_t +- plaintext[/* cid len = */ 1 + NGTCP2_MAX_CIDLEN + sizeof(ngtcp2_tstamp)]; ++ uint8_t plaintext[/* cid len = */ 1 + NGTCP2_MAX_CIDLEN + ++ sizeof(ngtcp2_tstamp)] = {0}; + uint8_t rand_data[NGTCP2_CRYPTO_TOKEN_RAND_DATALEN]; + uint8_t key[16]; + uint8_t iv[12]; +@@ -938,8 +958,6 @@ ngtcp2_ssize ngtcp2_crypto_generate_retry_token( + + assert((size_t)remote_addrlen <= sizeof(ngtcp2_sockaddr_union)); + +- memset(plaintext, 0, sizeof(plaintext)); +- + *p++ = (uint8_t)odcid->datalen; + memcpy(p, odcid->data, odcid->datalen); + p += NGTCP2_MAX_CIDLEN; +@@ -961,10 +979,10 @@ ngtcp2_ssize ngtcp2_crypto_generate_retry_token( + assert(sizeof(key) == keylen); + assert(sizeof(iv) == ivlen); + +- if (crypto_derive_token_key(key, keylen, iv, ivlen, &md, secret, secretlen, +- rand_data, sizeof(rand_data), +- retry_token_info_prefix, +- sizeof(retry_token_info_prefix) - 1) != 0) { ++ if (crypto_derive_token_key( ++ key, keylen, iv, ivlen, &md, secret, secretlen, rand_data, ++ sizeof(rand_data), retry_token_info_prefix, ++ ngtcp2_strlen_lit(retry_token_info_prefix)) != 0) { + return -1; + } + +@@ -1038,10 +1056,10 @@ int ngtcp2_crypto_verify_retry_token( + assert(sizeof(key) == keylen); + assert(sizeof(iv) == ivlen); + +- if (crypto_derive_token_key(key, keylen, iv, ivlen, &md, secret, secretlen, +- rand_data, NGTCP2_CRYPTO_TOKEN_RAND_DATALEN, +- retry_token_info_prefix, +- sizeof(retry_token_info_prefix) - 1) != 0) { ++ if (crypto_derive_token_key( ++ key, keylen, iv, ivlen, &md, secret, secretlen, rand_data, ++ NGTCP2_CRYPTO_TOKEN_RAND_DATALEN, retry_token_info_prefix, ++ ngtcp2_strlen_lit(retry_token_info_prefix)) != 0) { + return -1; + } + +@@ -1100,7 +1118,7 @@ ngtcp2_ssize ngtcp2_crypto_generate_retry_token2( + const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen, + const ngtcp2_cid *retry_scid, const ngtcp2_cid *odcid, ngtcp2_tstamp ts) { + uint8_t plaintext[sizeof(ngtcp2_sockaddr_union) + /* cid len = */ 1 + +- NGTCP2_MAX_CIDLEN + sizeof(ngtcp2_tstamp)]; ++ NGTCP2_MAX_CIDLEN + sizeof(ngtcp2_tstamp)] = {0}; + uint8_t rand_data[NGTCP2_CRYPTO_TOKEN_RAND_DATALEN]; + uint8_t key[16]; + uint8_t iv[12]; +@@ -1117,8 +1135,6 @@ ngtcp2_ssize ngtcp2_crypto_generate_retry_token2( + + assert((size_t)remote_addrlen <= sizeof(ngtcp2_sockaddr_union)); + +- memset(plaintext, 0, sizeof(plaintext)); +- + memcpy(p, remote_addr, (size_t)remote_addrlen); + p += sizeof(ngtcp2_sockaddr_union); + *p++ = (uint8_t)odcid->datalen; +@@ -1141,10 +1157,10 @@ ngtcp2_ssize ngtcp2_crypto_generate_retry_token2( + assert(sizeof(key) == keylen); + assert(sizeof(iv) == ivlen); + +- if (crypto_derive_token_key(key, keylen, iv, ivlen, &md, secret, secretlen, +- rand_data, sizeof(rand_data), +- retry_token_info_prefix2, +- sizeof(retry_token_info_prefix2) - 1) != 0) { ++ if (crypto_derive_token_key( ++ key, keylen, iv, ivlen, &md, secret, secretlen, rand_data, ++ sizeof(rand_data), retry_token_info_prefix2, ++ ngtcp2_strlen_lit(retry_token_info_prefix2)) != 0) { + return -1; + } + +@@ -1219,10 +1235,10 @@ int ngtcp2_crypto_verify_retry_token2( + assert(sizeof(key) == keylen); + assert(sizeof(iv) == ivlen); + +- if (crypto_derive_token_key(key, keylen, iv, ivlen, &md, secret, secretlen, +- rand_data, NGTCP2_CRYPTO_TOKEN_RAND_DATALEN, +- retry_token_info_prefix2, +- sizeof(retry_token_info_prefix2) - 1) != 0) { ++ if (crypto_derive_token_key( ++ key, keylen, iv, ivlen, &md, secret, secretlen, rand_data, ++ NGTCP2_CRYPTO_TOKEN_RAND_DATALEN, retry_token_info_prefix2, ++ ngtcp2_strlen_lit(retry_token_info_prefix2)) != 0) { + return NGTCP2_CRYPTO_ERR_INTERNAL; + } + +@@ -1305,13 +1321,22 @@ static size_t crypto_generate_regular_token_aad(uint8_t *dest, + return addrlen; + } + ++/* NGTCP2_CRYPTO_MAX_REGULAR_TOKEN_DATALEN is the maximum length of ++ opaque data embedded in a regular token. */ ++#define NGTCP2_CRYPTO_MAX_REGULAR_TOKEN_DATALEN 256 ++ ++/* NGTCP2_CRYPTO_MAX_REGULAR_TOKEN_PLAINTEXTLEN is the maximum length ++ of plaintext included in a regular token. */ ++#define NGTCP2_CRYPTO_MAX_REGULAR_TOKEN_PLAINTEXTLEN \ ++ (sizeof(ngtcp2_tstamp) + NGTCP2_CRYPTO_MAX_REGULAR_TOKEN_DATALEN) ++ + static const uint8_t regular_token_info_prefix[] = "regular_token"; + +-ngtcp2_ssize ngtcp2_crypto_generate_regular_token( ++static ngtcp2_ssize crypto_generate_regular_token( + uint8_t *token, const uint8_t *secret, size_t secretlen, + const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen, +- ngtcp2_tstamp ts) { +- uint8_t plaintext[sizeof(ngtcp2_tstamp)]; ++ const void *data, size_t datalen, ngtcp2_tstamp ts) { ++ uint8_t plaintext[NGTCP2_CRYPTO_MAX_REGULAR_TOKEN_PLAINTEXTLEN]; + uint8_t rand_data[NGTCP2_CRYPTO_TOKEN_RAND_DATALEN]; + uint8_t key[16]; + uint8_t iv[12]; +@@ -1328,9 +1353,18 @@ ngtcp2_ssize ngtcp2_crypto_generate_regular_token( + int rv; + (void)remote_addrlen; + ++ if (datalen > NGTCP2_CRYPTO_MAX_REGULAR_TOKEN_DATALEN) { ++ return -1; ++ } ++ + memcpy(p, &ts_be, sizeof(ts_be)); + p += sizeof(ts_be); + ++ if (datalen) { ++ memcpy(p, data, datalen); ++ p += datalen; ++ } ++ + plaintextlen = (size_t)(p - plaintext); + + if (ngtcp2_crypto_random(rand_data, sizeof(rand_data)) != 0) { +@@ -1346,10 +1380,10 @@ ngtcp2_ssize ngtcp2_crypto_generate_regular_token( + assert(sizeof(key) == keylen); + assert(sizeof(iv) == ivlen); + +- if (crypto_derive_token_key(key, keylen, iv, ivlen, &md, secret, secretlen, +- rand_data, sizeof(rand_data), +- regular_token_info_prefix, +- sizeof(regular_token_info_prefix) - 1) != 0) { ++ if (crypto_derive_token_key( ++ key, keylen, iv, ivlen, &md, secret, secretlen, rand_data, ++ sizeof(rand_data), regular_token_info_prefix, ++ ngtcp2_strlen_lit(regular_token_info_prefix)) != 0) { + return -1; + } + +@@ -1378,13 +1412,11 @@ ngtcp2_ssize ngtcp2_crypto_generate_regular_token( + return p - token; + } + +-int ngtcp2_crypto_verify_regular_token(const uint8_t *token, size_t tokenlen, +- const uint8_t *secret, size_t secretlen, +- const ngtcp2_sockaddr *remote_addr, +- ngtcp2_socklen remote_addrlen, +- ngtcp2_duration timeout, +- ngtcp2_tstamp ts) { +- uint8_t plaintext[sizeof(ngtcp2_tstamp)]; ++static ngtcp2_ssize crypto_verify_regular_token( ++ void *data, size_t max_datalen, const uint8_t *token, size_t tokenlen, ++ const uint8_t *secret, size_t secretlen, const ngtcp2_sockaddr *remote_addr, ++ ngtcp2_socklen remote_addrlen, ngtcp2_duration timeout, ngtcp2_tstamp ts) { ++ uint8_t plaintext[NGTCP2_CRYPTO_MAX_REGULAR_TOKEN_PLAINTEXTLEN]; + uint8_t key[16]; + uint8_t iv[12]; + size_t keylen; +@@ -1397,13 +1429,14 @@ int ngtcp2_crypto_verify_regular_token(const uint8_t *token, size_t tokenlen, + const uint8_t *rand_data; + const uint8_t *ciphertext; + size_t ciphertextlen; ++ size_t datalen; + int rv; + ngtcp2_tstamp gen_ts; + (void)remote_addrlen; + +- if (tokenlen != NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN || ++ if (tokenlen < NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN || + token[0] != NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR) { +- return -1; ++ return NGTCP2_CRYPTO_ERR_UNREADABLE_TOKEN; + } + + rand_data = token + tokenlen - NGTCP2_CRYPTO_TOKEN_RAND_DATALEN; +@@ -1413,23 +1446,27 @@ int ngtcp2_crypto_verify_regular_token(const uint8_t *token, size_t tokenlen, + ngtcp2_crypto_aead_aes_128_gcm(&aead); + ngtcp2_crypto_md_sha256(&md); + ++ if (ciphertextlen > sizeof(plaintext) + aead.max_overhead) { ++ return NGTCP2_CRYPTO_ERR_UNREADABLE_TOKEN; ++ } ++ + keylen = ngtcp2_crypto_aead_keylen(&aead); + ivlen = ngtcp2_crypto_aead_noncelen(&aead); + + assert(sizeof(key) == keylen); + assert(sizeof(iv) == ivlen); + +- if (crypto_derive_token_key(key, keylen, iv, ivlen, &md, secret, secretlen, +- rand_data, NGTCP2_CRYPTO_TOKEN_RAND_DATALEN, +- regular_token_info_prefix, +- sizeof(regular_token_info_prefix) - 1) != 0) { +- return -1; ++ if (crypto_derive_token_key( ++ key, keylen, iv, ivlen, &md, secret, secretlen, rand_data, ++ NGTCP2_CRYPTO_TOKEN_RAND_DATALEN, regular_token_info_prefix, ++ ngtcp2_strlen_lit(regular_token_info_prefix)) != 0) { ++ return NGTCP2_CRYPTO_ERR_INTERNAL; + } + + aadlen = crypto_generate_regular_token_aad(aad, remote_addr); + + if (ngtcp2_crypto_aead_ctx_decrypt_init(&aead_ctx, &aead, key, ivlen) != 0) { +- return -1; ++ return NGTCP2_CRYPTO_ERR_INTERNAL; + } + + rv = ngtcp2_crypto_decrypt(plaintext, &aead, &aead_ctx, ciphertext, +@@ -1438,19 +1475,74 @@ int ngtcp2_crypto_verify_regular_token(const uint8_t *token, size_t tokenlen, + ngtcp2_crypto_aead_ctx_free(&aead_ctx); + + if (rv != 0) { +- return -1; ++ return NGTCP2_CRYPTO_ERR_UNREADABLE_TOKEN; + } + + memcpy(&gen_ts, plaintext, sizeof(gen_ts)); + + gen_ts = ngtcp2_ntohl64(gen_ts); + if (gen_ts + timeout <= ts) { ++ return NGTCP2_CRYPTO_ERR_VERIFY_TOKEN; ++ } ++ ++ if (max_datalen == 0) { ++ return 0; ++ } ++ ++ datalen = ciphertextlen - aead.max_overhead - sizeof(gen_ts); ++ if (datalen > max_datalen) { ++ return 0; ++ } ++ ++ memcpy(data, plaintext + sizeof(gen_ts), datalen); ++ ++ return (ngtcp2_ssize)datalen; ++} ++ ++ngtcp2_ssize ngtcp2_crypto_generate_regular_token( ++ uint8_t *token, const uint8_t *secret, size_t secretlen, ++ const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen, ++ ngtcp2_tstamp ts) { ++ return crypto_generate_regular_token(token, secret, secretlen, remote_addr, ++ remote_addrlen, NULL, 0, ts); ++} ++ ++int ngtcp2_crypto_verify_regular_token(const uint8_t *token, size_t tokenlen, ++ const uint8_t *secret, size_t secretlen, ++ const ngtcp2_sockaddr *remote_addr, ++ ngtcp2_socklen remote_addrlen, ++ ngtcp2_duration timeout, ++ ngtcp2_tstamp ts) { ++ ngtcp2_ssize datalen = ++ crypto_verify_regular_token(NULL, 0, token, tokenlen, secret, secretlen, ++ remote_addr, remote_addrlen, timeout, ts); ++ ++ if (datalen < 0) { + return -1; + } + ++ assert(0 == datalen); ++ + return 0; + } + ++ngtcp2_ssize ngtcp2_crypto_generate_regular_token2( ++ uint8_t *token, const uint8_t *secret, size_t secretlen, ++ const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen, ++ const void *data, size_t datalen, ngtcp2_tstamp ts) { ++ return crypto_generate_regular_token(token, secret, secretlen, remote_addr, ++ remote_addrlen, data, datalen, ts); ++} ++ ++ngtcp2_ssize ngtcp2_crypto_verify_regular_token2( ++ void *data, size_t max_datalen, const uint8_t *token, size_t tokenlen, ++ const uint8_t *secret, size_t secretlen, const ngtcp2_sockaddr *remote_addr, ++ ngtcp2_socklen remote_addrlen, ngtcp2_duration timeout, ngtcp2_tstamp ts) { ++ return crypto_verify_regular_token(data, max_datalen, token, tokenlen, secret, ++ secretlen, remote_addr, remote_addrlen, ++ timeout, ts); ++} ++ + ngtcp2_ssize ngtcp2_crypto_write_connection_close( + uint8_t *dest, size_t destlen, uint32_t version, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, uint64_t error_code, const uint8_t *reason, +@@ -1523,11 +1615,11 @@ ngtcp2_ssize ngtcp2_crypto_write_retry(uint8_t *dest, size_t destlen, + case NGTCP2_PROTO_VER_V1: + default: + key = (const uint8_t *)NGTCP2_RETRY_KEY_V1; +- noncelen = sizeof(NGTCP2_RETRY_NONCE_V1) - 1; ++ noncelen = ngtcp2_strlen_lit(NGTCP2_RETRY_NONCE_V1); + break; + case NGTCP2_PROTO_VER_V2: + key = (const uint8_t *)NGTCP2_RETRY_KEY_V2; +- noncelen = sizeof(NGTCP2_RETRY_NONCE_V2) - 1; ++ noncelen = ngtcp2_strlen_lit(NGTCP2_RETRY_NONCE_V2); + break; + } + +@@ -1637,8 +1729,21 @@ int ngtcp2_crypto_recv_crypto_data_cb(ngtcp2_conn *conn, + (void)offset; + (void)user_data; + +- if (ngtcp2_crypto_read_write_crypto_data(conn, encryption_level, data, +- datalen) != 0) { ++ rv = ++ ngtcp2_crypto_read_write_crypto_data(conn, encryption_level, data, datalen); ++ if (rv != 0) { ++ switch (rv) { ++ case /* NGTCP2_CRYPTO_QUICTLS_ERR_TLS_WANT_CLIENT_HELLO_CB */ -10001: ++ case /* NGTCP2_CRYPTO_QUICTLS_ERR_TLS_WANT_X509_LOOKUP */ -10002: ++ /* These errors are not unrecoverable error, and they just ++ indicate that handshake has been interrupted. ngtcp2 does ++ not mind whether handshake is interrupted or not. Just ++ return 0 in this case. There are OSSL version and they have ++ the same enum value, therefore we cannot enumerate them ++ here. */ ++ return 0; ++ } ++ + rv = ngtcp2_conn_get_tls_error(conn); + if (rv) { + return rv; +diff --git a/third_party/ngtcp2/crypto/shared.h b/third_party/ngtcp2/crypto/shared.h +index 34158d3d02d..c517b7f2249 100644 +--- a/third_party/ngtcp2/crypto/shared.h ++++ b/third_party/ngtcp2/crypto/shared.h +@@ -31,26 +31,6 @@ + + #include + +-/** +- * @macro +- * +- * :macro:`NGTCP2_INITIAL_SALT_V1` is a salt value which is used to +- * derive initial secret. It is used for QUIC v1. +- */ +-#define NGTCP2_INITIAL_SALT_V1 \ +- "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb" \ +- "\x7f\x0a" +- +-/** +- * @macro +- * +- * :macro:`NGTCP2_INITIAL_SALT_V2` is a salt value which is used to +- * derive initial secret. It is used for QUIC v2. +- */ +-#define NGTCP2_INITIAL_SALT_V2 \ +- "\x0d\xed\xe3\xde\xf7\x00\xa6\xdb\x81\x93\x81\xbe\x6e\x26\x9d\xcb\xf9\xbd" \ +- "\x2e\xd9" +- + /* Maximum key usage (encryption) limits */ + #define NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_GCM (1ULL << 23) + #define NGTCP2_CRYPTO_MAX_ENCRYPTION_CHACHA20_POLY1305 (1ULL << 62) +diff --git a/third_party/ngtcp2/crypto/wolfssl/libngtcp2_crypto_wolfssl.pc.in b/third_party/ngtcp2/crypto/wolfssl/libngtcp2_crypto_wolfssl.pc.in +index 720c7849c0f..e9e670c44f9 100644 +--- a/third_party/ngtcp2/crypto/wolfssl/libngtcp2_crypto_wolfssl.pc.in ++++ b/third_party/ngtcp2/crypto/wolfssl/libngtcp2_crypto_wolfssl.pc.in +@@ -31,3 +31,4 @@ URL: https://github.com/ngtcp2/ngtcp2 + Version: @VERSION@ + Libs: -L${libdir} -lngtcp2_crypto_wolfssl + Cflags: -I${includedir} ++Requires.private: libngtcp2, wolfssl +diff --git a/third_party/ngtcp2/crypto/wolfssl/wolfssl.c b/third_party/ngtcp2/crypto/wolfssl/wolfssl.c +index bc9d9d84a86..fa2b147b6ba 100644 +--- a/third_party/ngtcp2/crypto/wolfssl/wolfssl.c ++++ b/third_party/ngtcp2/crypto/wolfssl/wolfssl.c +@@ -34,6 +34,7 @@ + #include + #include + ++#include "ngtcp2_macro.h" + #include "shared.h" + + #define PRINTF_DEBUG 0 +@@ -55,7 +56,7 @@ ngtcp2_crypto_md *ngtcp2_crypto_md_sha256(ngtcp2_crypto_md *md) { + ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_initial(ngtcp2_crypto_ctx *ctx) { + ngtcp2_crypto_aead_init(&ctx->aead, (void *)wolfSSL_EVP_aes_128_gcm()); + ctx->md.native_handle = (void *)wolfSSL_EVP_sha256(); +- ctx->hp.native_handle = (void *)wolfSSL_EVP_aes_128_ctr(); ++ ctx->hp.native_handle = (void *)wolfSSL_EVP_aes_128_ecb(); + ctx->max_encryption = 0; + ctx->max_decryption_failure = 0; + return ctx; +@@ -106,6 +107,21 @@ static int supported_aead(const WOLFSSL_EVP_CIPHER *aead) { + wolfSSL_quic_aead_is_chacha20(aead) || wolfSSL_quic_aead_is_ccm(aead); + } + ++static const WOLFSSL_EVP_CIPHER * ++crypto_aead_get_hp(const WOLFSSL_EVP_CIPHER *aead) { ++ switch (wolfSSL_EVP_CIPHER_nid(aead)) { ++ case NID_aes_128_gcm: ++ case NID_aes_128_ccm: ++ return wolfSSL_EVP_aes_128_ecb(); ++ case NID_aes_256_gcm: ++ return wolfSSL_EVP_aes_256_ecb(); ++ case NID_chacha20_poly1305: ++ return wolfSSL_EVP_chacha20(); ++ default: ++ return NULL; ++ } ++} ++ + ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls(ngtcp2_crypto_ctx *ctx, + void *tls_native_handle) { + WOLFSSL *ssl = tls_native_handle; +@@ -121,7 +137,7 @@ ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls(ngtcp2_crypto_ctx *ctx, + + ngtcp2_crypto_aead_init(&ctx->aead, (void *)aead); + ctx->md.native_handle = (void *)wolfSSL_quic_get_md(ssl); +- ctx->hp.native_handle = (void *)wolfSSL_quic_get_hp(ssl); ++ ctx->hp.native_handle = (void *)crypto_aead_get_hp(aead); + ctx->max_encryption = crypto_aead_get_aead_max_encryption(aead); + ctx->max_decryption_failure = + crypto_aead_get_aead_max_decryption_failure(aead); +@@ -288,20 +304,33 @@ int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample) { +- static const uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00"; ++ static const uint8_t PLAINTEXT[16] = {0}; + WOLFSSL_EVP_CIPHER_CTX *actx = hp_ctx->native_handle; + int len; + + (void)hp; + +- if (wolfSSL_EVP_EncryptInit_ex(actx, NULL, NULL, NULL, sample) != +- WOLFSSL_SUCCESS || +- wolfSSL_EVP_CipherUpdate(actx, dest, &len, PLAINTEXT, +- sizeof(PLAINTEXT) - 1) != WOLFSSL_SUCCESS || +- wolfSSL_EVP_EncryptFinal_ex(actx, dest + sizeof(PLAINTEXT) - 1, &len) != +- WOLFSSL_SUCCESS) { +- DEBUG_MSG("WOLFSSL: hp_mask FAILED\n"); +- return -1; ++ switch (wolfSSL_EVP_CIPHER_CTX_nid(actx)) { ++ case NID_aes_128_ecb: ++ case NID_aes_256_ecb: ++ if (!wolfSSL_EVP_CipherUpdate(actx, dest, &len, sample, ++ NGTCP2_HP_SAMPLELEN)) { ++ return -1; ++ } ++ ++ break; ++ case NID_chacha20: ++ if (wolfSSL_EVP_EncryptInit_ex(actx, NULL, NULL, NULL, sample) != ++ WOLFSSL_SUCCESS || ++ wolfSSL_EVP_CipherUpdate(actx, dest, &len, PLAINTEXT, ++ sizeof(PLAINTEXT)) != WOLFSSL_SUCCESS || ++ wolfSSL_EVP_EncryptFinal_ex(actx, dest + sizeof(PLAINTEXT), &len) != ++ WOLFSSL_SUCCESS) { ++ DEBUG_MSG("WOLFSSL: hp_mask FAILED\n"); ++ return -1; ++ } ++ ++ break; + } + + return 0; +@@ -442,6 +471,19 @@ int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, uint8_t *data, + return 0; + } + ++int ngtcp2_crypto_get_path_challenge_data2_cb(ngtcp2_conn *conn, ++ ngtcp2_path_challenge_data *data, ++ void *user_data) { ++ (void)conn; ++ (void)user_data; ++ ++ DEBUG_MSG("WOLFSSL: get path challenge data\n"); ++ if (wolfSSL_RAND_bytes(data->data, NGTCP2_PATH_CHALLENGE_DATALEN) != 1) { ++ return NGTCP2_ERR_CALLBACK_FAILURE; ++ } ++ return 0; ++} ++ + int ngtcp2_crypto_random(uint8_t *data, size_t datalen) { + DEBUG_MSG("WOLFSSL: get random\n"); + if (wolfSSL_RAND_bytes(data, (int)datalen) != 1) { +diff --git a/third_party/ngtcp2/lib/CMakeLists.txt b/third_party/ngtcp2/lib/CMakeLists.txt +index dc390e723b5..d16eba05a3e 100644 +--- a/third_party/ngtcp2/lib/CMakeLists.txt ++++ b/third_party/ngtcp2/lib/CMakeLists.txt +@@ -67,12 +67,17 @@ set(ngtcp2_SOURCES + ngtcp2_unreachable.c + ngtcp2_transport_params.c + ngtcp2_settings.c ++ ngtcp2_callbacks.c + ngtcp2_dcidtr.c ++ ngtcp2_pcg.c ++ ngtcp2_ratelim.c ++ ngtcp2_conn_info.c + ) + + set(ngtcp2_INCLUDE_DIRS +- "${CMAKE_CURRENT_SOURCE_DIR}/includes" +- "${CMAKE_CURRENT_BINARY_DIR}/includes" ++ "$" ++ "$" ++ "$" + ) + + set(NGTCP2_GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated") +@@ -103,11 +108,7 @@ if(ENABLE_SHARED_LIB) + C_VISIBILITY_PRESET hidden + POSITION_INDEPENDENT_CODE ON + ) +- foreach(include_DIR IN LISTS ngtcp2_INCLUDE_DIRS) +- target_include_directories(ngtcp2 PUBLIC $) +- endforeach() +- +- target_include_directories(ngtcp2 PUBLIC $) ++ target_include_directories(ngtcp2 PUBLIC ${ngtcp2_INCLUDE_DIRS}) + + install(TARGETS ngtcp2 + EXPORT ${NGTCP2_TARGETS_EXPORT_NAME} +@@ -126,11 +127,7 @@ if(ENABLE_STATIC_LIB) + C_VISIBILITY_PRESET hidden + ) + target_compile_definitions(ngtcp2_static PUBLIC "-DNGTCP2_STATICLIB") +- foreach(include_DIR IN LISTS ngtcp2_INCLUDE_DIRS) +- target_include_directories(ngtcp2_static PUBLIC $) +- endforeach() +- +- target_include_directories(ngtcp2_static PUBLIC $) ++ target_include_directories(ngtcp2_static PUBLIC ${ngtcp2_INCLUDE_DIRS}) + + install(TARGETS ngtcp2_static + EXPORT ${NGTCP2_TARGETS_EXPORT_NAME} +diff --git a/third_party/ngtcp2/lib/Makefile.am b/third_party/ngtcp2/lib/Makefile.am +index c5231115c3d..8748ca5fb43 100644 +--- a/third_party/ngtcp2/lib/Makefile.am ++++ b/third_party/ngtcp2/lib/Makefile.am +@@ -75,7 +75,11 @@ OBJECTS = \ + ngtcp2_unreachable.c \ + ngtcp2_transport_params.c \ + ngtcp2_settings.c \ +- ngtcp2_dcidtr.c ++ ngtcp2_callbacks.c \ ++ ngtcp2_dcidtr.c \ ++ ngtcp2_pcg.c \ ++ ngtcp2_ratelim.c \ ++ ngtcp2_conn_info.c + + HFILES = \ + ngtcp2_pkt.h \ +@@ -120,7 +124,11 @@ HFILES = \ + ngtcp2_unreachable.h \ + ngtcp2_transport_params.h \ + ngtcp2_settings.h \ ++ ngtcp2_callbacks.h \ + ngtcp2_dcidtr.h \ ++ ngtcp2_pcg.h \ ++ ngtcp2_ratelim.h \ ++ ngtcp2_conn_info.h \ + ngtcp2_conn_stat.h \ + ngtcp2_pktns_id.h \ + ngtcp2_tstamp.h +diff --git a/third_party/ngtcp2/lib/config.cmake.in b/third_party/ngtcp2/lib/config.cmake.in +index 435a4d4a500..fffcd517289 100644 +--- a/third_party/ngtcp2/lib/config.cmake.in ++++ b/third_party/ngtcp2/lib/config.cmake.in +@@ -1,3 +1,6 @@ + include(CMakeFindDependencyMacro) ++if("@HAVE_OSSL@") ++ find_dependency(OpenSSL) ++endif() + + include("${CMAKE_CURRENT_LIST_DIR}/@NGTCP2_TARGETS_EXPORT_NAME@.cmake") +diff --git a/third_party/ngtcp2/lib/includes/ngtcp2/ngtcp2.h b/third_party/ngtcp2/lib/includes/ngtcp2/ngtcp2.h +index d7a27b9213b..c71ff364e30 100644 +--- a/third_party/ngtcp2/lib/includes/ngtcp2/ngtcp2.h ++++ b/third_party/ngtcp2/lib/includes/ngtcp2/ngtcp2.h +@@ -262,7 +262,7 @@ typedef struct ngtcp2_mem { + * + * :macro:`NGTCP2_PROTO_VER_V1` is the QUIC version 1. + */ +-#define NGTCP2_PROTO_VER_V1 ((uint32_t)0x00000001u) ++#define NGTCP2_PROTO_VER_V1 ((uint32_t)0x00000001U) + + /** + * @macro +@@ -270,7 +270,7 @@ typedef struct ngtcp2_mem { + * :macro:`NGTCP2_PROTO_VER_V2` is the QUIC version 2. See + * :rfc:`9369`. + */ +-#define NGTCP2_PROTO_VER_V2 ((uint32_t)0x6b3343cfu) ++#define NGTCP2_PROTO_VER_V2 ((uint32_t)0x6B3343CFU) + + /** + * @macro +@@ -294,7 +294,7 @@ typedef struct ngtcp2_mem { + * :macro:`NGTCP2_RESERVED_VERSION_MASK` is the bit mask of reserved + * version. + */ +-#define NGTCP2_RESERVED_VERSION_MASK 0x0a0a0a0au ++#define NGTCP2_RESERVED_VERSION_MASK 0x0A0A0A0AU + + /** + * @macrosection +@@ -306,15 +306,29 @@ typedef struct ngtcp2_mem { + * @macro + * + * :macro:`NGTCP2_MAX_UDP_PAYLOAD_SIZE` is the default maximum UDP +- * datagram payload size that the local endpoint transmits. ++ * datagram payload size that the local endpoint transmits without ++ * Path MTU Discovery (PMTUD) or the custom settings (see ++ * :member:`ngtcp2_settings.max_tx_udp_payload_size` and ++ * :member:`ngtcp2_settings.no_tx_udp_payload_size_shaping`). + */ + #define NGTCP2_MAX_UDP_PAYLOAD_SIZE 1200 + + /** + * @macro + * +- * :macro:`NGTCP2_MAX_PMTUD_UDP_PAYLOAD_SIZE` is the maximum UDP ++ * :macro:`NGTCP2_MAX_TX_UDP_PAYLOAD_SIZE` is the maximum UDP datagram ++ * payload size that this library can output. ++ */ ++#define NGTCP2_MAX_TX_UDP_PAYLOAD_SIZE 65527 ++ ++/** ++ * @macro ++ * ++ * :macro:`NGTCP2_MAX_PMTUD_UDP_PAYLOAD_SIZE` was the maximum UDP + * datagram payload size that Path MTU Discovery can discover. ++ * ++ * Deprecated since v1.17.0. Path MTU Discovery is not capped to this ++ * value anymore. + */ + #define NGTCP2_MAX_PMTUD_UDP_PAYLOAD_SIZE 1452 + +@@ -363,7 +377,7 @@ typedef struct ngtcp2_mem { + * integrity tag of Retry packet. It is used for QUIC v1. + */ + #define NGTCP2_RETRY_KEY_V1 \ +- "\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e" ++ "\xBE\x0C\x69\x0B\x9F\x66\x57\x5A\x1D\x76\x6B\x54\xE3\x68\xC8\x4E" + + /** + * @macro +@@ -371,7 +385,7 @@ typedef struct ngtcp2_mem { + * :macro:`NGTCP2_RETRY_NONCE_V1` is nonce used when generating + * integrity tag of Retry packet. It is used for QUIC v1. + */ +-#define NGTCP2_RETRY_NONCE_V1 "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb" ++#define NGTCP2_RETRY_NONCE_V1 "\x46\x15\x99\xD3\x5D\x63\x2B\xF2\x23\x98\x25\xBB" + + /** + * @macro +@@ -381,7 +395,7 @@ typedef struct ngtcp2_mem { + * :rfc:`9369`. + */ + #define NGTCP2_RETRY_KEY_V2 \ +- "\x8f\xb4\xb0\x1b\x56\xac\x48\xe2\x60\xfb\xcb\xce\xad\x7c\xcc\x92" ++ "\x8F\xB4\xB0\x1B\x56\xAC\x48\xE2\x60\xFB\xCB\xCE\xAD\x7C\xCC\x92" + + /** + * @macro +@@ -390,7 +404,7 @@ typedef struct ngtcp2_mem { + * integrity tag of Retry packet. It is used for QUIC v2. See + * :rfc:`9369`. + */ +-#define NGTCP2_RETRY_NONCE_V2 "\xd8\x69\x69\xbc\x2d\x7c\x6d\x99\x90\xef\xb0\x4a" ++#define NGTCP2_RETRY_NONCE_V2 "\xD8\x69\x69\xBC\x2D\x7C\x6D\x99\x90\xEF\xB0\x4A" + + /** + * @macro +@@ -798,7 +812,7 @@ typedef struct NGTCP2_ALIGN(8) ngtcp2_pkt_info { + * + * :macro:`NGTCP2_PKT_FLAG_NONE` indicates no flag set. + */ +-#define NGTCP2_PKT_FLAG_NONE 0x00u ++#define NGTCP2_PKT_FLAG_NONE 0x00U + + /** + * @macro +@@ -806,7 +820,7 @@ typedef struct NGTCP2_ALIGN(8) ngtcp2_pkt_info { + * :macro:`NGTCP2_PKT_FLAG_LONG_FORM` indicates the Long header packet + * header. + */ +-#define NGTCP2_PKT_FLAG_LONG_FORM 0x01u ++#define NGTCP2_PKT_FLAG_LONG_FORM 0x01U + + /** + * @macro +@@ -814,14 +828,14 @@ typedef struct NGTCP2_ALIGN(8) ngtcp2_pkt_info { + * :macro:`NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR` indicates that Fixed Bit + * (aka QUIC bit) is not set. + */ +-#define NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR 0x02u ++#define NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR 0x02U + + /** + * @macro + * + * :macro:`NGTCP2_PKT_FLAG_KEY_PHASE` indicates Key Phase bit set. + */ +-#define NGTCP2_PKT_FLAG_KEY_PHASE 0x04u ++#define NGTCP2_PKT_FLAG_KEY_PHASE 0x04U + + /** + * @enum +@@ -873,7 +887,7 @@ typedef enum ngtcp2_pkt_type { + * + * :macro:`NGTCP2_NO_ERROR` is QUIC transport error code ``NO_ERROR``. + */ +-#define NGTCP2_NO_ERROR 0x0u ++#define NGTCP2_NO_ERROR 0x0U + + /** + * @macro +@@ -881,7 +895,7 @@ typedef enum ngtcp2_pkt_type { + * :macro:`NGTCP2_INTERNAL_ERROR` is QUIC transport error code + * ``INTERNAL_ERROR``. + */ +-#define NGTCP2_INTERNAL_ERROR 0x1u ++#define NGTCP2_INTERNAL_ERROR 0x1U + + /** + * @macro +@@ -889,7 +903,7 @@ typedef enum ngtcp2_pkt_type { + * :macro:`NGTCP2_CONNECTION_REFUSED` is QUIC transport error code + * ``CONNECTION_REFUSED``. + */ +-#define NGTCP2_CONNECTION_REFUSED 0x2u ++#define NGTCP2_CONNECTION_REFUSED 0x2U + + /** + * @macro +@@ -897,7 +911,7 @@ typedef enum ngtcp2_pkt_type { + * :macro:`NGTCP2_FLOW_CONTROL_ERROR` is QUIC transport error code + * ``FLOW_CONTROL_ERROR``. + */ +-#define NGTCP2_FLOW_CONTROL_ERROR 0x3u ++#define NGTCP2_FLOW_CONTROL_ERROR 0x3U + + /** + * @macro +@@ -905,7 +919,7 @@ typedef enum ngtcp2_pkt_type { + * :macro:`NGTCP2_STREAM_LIMIT_ERROR` is QUIC transport error code + * ``STREAM_LIMIT_ERROR``. + */ +-#define NGTCP2_STREAM_LIMIT_ERROR 0x4u ++#define NGTCP2_STREAM_LIMIT_ERROR 0x4U + + /** + * @macro +@@ -913,7 +927,7 @@ typedef enum ngtcp2_pkt_type { + * :macro:`NGTCP2_STREAM_STATE_ERROR` is QUIC transport error code + * ``STREAM_STATE_ERROR``. + */ +-#define NGTCP2_STREAM_STATE_ERROR 0x5u ++#define NGTCP2_STREAM_STATE_ERROR 0x5U + + /** + * @macro +@@ -921,7 +935,7 @@ typedef enum ngtcp2_pkt_type { + * :macro:`NGTCP2_FINAL_SIZE_ERROR` is QUIC transport error code + * ``FINAL_SIZE_ERROR``. + */ +-#define NGTCP2_FINAL_SIZE_ERROR 0x6u ++#define NGTCP2_FINAL_SIZE_ERROR 0x6U + + /** + * @macro +@@ -929,7 +943,7 @@ typedef enum ngtcp2_pkt_type { + * :macro:`NGTCP2_FRAME_ENCODING_ERROR` is QUIC transport error code + * ``FRAME_ENCODING_ERROR``. + */ +-#define NGTCP2_FRAME_ENCODING_ERROR 0x7u ++#define NGTCP2_FRAME_ENCODING_ERROR 0x7U + + /** + * @macro +@@ -937,7 +951,7 @@ typedef enum ngtcp2_pkt_type { + * :macro:`NGTCP2_TRANSPORT_PARAMETER_ERROR` is QUIC transport error + * code ``TRANSPORT_PARAMETER_ERROR``. + */ +-#define NGTCP2_TRANSPORT_PARAMETER_ERROR 0x8u ++#define NGTCP2_TRANSPORT_PARAMETER_ERROR 0x8U + + /** + * @macro +@@ -945,7 +959,7 @@ typedef enum ngtcp2_pkt_type { + * :macro:`NGTCP2_CONNECTION_ID_LIMIT_ERROR` is QUIC transport error + * code ``CONNECTION_ID_LIMIT_ERROR``. + */ +-#define NGTCP2_CONNECTION_ID_LIMIT_ERROR 0x9u ++#define NGTCP2_CONNECTION_ID_LIMIT_ERROR 0x9U + + /** + * @macro +@@ -953,7 +967,7 @@ typedef enum ngtcp2_pkt_type { + * :macro:`NGTCP2_PROTOCOL_VIOLATION` is QUIC transport error code + * ``PROTOCOL_VIOLATION``. + */ +-#define NGTCP2_PROTOCOL_VIOLATION 0xau ++#define NGTCP2_PROTOCOL_VIOLATION 0xAU + + /** + * @macro +@@ -961,7 +975,7 @@ typedef enum ngtcp2_pkt_type { + * :macro:`NGTCP2_INVALID_TOKEN` is QUIC transport error code + * ``INVALID_TOKEN``. + */ +-#define NGTCP2_INVALID_TOKEN 0xbu ++#define NGTCP2_INVALID_TOKEN 0xBU + + /** + * @macro +@@ -969,7 +983,7 @@ typedef enum ngtcp2_pkt_type { + * :macro:`NGTCP2_APPLICATION_ERROR` is QUIC transport error code + * ``APPLICATION_ERROR``. + */ +-#define NGTCP2_APPLICATION_ERROR 0xcu ++#define NGTCP2_APPLICATION_ERROR 0xCU + + /** + * @macro +@@ -977,7 +991,7 @@ typedef enum ngtcp2_pkt_type { + * :macro:`NGTCP2_CRYPTO_BUFFER_EXCEEDED` is QUIC transport error code + * ``CRYPTO_BUFFER_EXCEEDED``. + */ +-#define NGTCP2_CRYPTO_BUFFER_EXCEEDED 0xdu ++#define NGTCP2_CRYPTO_BUFFER_EXCEEDED 0xDU + + /** + * @macro +@@ -985,7 +999,7 @@ typedef enum ngtcp2_pkt_type { + * :macro:`NGTCP2_KEY_UPDATE_ERROR` is QUIC transport error code + * ``KEY_UPDATE_ERROR``. + */ +-#define NGTCP2_KEY_UPDATE_ERROR 0xeu ++#define NGTCP2_KEY_UPDATE_ERROR 0xEU + + /** + * @macro +@@ -993,7 +1007,7 @@ typedef enum ngtcp2_pkt_type { + * :macro:`NGTCP2_AEAD_LIMIT_REACHED` is QUIC transport error code + * ``AEAD_LIMIT_REACHED``. + */ +-#define NGTCP2_AEAD_LIMIT_REACHED 0xfu ++#define NGTCP2_AEAD_LIMIT_REACHED 0xFU + + /** + * @macro +@@ -1001,7 +1015,7 @@ typedef enum ngtcp2_pkt_type { + * :macro:`NGTCP2_NO_VIABLE_PATH` is QUIC transport error code + * ``NO_VIABLE_PATH``. + */ +-#define NGTCP2_NO_VIABLE_PATH 0x10u ++#define NGTCP2_NO_VIABLE_PATH 0x10U + + /** + * @macro +@@ -1009,7 +1023,7 @@ typedef enum ngtcp2_pkt_type { + * :macro:`NGTCP2_CRYPTO_ERROR` is QUIC transport error code + * ``CRYPTO_ERROR``. + */ +-#define NGTCP2_CRYPTO_ERROR 0x100u ++#define NGTCP2_CRYPTO_ERROR 0x100U + + /** + * @macro +@@ -1173,6 +1187,9 @@ typedef struct ngtcp2_pkt_hd { + * @struct + * + * :type:`ngtcp2_pkt_stateless_reset` represents Stateless Reset. ++ * ++ * Deprecated since v1.22.0. Use :type:`ngtcp2_pkt_stateless_reset2` ++ * instead. + */ + typedef struct ngtcp2_pkt_stateless_reset { + /** +@@ -1190,6 +1207,40 @@ typedef struct ngtcp2_pkt_stateless_reset { + size_t randlen; + } ngtcp2_pkt_stateless_reset; + ++/** ++ * @struct ++ * ++ * :type:`ngtcp2_stateless_reset_token` stores stateless reset token. ++ * ++ * This struct has been available since v1.22.0. ++ */ ++typedef struct ngtcp2_stateless_reset_token { ++ uint8_t data[NGTCP2_STATELESS_RESET_TOKENLEN]; ++} ngtcp2_stateless_reset_token; ++ ++/** ++ * @struct ++ * ++ * :type:`ngtcp2_pkt_stateless_reset2` represents Stateless Reset. ++ * ++ * This struct has been available since v1.22.0. ++ */ ++typedef struct ngtcp2_pkt_stateless_reset2 { ++ /** ++ * :member:`token` contains stateless reset token. ++ */ ++ ngtcp2_stateless_reset_token token; ++ /** ++ * :member:`rand` points a buffer which contains random bytes ++ * section. ++ */ ++ const uint8_t *rand; ++ /** ++ * :member:`randlen` is the number of random bytes. ++ */ ++ size_t randlen; ++} ngtcp2_pkt_stateless_reset2; ++ + /** + * @macrosection + * +@@ -1236,7 +1287,7 @@ typedef struct ngtcp2_pkt_stateless_reset { + * :macro:`NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1` is TLS + * extension type of quic_transport_parameters. + */ +-#define NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1 0x39u ++#define NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1 0x39U + + #ifdef NGTCP2_USE_GENERIC_SOCKADDR + # ifndef NGTCP2_AF_INET +@@ -1267,7 +1318,7 @@ typedef struct ngtcp2_sockaddr_in { + } ngtcp2_sockaddr_in; + + typedef struct ngtcp2_in6_addr { +- uint8_t in6_addr[16]; ++ uint8_t s6_addr[16]; + } ngtcp2_in6_addr; + + typedef struct ngtcp2_sockaddr_in6 { +@@ -1561,7 +1612,8 @@ typedef struct ngtcp2_transport_params { + } ngtcp2_transport_params; + + #define NGTCP2_CONN_INFO_V1 1 +-#define NGTCP2_CONN_INFO_VERSION NGTCP2_CONN_INFO_V1 ++#define NGTCP2_CONN_INFO_V2 2 ++#define NGTCP2_CONN_INFO_VERSION NGTCP2_CONN_INFO_V2 + + /** + * @struct +@@ -1600,6 +1652,52 @@ typedef struct ngtcp2_conn_info { + * packets which have not been acknowledged. + */ + uint64_t bytes_in_flight; ++ /* The following fields have been added since NGTCP2_CONN_INFO_V2. */ ++ /** ++ * :member:`pkt_sent` is the number of QUIC packets sent. This ++ * field has been available since v1.16.0. ++ */ ++ uint64_t pkt_sent; ++ /** ++ * :member:`bytes_sent` is the number of bytes (the sum of QUIC ++ * packet length) sent. This field has been available since ++ * v1.16.0. ++ */ ++ uint64_t bytes_sent; ++ /** ++ * :member:`pkt_recv` is the number of QUIC packets received, ++ * excluding discarded ones. This field has been available since ++ * v1.16.0. ++ */ ++ uint64_t pkt_recv; ++ /** ++ * :member:`bytes_recv` is the number of bytes (the sum of QUIC ++ * packet length) received, excluding discarded ones. This field ++ * has been available since v1.16.0. ++ */ ++ uint64_t bytes_recv; ++ /** ++ * :member:`pkt_lost` is the number of QUIC packets that are ++ * considered lost, excluding PMTUD packets. This field has been ++ * available since v1.16.0. ++ */ ++ uint64_t pkt_lost; ++ /** ++ * :member:`bytes_lost` is the number of bytes (the sum of QUIC ++ * packet length) lost, excluding PMTUD packets. This field has ++ * been available since v1.16.0. ++ */ ++ uint64_t bytes_lost; ++ /** ++ * :member:`ping_recv` is the number of PING frames received. This ++ * field has been available since v1.16.0. ++ */ ++ uint64_t ping_recv; ++ /** ++ * :member:`pkt_discarded` is the number of QUIC packets discarded. ++ * This field has been available since v1.16.0. ++ */ ++ uint64_t pkt_discarded; + } ngtcp2_conn_info; + + /** +@@ -1642,14 +1740,14 @@ typedef void (*ngtcp2_printf)(void *user_data, const char *format, ...); + * + * :macro:`NGTCP2_QLOG_WRITE_FLAG_NONE` indicates no flag set. + */ +-#define NGTCP2_QLOG_WRITE_FLAG_NONE 0x00u ++#define NGTCP2_QLOG_WRITE_FLAG_NONE 0x00U + /** + * @macro + * + * :macro:`NGTCP2_QLOG_WRITE_FLAG_FIN` indicates that this is the + * final call to :type:`ngtcp2_qlog_write` in the current connection. + */ +-#define NGTCP2_QLOG_WRITE_FLAG_FIN 0x01u ++#define NGTCP2_QLOG_WRITE_FLAG_FIN 0x01U + + /** + * @struct +@@ -1704,7 +1802,8 @@ typedef enum ngtcp2_token_type { + + #define NGTCP2_SETTINGS_V1 1 + #define NGTCP2_SETTINGS_V2 2 +-#define NGTCP2_SETTINGS_VERSION NGTCP2_SETTINGS_V2 ++#define NGTCP2_SETTINGS_V3 3 ++#define NGTCP2_SETTINGS_VERSION NGTCP2_SETTINGS_V3 + + /** + * @struct +@@ -1738,7 +1837,9 @@ typedef struct ngtcp2_settings { + ngtcp2_printf log_printf; + /** + * :member:`max_tx_udp_payload_size` is the maximum size of UDP +- * datagram payload that the local endpoint transmits. ++ * datagram payload that the local endpoint transmits. This must be ++ * larger than or equal to :macro:`NGTCP2_MAX_UDP_PAYLOAD_SIZE`, and ++ * less then or equal to :macro:`NGTCP2_MAX_TX_UDP_PAYLOAD_SIZE`. + */ + size_t max_tx_udp_payload_size; + /** +@@ -1802,8 +1903,8 @@ typedef struct ngtcp2_settings { + uint64_t max_stream_window; + /** + * :member:`ack_thresh` is the minimum number of the received ACK +- * eliciting packets that trigger the immediate acknowledgement from +- * the local endpoint. ++ * eliciting packets that triggers the immediate acknowledgement ++ * from the local endpoint. + */ + size_t ack_thresh; + /** +@@ -1903,12 +2004,13 @@ typedef struct ngtcp2_settings { + /** + * :member:`pmtud_probes` is the array of UDP datagram payload size + * to probe during Path MTU Discovery. The discovery is done in the +- * order appeared in this array. The size must be strictly larger +- * than 1200, otherwise the behavior is undefined. The maximum +- * value in this array should be set to +- * :member:`max_tx_udp_payload_size`. If this field is not set, the +- * predefined PMTUD probes are made. This field has been available +- * since v1.4.0. ++ * order appeared in this array. The payload size must be strictly ++ * larger than :macro:`NGTCP2_MAX_UDP_PAYLOAD_SIZE`, and less than ++ * or equal to :macro:`NGTCP2_MAX_TX_UDP_PAYLOAD_SIZE`. Otherwise ++ * the behavior is undefined. The maximum value in this array ++ * should be set to :member:`max_tx_udp_payload_size`. If this ++ * field is not set, the predefined PMTUD probes are made. This ++ * field has been available since v1.4.0. + */ + const uint16_t *pmtud_probes; + /** +@@ -1917,6 +2019,23 @@ typedef struct ngtcp2_settings { + * field has been available since v1.4.0. + */ + size_t pmtud_probeslen; ++ /* The following fields have been added since NGTCP2_SETTINGS_V3. */ ++ /** ++ * :member:`glitch_ratelim_burst` is the maximum number of tokens ++ * available to "glitch" rate limiter. "glitch" is a suspicious ++ * activity from a remote endpoint. If detected, certain amount of ++ * tokens are consumed. If no tokens are available to consume, the ++ * connection is closed. The rate of token generation is specified ++ * by :member:`glitch_ratelim_rate`. This field has been available ++ * since v1.15.0. ++ */ ++ uint64_t glitch_ratelim_burst; ++ /** ++ * :member:`glitch_ratelim_rate` is the number of tokens generated ++ * per second. See :member:`glitch_ratelim_burst` for "glitch" rate ++ * limiter. This field has been available since v1.15.0. ++ */ ++ uint64_t glitch_ratelim_rate; + } ngtcp2_settings; + + /** +@@ -2362,11 +2481,42 @@ NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_decode_hd_short(ngtcp2_pkt_hd *dest, + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * |randlen| is strictly less than + * :macro:`NGTCP2_MIN_STATELESS_RESET_RANDLEN`. ++ * ++ * Deprecated since v1.22.0. Use `ngtcp2_pkt_write_stateless_reset2` ++ * instead. + */ + NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_write_stateless_reset( + uint8_t *dest, size_t destlen, const uint8_t *stateless_reset_token, + const uint8_t *rand, size_t randlen); + ++/** ++ * @function ++ * ++ * `ngtcp2_pkt_write_stateless_reset2` writes Stateless Reset packet ++ * in the buffer pointed by |dest| whose length is |destlen|. |token| ++ * must store the Stateless Reset Token. |rand| specifies the random ++ * octets preceding Stateless Reset Token. The length of |rand| is ++ * specified by |randlen| which must be at least ++ * :macro:`NGTCP2_MIN_STATELESS_RESET_RANDLEN` bytes long. ++ * ++ * If |randlen| is too long to write them all in the buffer, |rand| is ++ * written to the buffer as much as possible, and is truncated. ++ * ++ * This function returns the number of bytes written to the buffer, or ++ * one of the following negative error codes: ++ * ++ * :macro:`NGTCP2_ERR_NOBUF` ++ * Buffer is too small. ++ * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` ++ * |randlen| is strictly less than ++ * :macro:`NGTCP2_MIN_STATELESS_RESET_RANDLEN`. ++ * ++ * This function has been available since v1.22.0. ++ */ ++NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_write_stateless_reset2( ++ uint8_t *dest, size_t destlen, const ngtcp2_stateless_reset_token *token, ++ const uint8_t *rand, size_t randlen); ++ + /** + * @function + * +@@ -2662,7 +2812,7 @@ typedef int (*ngtcp2_hp_mask)(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + * + * :macro:`NGTCP2_STREAM_DATA_FLAG_NONE` indicates no flag set. + */ +-#define NGTCP2_STREAM_DATA_FLAG_NONE 0x00u ++#define NGTCP2_STREAM_DATA_FLAG_NONE 0x00U + + /** + * @macro +@@ -2670,7 +2820,7 @@ typedef int (*ngtcp2_hp_mask)(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + * :macro:`NGTCP2_STREAM_DATA_FLAG_FIN` indicates that this chunk of + * data is final piece of an incoming stream. + */ +-#define NGTCP2_STREAM_DATA_FLAG_FIN 0x01u ++#define NGTCP2_STREAM_DATA_FLAG_FIN 0x01U + + /** + * @macro +@@ -2679,7 +2829,7 @@ typedef int (*ngtcp2_hp_mask)(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + * data contains data received in 0-RTT packet, and the handshake has + * not completed yet, which means that the data might be replayed. + */ +-#define NGTCP2_STREAM_DATA_FLAG_0RTT 0x02u ++#define NGTCP2_STREAM_DATA_FLAG_0RTT 0x02U + + /** + * @functypedef +@@ -2734,7 +2884,7 @@ typedef int (*ngtcp2_stream_open)(ngtcp2_conn *conn, int64_t stream_id, + * + * :macro:`NGTCP2_STREAM_CLOSE_FLAG_NONE` indicates no flag set. + */ +-#define NGTCP2_STREAM_CLOSE_FLAG_NONE 0x00u ++#define NGTCP2_STREAM_CLOSE_FLAG_NONE 0x00U + + /** + * @macro +@@ -2742,7 +2892,7 @@ typedef int (*ngtcp2_stream_open)(ngtcp2_conn *conn, int64_t stream_id, + * :macro:`NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET` indicates that + * app_error_code parameter is set. + */ +-#define NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET 0x01u ++#define NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET 0x01U + + /** + * @functypedef +@@ -2823,6 +2973,9 @@ typedef int (*ngtcp2_acked_stream_data_offset)( + * The implementation of this callback should return 0 if it succeeds. + * Returning :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library + * call return immediately. ++ * ++ * Deprecated since v1.22.0. Use :type:`ngtcp2_recv_stateless_reset2` ++ * instead. + */ + typedef int (*ngtcp2_recv_stateless_reset)(ngtcp2_conn *conn, + const ngtcp2_pkt_stateless_reset *sr, +@@ -2887,6 +3040,9 @@ typedef void (*ngtcp2_rand)(uint8_t *dest, size_t destlen, + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. ++ * ++ * Deprecated since v1.22.0. Use ++ * :type:`ngtcp2_get_new_connection_id2` instead. + */ + typedef int (*ngtcp2_get_new_connection_id)(ngtcp2_conn *conn, ngtcp2_cid *cid, + uint8_t *token, size_t cidlen, +@@ -2951,7 +3107,7 @@ typedef int (*ngtcp2_update_key)( + * + * :macro:`NGTCP2_PATH_VALIDATION_FLAG_NONE` indicates no flag set. + */ +-#define NGTCP2_PATH_VALIDATION_FLAG_NONE 0x00u ++#define NGTCP2_PATH_VALIDATION_FLAG_NONE 0x00U + + /** + * @macro +@@ -2960,7 +3116,7 @@ typedef int (*ngtcp2_update_key)( + * validation involving server preferred address. This flag is only + * set for client. + */ +-#define NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR 0x01u ++#define NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR 0x01U + + /** + * @macro +@@ -2969,7 +3125,29 @@ typedef int (*ngtcp2_update_key)( + * server should send NEW_TOKEN frame for the new remote address. + * This flag is only set for server. + */ +-#define NGTCP2_PATH_VALIDATION_FLAG_NEW_TOKEN 0x02u ++#define NGTCP2_PATH_VALIDATION_FLAG_NEW_TOKEN 0x02U ++ ++/** ++ * @functypedef ++ * ++ * :type:`ngtcp2_begin_path_validation` is a callback function which ++ * is called when the path validation has started. |flags| is zero or ++ * more of :macro:`NGTCP2_PATH_VALIDATION_FLAG_* ++ * `. |path| is the path that is ++ * being validated. |fallback_path|, if not NULL, is the path that is ++ * used when this validation fails. ++ * ++ * Currently, the flags may only contain ++ * :macro:`NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR`. ++ * ++ * The callback function must return 0 if it succeeds. Returning ++ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return ++ * immediately. ++ */ ++typedef int (*ngtcp2_begin_path_validation)(ngtcp2_conn *conn, uint32_t flags, ++ const ngtcp2_path *path, ++ const ngtcp2_path *fallback_path, ++ void *user_data); + + /** + * @functypedef +@@ -2978,9 +3156,8 @@ typedef int (*ngtcp2_update_key)( + * an application the outcome of path validation. |flags| is zero or + * more of :macro:`NGTCP2_PATH_VALIDATION_FLAG_* + * `. |path| is the path that was +- * validated. |old_path| is the path that is previously used before a +- * local endpoint has migrated to |path| if |old_path| is not NULL. +- * If |res| is ++ * validated. |fallback_path|, if not NULL, is the path that is used ++ * if the path validation failed. If |res| is + * :enum:`ngtcp2_path_validation_result.NGTCP2_PATH_VALIDATION_RESULT_SUCCESS`, + * the path validation succeeded. If |res| is + * :enum:`ngtcp2_path_validation_result.NGTCP2_PATH_VALIDATION_RESULT_FAILURE`, +@@ -2992,7 +3169,7 @@ typedef int (*ngtcp2_update_key)( + */ + typedef int (*ngtcp2_path_validation)(ngtcp2_conn *conn, uint32_t flags, + const ngtcp2_path *path, +- const ngtcp2_path *old_path, ++ const ngtcp2_path *fallback_path, + ngtcp2_path_validation_result res, + void *user_data); + +@@ -3063,6 +3240,9 @@ typedef enum ngtcp2_connection_id_status_type { + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. ++ * ++ * Deprecated since v1.22.0. Use :type:`ngtcp2_connection_id_status2` ++ * instead. + */ + typedef int (*ngtcp2_connection_id_status)( + ngtcp2_conn *conn, ngtcp2_connection_id_status_type type, uint64_t seq, +@@ -3118,7 +3298,7 @@ typedef void (*ngtcp2_delete_crypto_cipher_ctx)( + * + * :macro:`NGTCP2_DATAGRAM_FLAG_NONE` indicates no flag set. + */ +-#define NGTCP2_DATAGRAM_FLAG_NONE 0x00u ++#define NGTCP2_DATAGRAM_FLAG_NONE 0x00U + + /** + * @macro +@@ -3127,7 +3307,7 @@ typedef void (*ngtcp2_delete_crypto_cipher_ctx)( + * received in 0-RTT packet, and the handshake has not completed yet, + * which means that the data might be replayed. + */ +-#define NGTCP2_DATAGRAM_FLAG_0RTT 0x01u ++#define NGTCP2_DATAGRAM_FLAG_0RTT 0x01U + + /** + * @functypedef +@@ -3190,6 +3370,9 @@ typedef int (*ngtcp2_lost_datagram)(ngtcp2_conn *conn, uint64_t dgram_id, + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. ++ * ++ * Deprecated since v1.22.0. Use ++ * :type:`ngtcp2_get_path_challenge_data2` instead. + */ + typedef int (*ngtcp2_get_path_challenge_data)(ngtcp2_conn *conn, uint8_t *data, + void *user_data); +@@ -3261,8 +3444,99 @@ typedef int (*ngtcp2_recv_key)(ngtcp2_conn *conn, ngtcp2_encryption_level level, + typedef int (*ngtcp2_tls_early_data_rejected)(ngtcp2_conn *conn, + void *user_data); + ++/** ++ * @functypedef ++ * ++ * :type:`ngtcp2_recv_stateless_reset2` is a callback function which ++ * is called when Stateless Reset packet is received. The stateless ++ * reset details are given in |sr|. ++ * ++ * The implementation of this callback should return 0 if it succeeds. ++ * Returning :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library ++ * call return immediately. ++ * ++ * This type has been available since v1.22.0 ++ */ ++typedef int (*ngtcp2_recv_stateless_reset2)( ++ ngtcp2_conn *conn, const ngtcp2_pkt_stateless_reset2 *sr, void *user_data); ++ ++/** ++ * @functypedef ++ * ++ * :type:`ngtcp2_get_new_connection_id2` is a callback function to ask ++ * an application for new connection ID. Application must generate ++ * new unused connection ID with the exact |cidlen| bytes, and store ++ * it in |cid|. It also has to generate a stateless reset token, and ++ * store it in |token|. ++ * ++ * The callback function must return 0 if it succeeds. Returning ++ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return ++ * immediately. ++ * ++ * This type has been available since v1.22.0 ++ */ ++typedef int (*ngtcp2_get_new_connection_id2)( ++ ngtcp2_conn *conn, ngtcp2_cid *cid, ngtcp2_stateless_reset_token *token, ++ size_t cidlen, void *user_data); ++ ++/** ++ * @functypedef ++ * ++ * :type:`ngtcp2_connection_id_status2` is a callback function which ++ * is called when the status of Destination Connection ID changes. ++ * ++ * |token| is the associated stateless reset token, and it is ``NULL`` ++ * if no token is present. ++ * ++ * |type| is the one of the value defined in ++ * :type:`ngtcp2_connection_id_status_type`. The new value might be ++ * added in the future release. ++ * ++ * The callback function must return 0 if it succeeds. Returning ++ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return ++ * immediately. ++ * ++ * This type has been available since v1.22.0 ++ */ ++typedef int (*ngtcp2_connection_id_status2)( ++ ngtcp2_conn *conn, ngtcp2_connection_id_status_type type, uint64_t seq, ++ const ngtcp2_cid *cid, const ngtcp2_stateless_reset_token *token, ++ void *user_data); ++ ++/** ++ * @struct ++ * ++ * :type:`ngtcp2_path_challenge_data` stores path challenge data. ++ * ++ * This type has been available since v1.22.0. ++ */ ++typedef struct ngtcp2_path_challenge_data { ++ uint8_t data[NGTCP2_PATH_CHALLENGE_DATALEN]; ++} ngtcp2_path_challenge_data; ++ ++/** ++ * @functypedef ++ * ++ * :type:`ngtcp2_get_path_challenge_data2` is a callback function to ++ * ask an application for new data that is sent in PATH_CHALLENGE ++ * frame. Application must generate new unpredictable, exactly ++ * :macro:`NGTCP2_PATH_CHALLENGE_DATALEN` bytes of random data, and ++ * store them into |data|. ++ * ++ * The callback function must return 0 if it succeeds. Returning ++ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return ++ * immediately. ++ * ++ * This type has been available since v1.22.0. ++ */ ++typedef int (*ngtcp2_get_path_challenge_data2)(ngtcp2_conn *conn, ++ ngtcp2_path_challenge_data *data, ++ void *user_data); ++ + #define NGTCP2_CALLBACKS_V1 1 +-#define NGTCP2_CALLBACKS_VERSION NGTCP2_CALLBACKS_V1 ++#define NGTCP2_CALLBACKS_V2 2 ++#define NGTCP2_CALLBACKS_V3 3 ++#define NGTCP2_CALLBACKS_VERSION NGTCP2_CALLBACKS_V3 + + /** + * @struct +@@ -3349,6 +3623,11 @@ typedef struct ngtcp2_callbacks { + * :member:`recv_stateless_reset` is a callback function which is + * invoked when Stateless Reset packet is received. This callback + * function is optional. ++ * ++ * Deprecated since v1.22.0. Use :member:`recv_stateless_reset2` ++ * instead. If both :member:`recv_stateless_reset` and ++ * :member:`recv_stateless_reset2` are set, the latter has the ++ * precedence. + */ + ngtcp2_recv_stateless_reset recv_stateless_reset; + /** +@@ -3379,8 +3658,14 @@ typedef struct ngtcp2_callbacks { + ngtcp2_rand rand; + /** + * :member:`get_new_connection_id` is a callback function which is +- * invoked when the library needs new connection ID. This callback +- * function must be specified. ++ * invoked when the library needs new connection ID. Either this ++ * callback function or :member:`get_new_connection_id2` must be ++ * specified. ++ * ++ * Deprecated since v1.22.0. Use :member:`get_new_connection_id2` ++ * instead. If both :member:`get_new_connection_id` and ++ * :member:`get_new_connection_id2` are set, the latter has the ++ * precedence. + */ + ngtcp2_get_new_connection_id get_new_connection_id; + /** +@@ -3441,6 +3726,10 @@ typedef struct ngtcp2_callbacks { + * when the new Destination Connection ID is activated, or the + * activated Destination Connection ID is now deactivated. This + * callback function is optional. ++ * ++ * Deprecated since v1.22.0. Use :member:`dcid_status2` instead. ++ * If both :member:`dcid_status` and :member:`dcid_status2` are set, ++ * the latter has the precedence. + */ + ngtcp2_connection_id_status dcid_status; + /** +@@ -3491,6 +3780,9 @@ typedef struct ngtcp2_callbacks { + * :member:`get_path_challenge_data` is a callback function which is + * invoked when the library needs new data sent along with + * PATH_CHALLENGE frame. This callback must be specified. ++ * ++ * Deprecated since v1.22.0. Use :member:`get_path_challenge_data2` ++ * instead. + */ + ngtcp2_get_path_challenge_data get_path_challenge_data; + /** +@@ -3527,6 +3819,42 @@ typedef struct ngtcp2_callbacks { + * is only used by client. + */ + ngtcp2_tls_early_data_rejected tls_early_data_rejected; ++ /* The following fields have been added since NGTCP2_CALLBACKS_V2. */ ++ /** ++ * :member:`begin_path_validation` is a callback function which is ++ * invoked when a path validation has started. This field is ++ * available since v1.14.0. ++ */ ++ ngtcp2_begin_path_validation begin_path_validation; ++ /* The following fields have been added since NGTCP2_CALLBACKS_V3. */ ++ /** ++ * :member:`recv_stateless_reset2` is a callback function which is ++ * invoked when Stateless Reset packet is received. This callback ++ * function is optional. This field is available since v1.22.0. ++ */ ++ ngtcp2_recv_stateless_reset2 recv_stateless_reset2; ++ /** ++ * :member:`get_new_connection_id2` is a callback function which is ++ * invoked when the library needs new connection ID. This callback ++ * function must be specified. This field is available since ++ * v1.22.0. ++ */ ++ ngtcp2_get_new_connection_id2 get_new_connection_id2; ++ /** ++ * :member:`dcid_status2` is a callback function which is invoked ++ * when the new Destination Connection ID is activated, or the ++ * activated Destination Connection ID is now deactivated. This ++ * callback function is optional. This field is available since ++ * v1.22.0. ++ */ ++ ngtcp2_connection_id_status2 dcid_status2; ++ /** ++ * :member:`get_path_challenge_data2` is a callback function which ++ * is invoked when the library needs new data sent along with ++ * PATH_CHALLENGE frame. This callback must be specified. This ++ * field is available since v1.22.0. ++ */ ++ ngtcp2_get_path_challenge_data2 get_path_challenge_data2; + } ngtcp2_callbacks; + + /** +@@ -3742,6 +4070,21 @@ NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_write_pkt_versioned( + ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, ngtcp2_tstamp ts); + ++/** ++ * @function ++ * ++ * `ngtcp2_conn_continue_handshake` resumes handshake interrupted by ++ * TLS stack routine (e.g., private key operation offloading, ++ * certificate lookup, etc). ++ * ++ * This function returns 0 if it succeeds. In general, this function ++ * returns the same set of error codes from `ngtcp2_conn_read_pkt`. ++ * ++ * This function has been available since v1.22.0. ++ */ ++NGTCP2_EXTERN int ngtcp2_conn_continue_handshake(ngtcp2_conn *conn, ++ ngtcp2_tstamp ts); ++ + /** + * @function + * +@@ -4061,9 +4404,7 @@ NGTCP2_EXTERN void ngtcp2_conn_set_keep_alive_timeout(ngtcp2_conn *conn, + * `ngtcp2_conn_get_expiry` returns the next expiry time. It returns + * ``UINT64_MAX`` if there is no next expiry. + * +- * Call `ngtcp2_conn_handle_expiry` and then +- * `ngtcp2_conn_writev_stream` (or `ngtcp2_conn_writev_datagram`) when +- * the expiry time has passed. ++ * Call `ngtcp2_conn_handle_expiry` when the expiry time has passed. + */ + NGTCP2_EXTERN ngtcp2_tstamp ngtcp2_conn_get_expiry(ngtcp2_conn *conn); + +@@ -4071,6 +4412,20 @@ NGTCP2_EXTERN ngtcp2_tstamp ngtcp2_conn_get_expiry(ngtcp2_conn *conn); + * @function + * + * `ngtcp2_conn_handle_expiry` handles expired timer. ++ * ++ * If it returns :macro:`NGTCP2_ERR_IDLE_CLOSE`, it means that an idle ++ * timer has fired for this particular connection. In this case, drop ++ * the connection without calling ++ * `ngtcp2_conn_write_connection_close`. If it returns any of the ++ * other negative error codes, close the connection by sending the ++ * terminal packet produced by `ngtcp2_conn_write_connection_close`. ++ * Otherwise, schedule `ngtcp2_conn_writev_stream` call. An ++ * application may call any number of additional ++ * `ngtcp2_conn_read_pkt` and `ngtcp2_conn_handle_expiry` before ++ * calling `ngtcp2_conn_writev_stream`. After calling ++ * `ngtcp2_conn_writev_stream`, new expiry is set. The application ++ * should call `ngtcp2_conn_get_expiry` to get a new deadline and set ++ * the timer. + */ + NGTCP2_EXTERN int ngtcp2_conn_handle_expiry(ngtcp2_conn *conn, + ngtcp2_tstamp ts); +@@ -4389,7 +4744,7 @@ NGTCP2_EXTERN int ngtcp2_conn_shutdown_stream_read(ngtcp2_conn *conn, + * + * :macro:`NGTCP2_WRITE_STREAM_FLAG_NONE` indicates no flag set. + */ +-#define NGTCP2_WRITE_STREAM_FLAG_NONE 0x00u ++#define NGTCP2_WRITE_STREAM_FLAG_NONE 0x00U + + /** + * @macro +@@ -4397,7 +4752,7 @@ NGTCP2_EXTERN int ngtcp2_conn_shutdown_stream_read(ngtcp2_conn *conn, + * :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE` indicates that more data may + * come, and should be coalesced into the same packet if possible. + */ +-#define NGTCP2_WRITE_STREAM_FLAG_MORE 0x01u ++#define NGTCP2_WRITE_STREAM_FLAG_MORE 0x01U + + /** + * @macro +@@ -4405,7 +4760,7 @@ NGTCP2_EXTERN int ngtcp2_conn_shutdown_stream_read(ngtcp2_conn *conn, + * :macro:`NGTCP2_WRITE_STREAM_FLAG_FIN` indicates that a passed data + * is the final part of a stream. + */ +-#define NGTCP2_WRITE_STREAM_FLAG_FIN 0x02u ++#define NGTCP2_WRITE_STREAM_FLAG_FIN 0x02U + + /** + * @macro +@@ -4416,7 +4771,7 @@ NGTCP2_EXTERN int ngtcp2_conn_shutdown_stream_read(ngtcp2_conn *conn, + * finalizing it. PATH_CHALLENGE, PATH_RESPONSE, CONNECTION_CLOSE + * only packets and PMTUD packets are excluded. + */ +-#define NGTCP2_WRITE_STREAM_FLAG_PADDING 0x04u ++#define NGTCP2_WRITE_STREAM_FLAG_PADDING 0x04U + + /** + * @function +@@ -4601,7 +4956,7 @@ NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_writev_stream_versioned( + * + * :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_NONE` indicates no flag set. + */ +-#define NGTCP2_WRITE_DATAGRAM_FLAG_NONE 0x00u ++#define NGTCP2_WRITE_DATAGRAM_FLAG_NONE 0x00U + + /** + * @macro +@@ -4609,7 +4964,7 @@ NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_writev_stream_versioned( + * :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_MORE` indicates that more data + * may come, and should be coalesced into the same packet if possible. + */ +-#define NGTCP2_WRITE_DATAGRAM_FLAG_MORE 0x01u ++#define NGTCP2_WRITE_DATAGRAM_FLAG_MORE 0x01U + + /** + * @macro +@@ -4620,7 +4975,7 @@ NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_writev_stream_versioned( + * finalizing it. PATH_CHALLENGE, PATH_RESPONSE, CONNECTION_CLOSE + * only packets and PMTUD packets are excluded. + */ +-#define NGTCP2_WRITE_DATAGRAM_FLAG_PADDING 0x02u ++#define NGTCP2_WRITE_DATAGRAM_FLAG_PADDING 0x02U + + /** + * @function +@@ -4857,6 +5212,8 @@ NGTCP2_EXTERN size_t ngtcp2_conn_get_scid(ngtcp2_conn *conn, ngtcp2_cid *dest); + * + * :type:`ngtcp2_cid_token` is the convenient struct to store + * Connection ID, its associated path, and stateless reset token. ++ * ++ * Deprecated since v1.22.0. Use :type:`ngtcp2_cid_token2` instead. + */ + typedef struct ngtcp2_cid_token { + /** +@@ -4896,10 +5253,65 @@ typedef struct ngtcp2_cid_token { + * sizeof(:type:`ngtcp2_cid_token`) * n bytes available, where n is + * the return value of `ngtcp2_conn_get_active_dcid` with |dest| == + * NULL. ++ * ++ * Deprecated since v1.22.0. Use `ngtcp2_conn_get_active_dcid2` ++ * instead. + */ + NGTCP2_EXTERN size_t ngtcp2_conn_get_active_dcid(ngtcp2_conn *conn, + ngtcp2_cid_token *dest); + ++/** ++ * @struct ++ * ++ * :type:`ngtcp2_cid_token2` is the convenient struct to store ++ * Connection ID, its associated path, and stateless reset token. ++ * ++ * This type has been available since v1.22.0. ++ */ ++typedef struct ngtcp2_cid_token2 { ++ /** ++ * :member:`seq` is the sequence number of this Connection ID. ++ */ ++ uint64_t seq; ++ /** ++ * :member:`cid` is Connection ID. ++ */ ++ ngtcp2_cid cid; ++ /** ++ * :member:`ps` is the path which this Connection ID is associated ++ * with. ++ */ ++ ngtcp2_path_storage ps; ++ /** ++ * :member:`token` is the stateless reset token for this Connection ++ * ID. ++ */ ++ ngtcp2_stateless_reset_token token; ++ /** ++ * :member:`token_present` is nonzero if token contains stateless ++ * reset token. ++ */ ++ uint8_t token_present; ++} ngtcp2_cid_token2; ++ ++/** ++ * @function ++ * ++ * `ngtcp2_conn_get_active_dcid2` writes the all active Destination ++ * Connection IDs and their tokens to |dest|. Before handshake ++ * completes, this function returns 0. If |dest| is NULL, this ++ * function does not write anything, and returns the number of ++ * Destination Connection IDs that would otherwise be written to the ++ * provided buffer. The buffer pointed by |dest| must have ++ * sizeof(:type:`ngtcp2_cid_token2`) * n bytes available, where n is ++ * the return value of `ngtcp2_conn_get_active_dcid2` with |dest| == ++ * NULL. ++ * ++ * This function has been available since v1.22.0. ++ */ ++NGTCP2_EXTERN size_t ngtcp2_conn_get_active_dcid2(ngtcp2_conn *conn, ++ ngtcp2_cid_token2 *dest); ++ + /** + * @function + * +@@ -5430,7 +5842,7 @@ NGTCP2_EXTERN void ngtcp2_ccerr_set_application_error(ngtcp2_ccerr *ccerr, + * @function + * + * `ngtcp2_conn_write_connection_close` writes a packet which contains +- * CONNECTION_CLOSE frame(s) (type 0x1c or 0x1d) in the buffer pointed ++ * CONNECTION_CLOSE frame(s) (type 0x1C or 0x1D) in the buffer pointed + * by |dest| whose capacity is |destlen|. + * + * For client, |destlen| should be at least +@@ -5447,16 +5859,18 @@ NGTCP2_EXTERN void ngtcp2_ccerr_set_application_error(ngtcp2_ccerr *ccerr, + * + * If :member:`ccerr->type ` == + * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_TRANSPORT`, this +- * function sends CONNECTION_CLOSE (type 0x1c) frame. If ++ * function sends CONNECTION_CLOSE (type 0x1C) frame. If + * :member:`ccerr->type ` == + * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_APPLICATION`, it sends +- * CONNECTION_CLOSE (type 0x1d) frame. Otherwise, it does not produce ++ * CONNECTION_CLOSE (type 0x1D) frame. Otherwise, it does not produce + * any data, and returns 0. + * + * |destlen| could be shorten by some factors (e.g., server side + * amplification limit). This function returns + * :macro:`NGTCP2_ERR_NOBUF` if the resulting buffer is too small even +- * if the given buffer has enough space. ++ * if the given buffer has enough space. This can happen if sending a ++ * packet would exceed a transmission limit (e.g., for amplification ++ * attack protection). + * + * This function must not be called from inside the callback + * functions. +@@ -5471,7 +5885,8 @@ NGTCP2_EXTERN void ngtcp2_ccerr_set_application_error(ngtcp2_ccerr *ccerr, + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + * :macro:`NGTCP2_ERR_NOBUF` +- * Buffer is too small ++ * Buffer is too small or packet would exceed the transmission ++ * limit (e.g., for amplification attack protection). + * :macro:`NGTCP2_ERR_INVALID_STATE` + * The current state does not allow sending CONNECTION_CLOSE + * frame. +@@ -5535,6 +5950,26 @@ NGTCP2_EXTERN int ngtcp2_conn_set_stream_user_data(ngtcp2_conn *conn, + int64_t stream_id, + void *stream_user_data); + ++/** ++ * @function ++ * ++ * `ngtcp2_conn_get_stream_user_data` returns stream_user_data ++ * associated to the stream identified by |stream_id|. If the stream ++ * is not found, or no stream data is associated to the stream, this ++ * function returns NULL. ++ * ++ * The stream_user_data can be associated to the stream by one of the ++ * following functions: ++ * ++ * - `ngtcp2_conn_open_bidi_stream` ++ * - `ngtcp2_conn_open_uni_stream` ++ * - `ngtcp2_conn_set_stream_user_data` ++ * ++ * This function has been available since v1.17.0. ++ */ ++NGTCP2_EXTERN void *ngtcp2_conn_get_stream_user_data(ngtcp2_conn *conn, ++ int64_t stream_id); ++ + /** + * @function + * +@@ -5568,6 +6003,119 @@ NGTCP2_EXTERN size_t ngtcp2_conn_get_send_quantum(ngtcp2_conn *conn); + NGTCP2_EXTERN size_t ngtcp2_conn_get_stream_loss_count(ngtcp2_conn *conn, + int64_t stream_id); + ++/** ++ * @functypedef ++ * ++ * :type:`ngtcp2_write_pkt` is a callback function to write a single ++ * packet in the buffer pointed by |dest| of length |destlen|. The ++ * implementation should use `ngtcp2_conn_write_pkt`, ++ * `ngtcp2_conn_writev_stream`, `ngtcp2_conn_writev_datagram`, or ++ * their variants to write the packet. |path|, |pi|, |dest|, ++ * |destlen|, and |ts| should be directly passed to those functions. ++ * If the callback succeeds, it should return the number of bytes ++ * written to the buffer. In general, this callback function should ++ * return the value that the above mentioned functions returned except ++ * for the following error codes: ++ * ++ * - :macro:`NGTCP2_ERR_STREAM_DATA_BLOCKED` ++ * - :macro:`NGTCP2_ERR_STREAM_SHUT_WR` ++ * - :macro:`NGTCP2_ERR_STREAM_NOT_FOUND` ++ * ++ * Those error codes should be handled by an application. If any ++ * error occurred outside those functions, return ++ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`. If no packet is produced, ++ * return 0. ++ * ++ * Because GSO requires that the aggregated packets have the same ++ * length, :macro:`NGTCP2_WRITE_STREAM_FLAG_PADDING` (or ++ * :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_PADDING` if ++ * `ngtcp2_conn_writev_datagram` is used) is recommended. ++ * ++ * This callback function has been available since v1.15.0. ++ */ ++typedef ngtcp2_ssize (*ngtcp2_write_pkt)(ngtcp2_conn *conn, ngtcp2_path *path, ++ ngtcp2_pkt_info *pi, uint8_t *dest, ++ size_t destlen, ngtcp2_tstamp ts, ++ void *user_data); ++ ++/** ++ * @function ++ * ++ * `ngtcp2_conn_write_aggregate_pkt` is a helper function to write ++ * multiple packets in the provided buffer, which is suitable to be ++ * sent at once in GSO. This function returns the number of bytes ++ * written to the buffer pointed by |buf| of length |buflen|. ++ * |buflen| must be at least ++ * `ngtcp2_conn_get_path_max_tx_udp_payload_size(conn) ++ * ` bytes long. It is ++ * recommended to pass the buffer at least ++ * `ngtcp2_conn_get_max_tx_udp_payload_size(conn) ++ * ` bytes in order to send a ++ * PMTUD packet. This function only writes multiple packets if the ++ * first packet is `ngtcp2_conn_get_path_max_tx_udp_payload_size(conn) ++ * ` bytes long. The ++ * application can adjust the length of the buffer to limit the number ++ * of packets to aggregate (or use `ngtcp2_conn_write_aggregate_pkt2` ++ * to control the number of packets to write directly). If this ++ * function returns positive integer, all packets share the same ++ * :type:`ngtcp2_path` and :type:`ngtcp2_pkt_info` values, and they ++ * are assigned to the objects pointed by |path| and |pi| ++ * respectively. The length of all packets other than the last packet ++ * is assigned to |*pgsolen|. The length of last packet is equal to ++ * or less than |*pgsolen|. |write_pkt| must write a single packet. ++ * After all packets are written, this function calls ++ * `ngtcp2_conn_update_pkt_tx_time`. ++ * ++ * This function is equivalent to call ++ * `ngtcp2_conn_write_aggregate_pkt2` with |buflen| = min(|buflen|, ++ * `ngtcp2_conn_get_send_quantum(conn) ++ * `) and |num_pkts| = 0 followed by ++ * `ngtcp2_conn_update_pkt_tx_time(conn) ++ * `. ++ * ++ * This function returns the number of bytes written to the buffer, or ++ * a negative error code returned by |write_pkt|. ++ * ++ * This function has been available since v1.15.0. ++ */ ++NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_write_aggregate_pkt_versioned( ++ ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, ++ ngtcp2_pkt_info *pi, uint8_t *buf, size_t buflen, size_t *pgsolen, ++ ngtcp2_write_pkt write_pkt, ngtcp2_tstamp ts); ++ ++/** ++ * @function ++ * ++ * `ngtcp2_conn_write_aggregate_pkt2` behaves like ++ * `ngtcp2_conn_write_aggregate_pkt`, but it accepts |num_pkts| to ++ * specify the maximum number of packets to write. If |num_pkts| is ++ * 0, this function writes packets as much as possible. The actual ++ * number of packets to write is determined by the connection state ++ * (e.g., the congestion controller, data available to send) and the ++ * length of packet produced. It also does not clamp |buflen|, and ++ * does not call `ngtcp2_conn_update_pkt_tx_time`. ++ * ++ * This function offers more flexibility and optimization chances to ++ * an application. It can experiment different GSO buffer size ++ * strategy and number of GSO writes per event loop. ++ * ++ * This function has been available since v1.17.0. ++ */ ++NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_write_aggregate_pkt2_versioned( ++ ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, ++ ngtcp2_pkt_info *pi, uint8_t *buf, size_t buflen, size_t *pgsolen, ++ ngtcp2_write_pkt write_pkt, size_t num_pkts, ngtcp2_tstamp ts); ++ ++/** ++ * @function ++ * ++ * `ngtcp2_conn_get_timestamp` returns the latest timestamp that is ++ * known to |conn|. ++ * ++ * This function has been available since v1.16.0. ++ */ ++NGTCP2_EXTERN ngtcp2_tstamp ngtcp2_conn_get_timestamp(const ngtcp2_conn *conn); ++ + /** + * @function + * +@@ -5650,15 +6198,19 @@ NGTCP2_EXTERN void ngtcp2_path_storage_zero(ngtcp2_path_storage *ps); + * values. First this function fills |settings| with 0, and set the + * default value to the following fields: + * +- * * :type:`cc_algo ` = ++ * * :member:`cc_algo ` = + * :enum:`ngtcp2_cc_algo.NGTCP2_CC_ALGO_CUBIC` +- * * :type:`initial_rtt ` = ++ * * :member:`initial_rtt ` = + * :macro:`NGTCP2_DEFAULT_INITIAL_RTT` +- * * :type:`ack_thresh ` = 2 +- * * :type:`max_tx_udp_payload_size ++ * * :member:`ack_thresh ` = 2 ++ * * :member:`max_tx_udp_payload_size + * ` = 1452 +- * * :type:`handshake_timeout ` = ++ * * :member:`handshake_timeout ` = + * ``UINT64_MAX`` ++ * * :member:`glitch_ratelim_burst ++ * ` = 10000 ++ * * :member:`glitch_ratelim_rate ++ * ` = 330 + */ + NGTCP2_EXTERN void ngtcp2_settings_default_versioned(int settings_version, + ngtcp2_settings *settings); +@@ -5670,15 +6222,15 @@ NGTCP2_EXTERN void ngtcp2_settings_default_versioned(int settings_version, + * default values. First this function fills |params| with 0, and set + * the default value to the following fields: + * +- * * :type:`max_udp_payload_size ++ * * :member:`max_udp_payload_size + * ` = + * :macro:`NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE` +- * * :type:`ack_delay_exponent ++ * * :member:`ack_delay_exponent + * ` = + * :macro:`NGTCP2_DEFAULT_ACK_DELAY_EXPONENT` +- * * :type:`max_ack_delay ` = ++ * * :member:`max_ack_delay ` = + * :macro:`NGTCP2_DEFAULT_MAX_ACK_DELAY` +- * * :type:`active_connection_id_limit ++ * * :member:`active_connection_id_limit + * ` = + * :macro:`NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT` + */ +@@ -5950,6 +6502,28 @@ NGTCP2_EXTERN uint32_t ngtcp2_select_version(const uint32_t *preferred_versions, + #define ngtcp2_conn_get_conn_info(CONN, CINFO) \ + ngtcp2_conn_get_conn_info_versioned((CONN), NGTCP2_CONN_INFO_VERSION, (CINFO)) + ++/* ++ * `ngtcp2_conn_write_aggregate_pkt` is a wrapper around ++ * `ngtcp2_conn_write_aggregate_pkt_versioned` to set the correct ++ * struct version. ++ */ ++#define ngtcp2_conn_write_aggregate_pkt(CONN, PATH, PI, BUF, BUFLEN, PGSOLEN, \ ++ WRITE_PKT, TS) \ ++ ngtcp2_conn_write_aggregate_pkt_versioned( \ ++ (CONN), (PATH), NGTCP2_PKT_INFO_VERSION, (PI), (BUF), (BUFLEN), (PGSOLEN), \ ++ (WRITE_PKT), (TS)) ++ ++/* ++ * `ngtcp2_conn_write_aggregate_pkt2` is a wrapper around ++ * `ngtcp2_conn_write_aggregate_pkt2_versioned` to set the correct ++ * struct version. ++ */ ++#define ngtcp2_conn_write_aggregate_pkt2(CONN, PATH, PI, BUF, BUFLEN, PGSOLEN, \ ++ WRITE_PKT, NUM_PKTS, TS) \ ++ ngtcp2_conn_write_aggregate_pkt2_versioned( \ ++ (CONN), (PATH), NGTCP2_PKT_INFO_VERSION, (PI), (BUF), (BUFLEN), (PGSOLEN), \ ++ (WRITE_PKT), (NUM_PKTS), (TS)) ++ + /* + * `ngtcp2_settings_default` is a wrapper around + * `ngtcp2_settings_default_versioned` to set the correct struct +diff --git a/third_party/ngtcp2/lib/ngtcp2_acktr.c b/third_party/ngtcp2/lib/ngtcp2_acktr.c +index 776dc0c2c3e..4a9ac5cf0aa 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_acktr.c ++++ b/third_party/ngtcp2/lib/ngtcp2_acktr.c +@@ -34,9 +34,11 @@ ngtcp2_objalloc_def(acktr_entry, ngtcp2_acktr_entry, oplent) + + static void acktr_entry_init(ngtcp2_acktr_entry *ent, int64_t pkt_num, + ngtcp2_tstamp tstamp) { +- ent->pkt_num = pkt_num; +- ent->len = 1; +- ent->tstamp = tstamp; ++ *ent = (ngtcp2_acktr_entry){ ++ .pkt_num = pkt_num, ++ .len = 1, ++ .tstamp = tstamp, ++ }; + } + + int ngtcp2_acktr_entry_objalloc_new(ngtcp2_acktr_entry **ent, int64_t pkt_num, +@@ -219,8 +221,10 @@ ngtcp2_acktr_ack_entry *ngtcp2_acktr_add_ack(ngtcp2_acktr *acktr, + int64_t largest_ack) { + ngtcp2_acktr_ack_entry *ent = ngtcp2_ringbuf_push_front(&acktr->acks.rb); + +- ent->largest_ack = largest_ack; +- ent->pkt_num = pkt_num; ++ *ent = (ngtcp2_acktr_ack_entry){ ++ .largest_ack = largest_ack, ++ .pkt_num = pkt_num, ++ }; + + return ent; + } +@@ -316,9 +320,9 @@ void ngtcp2_acktr_recv_ack(ngtcp2_acktr *acktr, const ngtcp2_ack *fr) { + } + + void ngtcp2_acktr_commit_ack(ngtcp2_acktr *acktr) { +- acktr->flags &= (uint16_t) ~(NGTCP2_ACKTR_FLAG_ACTIVE_ACK | +- NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK | +- NGTCP2_ACKTR_FLAG_CANCEL_TIMER); ++ acktr->flags &= ++ (uint16_t)~(NGTCP2_ACKTR_FLAG_ACTIVE_ACK | NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK | ++ NGTCP2_ACKTR_FLAG_CANCEL_TIMER); + acktr->first_unacked_ts = UINT64_MAX; + acktr->rx_npkt = 0; + } +@@ -333,16 +337,14 @@ void ngtcp2_acktr_immediate_ack(ngtcp2_acktr *acktr) { + acktr->flags |= NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK; + } + +-ngtcp2_frame *ngtcp2_acktr_create_ack_frame(ngtcp2_acktr *acktr, +- ngtcp2_frame *fr, uint8_t type, +- ngtcp2_tstamp ts, +- ngtcp2_duration ack_delay, +- uint64_t ack_delay_exponent) { ++int ngtcp2_acktr_create_ack_frame(ngtcp2_acktr *acktr, ngtcp2_ack *ack, ++ uint8_t type, ngtcp2_tstamp ts, ++ ngtcp2_duration ack_delay, ++ uint64_t ack_delay_exponent) { + int64_t last_pkt_num; + ngtcp2_ack_range *range; + ngtcp2_ksl_it it; + ngtcp2_acktr_entry *rpkt; +- ngtcp2_ack *ack = &fr->ack; + ngtcp2_tstamp largest_ack_ts; + size_t num_acks; + +@@ -351,13 +353,13 @@ ngtcp2_frame *ngtcp2_acktr_create_ack_frame(ngtcp2_acktr *acktr, + } + + if (!ngtcp2_acktr_require_active_ack(acktr, ack_delay, ts)) { +- return NULL; ++ return -1; + } + + it = ngtcp2_acktr_get(acktr); + if (ngtcp2_ksl_it_end(&it)) { + ngtcp2_acktr_commit_ack(acktr); +- return NULL; ++ return -1; + } + + num_acks = ngtcp2_ksl_len(&acktr->ents); +@@ -420,7 +422,7 @@ ngtcp2_frame *ngtcp2_acktr_create_ack_frame(ngtcp2_acktr *acktr, + last_pkt_num = rpkt->pkt_num - (int64_t)(rpkt->len - 1); + } + +- return fr; ++ return 0; + } + + void ngtcp2_acktr_increase_ecn_counts(ngtcp2_acktr *acktr, +diff --git a/third_party/ngtcp2/lib/ngtcp2_acktr.h b/third_party/ngtcp2/lib/ngtcp2_acktr.h +index cf75a774db3..026c2cabe87 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_acktr.h ++++ b/third_party/ngtcp2/lib/ngtcp2_acktr.h +@@ -97,16 +97,16 @@ typedef struct ngtcp2_acktr_ack_entry { + } ngtcp2_acktr_ack_entry; + + /* NGTCP2_ACKTR_FLAG_NONE indicates that no flag set. */ +-#define NGTCP2_ACKTR_FLAG_NONE 0x00u ++#define NGTCP2_ACKTR_FLAG_NONE 0x00U + /* NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK indicates that immediate + acknowledgement is required. */ +-#define NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK 0x01u ++#define NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK 0x01U + /* NGTCP2_ACKTR_FLAG_ACTIVE_ACK indicates that there are pending + protected packet to be acknowledged. */ +-#define NGTCP2_ACKTR_FLAG_ACTIVE_ACK 0x02u ++#define NGTCP2_ACKTR_FLAG_ACTIVE_ACK 0x02U + /* NGTCP2_ACKTR_FLAG_CANCEL_TIMER is set when ACK delay timer is + expired and canceled. */ +-#define NGTCP2_ACKTR_FLAG_CANCEL_TIMER 0x0100u ++#define NGTCP2_ACKTR_FLAG_CANCEL_TIMER 0x0100U + + ngtcp2_static_ringbuf_def(acks, 32, sizeof(ngtcp2_acktr_ack_entry)) + +@@ -120,8 +120,6 @@ typedef struct ngtcp2_acktr { + packet number. */ + ngtcp2_ksl ents; + ngtcp2_log *log; +- /* flags is bitwise OR of zero, or more of NGTCP2_ACKTR_FLAG_*. */ +- uint16_t flags; + /* first_unacked_ts is timestamp when ngtcp2_acktr_entry is added + first time after the last outgoing ACK frame. */ + ngtcp2_tstamp first_unacked_ts; +@@ -148,6 +146,9 @@ typedef struct ngtcp2_acktr { + uint64_t ce; + } ack; + } ecn; ++ ++ /* flags is bitwise OR of zero, or more of NGTCP2_ACKTR_FLAG_*. */ ++ uint16_t flags; + } ngtcp2_acktr; + + /* +@@ -235,19 +236,18 @@ void ngtcp2_acktr_immediate_ack(ngtcp2_acktr *acktr); + + /* + * ngtcp2_acktr_create_ack_frame creates ACK frame in the object +- * pointed by |fr|, and returns |fr| if there are any received packets +- * to acknowledge. If there are no packets to acknowledge, this +- * function returns NULL. fr->ack.ranges must be able to contain at ++ * pointed by |ack|, and returns 0 if it successfully creates ACK ++ * frame in |ack|. If there are no packets to acknowledge, this ++ * function returns -1. |ack|->ranges must be able to contain at + * least NGTCP2_MAX_ACK_RANGES elements. + * + * Call ngtcp2_acktr_commit_ack after a created ACK frame is + * successfully serialized into a packet. + */ +-ngtcp2_frame *ngtcp2_acktr_create_ack_frame(ngtcp2_acktr *acktr, +- ngtcp2_frame *fr, uint8_t type, +- ngtcp2_tstamp ts, +- ngtcp2_duration ack_delay, +- uint64_t ack_delay_exponent); ++int ngtcp2_acktr_create_ack_frame(ngtcp2_acktr *acktr, ngtcp2_ack *ack, ++ uint8_t type, ngtcp2_tstamp ts, ++ ngtcp2_duration ack_delay, ++ uint64_t ack_delay_exponent); + + /* + * ngtcp2_acktr_increase_ecn_counts increases ECN counts from |pi|. +diff --git a/third_party/ngtcp2/lib/ngtcp2_addr.c b/third_party/ngtcp2/lib/ngtcp2_addr.c +index 1fb273d494e..58694e3836e 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_addr.c ++++ b/third_party/ngtcp2/lib/ngtcp2_addr.c +@@ -31,8 +31,11 @@ + + ngtcp2_addr *ngtcp2_addr_init(ngtcp2_addr *dest, const ngtcp2_sockaddr *addr, + ngtcp2_socklen addrlen) { +- dest->addrlen = addrlen; +- dest->addr = (ngtcp2_sockaddr *)addr; ++ *dest = (ngtcp2_addr){ ++ .addr = (ngtcp2_sockaddr *)addr, ++ .addrlen = addrlen, ++ }; ++ + return dest; + } + +@@ -58,14 +61,12 @@ int ngtcp2_sockaddr_eq(const ngtcp2_sockaddr *a, const ngtcp2_sockaddr *b) { + + switch (a->sa_family) { + case NGTCP2_AF_INET: { +- const ngtcp2_sockaddr_in *ai = (const ngtcp2_sockaddr_in *)(void *)a, +- *bi = (const ngtcp2_sockaddr_in *)(void *)b; ++ const ngtcp2_sockaddr_in *ai = (void *)a, *bi = (void *)b; + return ai->sin_port == bi->sin_port && + memcmp(&ai->sin_addr, &bi->sin_addr, sizeof(ai->sin_addr)) == 0; + } + case NGTCP2_AF_INET6: { +- const ngtcp2_sockaddr_in6 *ai = (const ngtcp2_sockaddr_in6 *)(void *)a, +- *bi = (const ngtcp2_sockaddr_in6 *)(void *)b; ++ const ngtcp2_sockaddr_in6 *ai = (void *)a, *bi = (void *)b; + return ai->sin6_port == bi->sin6_port && + memcmp(&ai->sin6_addr, &bi->sin6_addr, sizeof(ai->sin6_addr)) == 0; + } +@@ -89,8 +90,7 @@ uint32_t ngtcp2_addr_cmp(const ngtcp2_addr *aa, const ngtcp2_addr *bb) { + + switch (a->sa_family) { + case NGTCP2_AF_INET: { +- const ngtcp2_sockaddr_in *ai = (const ngtcp2_sockaddr_in *)(void *)a, +- *bi = (const ngtcp2_sockaddr_in *)(void *)b; ++ const ngtcp2_sockaddr_in *ai = (void *)a, *bi = (void *)b; + if (memcmp(&ai->sin_addr, &bi->sin_addr, sizeof(ai->sin_addr))) { + flags |= NGTCP2_ADDR_CMP_FLAG_ADDR; + } +@@ -100,8 +100,7 @@ uint32_t ngtcp2_addr_cmp(const ngtcp2_addr *aa, const ngtcp2_addr *bb) { + return flags; + } + case NGTCP2_AF_INET6: { +- const ngtcp2_sockaddr_in6 *ai = (const ngtcp2_sockaddr_in6 *)(void *)a, +- *bi = (const ngtcp2_sockaddr_in6 *)(void *)b; ++ const ngtcp2_sockaddr_in6 *ai = (void *)a, *bi = (void *)b; + if (memcmp(&ai->sin6_addr, &bi->sin6_addr, sizeof(ai->sin6_addr))) { + flags |= NGTCP2_ADDR_CMP_FLAG_ADDR; + } +diff --git a/third_party/ngtcp2/lib/ngtcp2_addr.h b/third_party/ngtcp2/lib/ngtcp2_addr.h +index c2224f85cd9..6314b1afc81 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_addr.h ++++ b/third_party/ngtcp2/lib/ngtcp2_addr.h +@@ -46,14 +46,14 @@ void ngtcp2_addr_copy(ngtcp2_addr *dest, const ngtcp2_addr *src); + int ngtcp2_addr_eq(const ngtcp2_addr *a, const ngtcp2_addr *b); + + /* NGTCP2_ADDR_CMP_FLAG_NONE indicates that no flag set. */ +-#define NGTCP2_ADDR_CMP_FLAG_NONE 0x0u ++#define NGTCP2_ADDR_CMP_FLAG_NONE 0x0U + /* NGTCP2_ADDR_CMP_FLAG_ADDR indicates IP addresses do not match. */ +-#define NGTCP2_ADDR_CMP_FLAG_ADDR 0x1u ++#define NGTCP2_ADDR_CMP_FLAG_ADDR 0x1U + /* NGTCP2_ADDR_CMP_FLAG_PORT indicates ports do not match. */ +-#define NGTCP2_ADDR_CMP_FLAG_PORT 0x2u ++#define NGTCP2_ADDR_CMP_FLAG_PORT 0x2U + /* NGTCP2_ADDR_CMP_FLAG_FAMILY indicates address families do not + match. */ +-#define NGTCP2_ADDR_CMP_FLAG_FAMILY 0x4u ++#define NGTCP2_ADDR_CMP_FLAG_FAMILY 0x4U + + /* + * ngtcp2_addr_cmp compares address and port between |a| and |b|, and +diff --git a/third_party/ngtcp2/lib/ngtcp2_balloc.c b/third_party/ngtcp2/lib/ngtcp2_balloc.c +index 4a6797689fc..fff7a9ef65b 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_balloc.c ++++ b/third_party/ngtcp2/lib/ngtcp2_balloc.c +@@ -30,7 +30,7 @@ + + void ngtcp2_balloc_init(ngtcp2_balloc *balloc, size_t blklen, + const ngtcp2_mem *mem) { +- assert((blklen & 0xfu) == 0); ++ assert((blklen & 0xFU) == 0); + + balloc->mem = mem; + balloc->blklen = blklen; +@@ -66,25 +66,25 @@ int ngtcp2_balloc_get(ngtcp2_balloc *balloc, void **pbuf, size_t n) { + + if (ngtcp2_buf_left(&balloc->buf) < n) { + p = ngtcp2_mem_malloc(balloc->mem, +- sizeof(ngtcp2_memblock_hd) + 0x8u + balloc->blklen); ++ sizeof(ngtcp2_memblock_hd) + 0x8U + balloc->blklen); + if (p == NULL) { + return NGTCP2_ERR_NOMEM; + } + +- hd = (ngtcp2_memblock_hd *)(void *)p; ++ hd = (void *)p; + hd->next = balloc->head; + balloc->head = hd; + ngtcp2_buf_init( + &balloc->buf, +- (uint8_t *)(((uintptr_t)p + sizeof(ngtcp2_memblock_hd) + 0xfu) & +- ~(uintptr_t)0xfu), ++ (uint8_t *)(((uintptr_t)p + sizeof(ngtcp2_memblock_hd) + 0xFU) & ++ ~(uintptr_t)0xFU), + balloc->blklen); + } + +- assert(((uintptr_t)balloc->buf.last & 0xfu) == 0); ++ assert(((uintptr_t)balloc->buf.last & 0xFU) == 0); + + *pbuf = balloc->buf.last; +- balloc->buf.last += (n + 0xfu) & ~(uintptr_t)0xfu; ++ balloc->buf.last += (n + 0xFU) & ~(uintptr_t)0xFU; + + return 0; + } +diff --git a/third_party/ngtcp2/lib/ngtcp2_bbr.c b/third_party/ngtcp2/lib/ngtcp2_bbr.c +index 04612f11be4..3db6a9f2bff 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_bbr.c ++++ b/third_party/ngtcp2/lib/ngtcp2_bbr.c +@@ -33,6 +33,7 @@ + #include "ngtcp2_rcvry.h" + #include "ngtcp2_rst.h" + #include "ngtcp2_conn_stat.h" ++#include "ngtcp2_pcg.h" + + #define NGTCP2_BBR_MAX_BW_FILTERLEN 2 + +@@ -69,19 +70,21 @@ static void bbr_on_transmit(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + + static void bbr_reset_congestion_signals(ngtcp2_cc_bbr *bbr); + +-static void bbr_reset_lower_bounds(ngtcp2_cc_bbr *bbr); ++static void bbr_reset_shortterm_model(ngtcp2_cc_bbr *bbr); + + static void bbr_init_round_counting(ngtcp2_cc_bbr *bbr); + + static void bbr_reset_full_bw(ngtcp2_cc_bbr *bbr); + +-static void bbr_init_pacing_rate(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); ++static void bbr_init_pacing_rate(const ngtcp2_cc_bbr *bbr, ++ ngtcp2_conn_stat *cstat); + +-static void bbr_set_pacing_rate_with_gain(ngtcp2_cc_bbr *bbr, ++static void bbr_set_pacing_rate_with_gain(const ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, + uint64_t pacing_gain_h); + +-static void bbr_set_pacing_rate(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); ++static void bbr_set_pacing_rate(const ngtcp2_cc_bbr *bbr, ++ ngtcp2_conn_stat *cstat); + + static void bbr_enter_startup(ngtcp2_cc_bbr *bbr); + +@@ -99,47 +102,47 @@ static void bbr_update_control_parameters(ngtcp2_cc_bbr *cc, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack); + +-static void bbr_update_on_loss(ngtcp2_cc_bbr *cc, ngtcp2_conn_stat *cstat, +- const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts); +- + static void bbr_update_latest_delivery_signals(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat); ++ const ngtcp2_conn_stat *cstat); + + static void bbr_advance_latest_delivery_signals(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat); ++ const ngtcp2_conn_stat *cstat); + + static void bbr_update_congestion_signals(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat, ++ const ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack); + +-static void bbr_adapt_lower_bounds_from_congestion(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat); ++static void ++bbr_adapt_lower_bounds_from_congestion(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat); + +-static void bbr_init_lower_bounds(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); ++static void bbr_init_lower_bounds(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat); + + static void bbr_loss_lower_bounds(ngtcp2_cc_bbr *bbr); + + static void bbr_bound_bw_for_model(ngtcp2_cc_bbr *bbr); + +-static void bbr_update_max_bw(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, ++static void bbr_update_max_bw(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack); + + static void bbr_update_round(ngtcp2_cc_bbr *bbr, const ngtcp2_cc_ack *ack); + + static void bbr_start_round(ngtcp2_cc_bbr *bbr); + +-static int bbr_is_in_probe_bw_state(ngtcp2_cc_bbr *bbr); ++static int bbr_is_in_probe_bw_state(const ngtcp2_cc_bbr *bbr); + +-static int bbr_is_probing_bw(ngtcp2_cc_bbr *bbr); ++static int bbr_is_probing_bw(const ngtcp2_cc_bbr *bbr); + + static void bbr_update_ack_aggregation(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat, ++ const ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts); + + static void bbr_enter_drain(ngtcp2_cc_bbr *bbr); + +-static void bbr_check_drain_done(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, ++static void bbr_check_drain_done(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + + static void bbr_enter_probe_bw(ngtcp2_cc_bbr *bbr, ngtcp2_tstamp ts); +@@ -150,59 +153,70 @@ static void bbr_start_probe_bw_cruise(ngtcp2_cc_bbr *bbr); + + static void bbr_start_probe_bw_refill(ngtcp2_cc_bbr *bbr); + +-static void bbr_start_probe_bw_up(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); ++static void bbr_start_probe_bw_up(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat); + + static void bbr_update_probe_bw_cycle_phase(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat, ++ const ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts); + +-static int bbr_is_time_to_cruise(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, +- ngtcp2_tstamp ts); ++static int bbr_is_time_to_cruise(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat); + +-static int bbr_is_time_to_go_down(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); ++static int bbr_is_time_to_go_down(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat); + +-static int bbr_has_elapsed_in_phase(ngtcp2_cc_bbr *bbr, ++static int bbr_has_elapsed_in_phase(const ngtcp2_cc_bbr *bbr, + ngtcp2_duration interval, ngtcp2_tstamp ts); + +-static uint64_t bbr_inflight_with_headroom(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat); ++static uint64_t bbr_inflight_with_headroom(const ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat); + + static void bbr_raise_inflight_longterm_slope(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat); ++ const ngtcp2_conn_stat *cstat); + + static void bbr_probe_inflight_longterm_upward(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat, ++ const ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack); + +-static void bbr_adapt_upper_bounds(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, +- const ngtcp2_cc_ack *ack); ++static void bbr_adapt_longterm_model(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat, ++ const ngtcp2_cc_ack *ack); + +-static int bbr_is_time_to_probe_bw(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, ++static int bbr_is_time_to_probe_bw(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + + static void bbr_pick_probe_wait(ngtcp2_cc_bbr *bbr); + +-static int bbr_is_reno_coexistence_probe_time(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat); ++static int bbr_is_reno_coexistence_probe_time(const ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat); + +-static uint64_t bbr_target_inflight(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat); ++static uint64_t bbr_target_inflight(const ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat); + +-static int bbr_is_inflight_too_high(ngtcp2_cc_bbr *bbr); ++static int bbr_is_inflight_too_high(const ngtcp2_cc_bbr *bbr, ++ const ngtcp2_rs *rs); + + static void bbr_handle_inflight_too_high(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat, +- ngtcp2_tstamp ts); ++ const ngtcp2_conn_stat *cstat, ++ const ngtcp2_rs *rs, ngtcp2_tstamp ts); + + static void bbr_note_loss(ngtcp2_cc_bbr *bbr); + +-static void bbr_handle_lost_packet(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, ++static void bbr_save_state_upon_loss(ngtcp2_cc_bbr *bbr); ++ ++static void bbr_handle_spurious_loss_detection(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat); ++ ++static void bbr_handle_lost_packet(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts); + +-static uint64_t +-bbr_inflight_longterm_from_lost_packet(ngtcp2_cc_bbr *bbr, +- const ngtcp2_cc_pkt *pkt); ++static uint64_t bbr_inflight_at_loss(const ngtcp2_cc_bbr *bbr, ++ const ngtcp2_cc_pkt *pkt, ++ const ngtcp2_rs *rs); + + static void bbr_update_min_rtt(ngtcp2_cc_bbr *bbr, const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts); +@@ -219,7 +233,7 @@ static void bbr_check_probe_rtt_done(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts); + + static void bbr_mark_connection_app_limited(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat); ++ const ngtcp2_conn_stat *cstat); + + static void bbr_exit_probe_rtt(ngtcp2_cc_bbr *bbr, ngtcp2_tstamp ts); + +@@ -230,27 +244,28 @@ static void bbr_handle_restart_from_idle(ngtcp2_cc_bbr *bbr, + static uint64_t bbr_bdp_multiple(ngtcp2_cc_bbr *bbr, uint64_t gain_h); + + static uint64_t bbr_quantization_budget(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat, ++ const ngtcp2_conn_stat *cstat, + uint64_t inflight); + +-static uint64_t bbr_inflight(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, ++static uint64_t bbr_inflight(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat, + uint64_t gain_h); + + static void bbr_update_max_inflight(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat); ++ const ngtcp2_conn_stat *cstat); + + static void bbr_update_offload_budget(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat); ++ const ngtcp2_conn_stat *cstat); + + static uint64_t min_pipe_cwnd(size_t max_udp_payload_size); + + static void bbr_advance_max_bw_filter(ngtcp2_cc_bbr *bbr); + +-static void bbr_save_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); ++static void bbr_save_cwnd(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat); + +-static void bbr_restore_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); ++static void bbr_restore_cwnd(const ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); + +-static uint64_t bbr_probe_rtt_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); ++static uint64_t bbr_probe_rtt_cwnd(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat); + + static void bbr_bound_cwnd_for_probe_rtt(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat); +@@ -258,10 +273,11 @@ static void bbr_bound_cwnd_for_probe_rtt(ngtcp2_cc_bbr *bbr, + static void bbr_set_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack); + +-static void bbr_bound_cwnd_for_model(ngtcp2_cc_bbr *bbr, ++static void bbr_bound_cwnd_for_model(const ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat); + +-static void bbr_set_send_quantum(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat); ++static void bbr_set_send_quantum(const ngtcp2_cc_bbr *bbr, ++ ngtcp2_conn_stat *cstat); + + static int in_congestion_recovery(const ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_time); +@@ -288,7 +304,7 @@ static void bbr_on_init(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + bbr->full_bw_reached = 0; + + bbr_reset_congestion_signals(bbr); +- bbr_reset_lower_bounds(bbr); ++ bbr_reset_shortterm_model(bbr); + bbr_init_round_counting(bbr); + bbr_reset_full_bw(bbr); + bbr_init_pacing_rate(bbr, cstat); +@@ -333,9 +349,12 @@ static void bbr_on_init(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + + bbr->max_inflight = 0; + +- bbr->congestion_recovery_start_ts = UINT64_MAX; +- + bbr->bdp = 0; ++ ++ bbr->undo_state = 0; ++ bbr->undo_bw_shortterm = 0; ++ bbr->undo_inflight_shortterm = 0; ++ bbr->undo_inflight_longterm = 0; + } + + static void bbr_reset_congestion_signals(ngtcp2_cc_bbr *bbr) { +@@ -344,7 +363,7 @@ static void bbr_reset_congestion_signals(ngtcp2_cc_bbr *bbr) { + bbr->inflight_latest = 0; + } + +-static void bbr_reset_lower_bounds(ngtcp2_cc_bbr *bbr) { ++static void bbr_reset_shortterm_model(ngtcp2_cc_bbr *bbr) { + bbr->bw_shortterm = UINT64_MAX; + bbr->inflight_shortterm = UINT64_MAX; + } +@@ -362,7 +381,7 @@ static void bbr_reset_full_bw(ngtcp2_cc_bbr *bbr) { + } + + static void bbr_check_full_bw_reached(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat) { ++ const ngtcp2_conn_stat *cstat) { + if (bbr->full_bw_now || !bbr->round_start || bbr->rst->rs.is_app_limited) { + return; + } +@@ -383,15 +402,16 @@ static void bbr_check_full_bw_reached(ngtcp2_cc_bbr *bbr, + + bbr->full_bw_reached = 1; + +- ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, +- "bbr reached full bandwidth, full_bw=%" PRIu64, bbr->full_bw); ++ ngtcp2_log_infof(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, ++ "bbr reached full bandwidth, full_bw=%" PRIu64, ++ bbr->full_bw); + } + + static void bbr_check_startup_high_loss(ngtcp2_cc_bbr *bbr) { + if (bbr->full_bw_reached || bbr->loss_events_in_round <= 6 || + (bbr->in_loss_recovery && + bbr->round_count <= bbr->round_count_at_recovery) || +- !bbr_is_inflight_too_high(bbr)) { ++ !bbr_is_inflight_too_high(bbr, &bbr->rst->rs)) { + return; + } + +@@ -400,15 +420,17 @@ static void bbr_check_startup_high_loss(ngtcp2_cc_bbr *bbr) { + bbr_bdp_multiple(bbr, bbr->cwnd_gain_h), bbr->inflight_latest); + } + +-static void bbr_init_pacing_rate(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { +- cstat->pacing_interval_m = ++static void bbr_init_pacing_rate(const ngtcp2_cc_bbr *bbr, ++ ngtcp2_conn_stat *cstat) { ++ cstat->pacing_interval_m = ngtcp2_max_uint64( + ((cstat->first_rtt_sample_ts == UINT64_MAX ? NGTCP2_MILLISECONDS + : cstat->smoothed_rtt) + << 10) * +- 100 / NGTCP2_BBR_STARTUP_PACING_GAIN_H / bbr->initial_cwnd; ++ 100 / NGTCP2_BBR_STARTUP_PACING_GAIN_H / bbr->initial_cwnd, ++ 1); + } + +-static void bbr_set_pacing_rate_with_gain(ngtcp2_cc_bbr *bbr, ++static void bbr_set_pacing_rate_with_gain(const ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat, + uint64_t pacing_gain_h) { + uint64_t interval_m; +@@ -426,7 +448,8 @@ static void bbr_set_pacing_rate_with_gain(ngtcp2_cc_bbr *bbr, + } + } + +-static void bbr_set_pacing_rate(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { ++static void bbr_set_pacing_rate(const ngtcp2_cc_bbr *bbr, ++ ngtcp2_conn_stat *cstat) { + bbr_set_pacing_rate_with_gain(bbr, cstat, bbr->pacing_gain_h); + } + +@@ -482,13 +505,8 @@ static void bbr_update_control_parameters(ngtcp2_cc_bbr *bbr, + bbr_set_cwnd(bbr, cstat, ack); + } + +-static void bbr_update_on_loss(ngtcp2_cc_bbr *cc, ngtcp2_conn_stat *cstat, +- const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts) { +- bbr_handle_lost_packet(cc, cstat, pkt, ts); +-} +- + static void bbr_update_latest_delivery_signals(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat) { ++ const ngtcp2_conn_stat *cstat) { + bbr->loss_round_start = 0; + bbr->bw_latest = ngtcp2_max_uint64(bbr->bw_latest, cstat->delivery_rate_sec); + bbr->inflight_latest = +@@ -501,7 +519,7 @@ static void bbr_update_latest_delivery_signals(ngtcp2_cc_bbr *bbr, + } + + static void bbr_advance_latest_delivery_signals(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat) { ++ const ngtcp2_conn_stat *cstat) { + if (bbr->loss_round_start) { + bbr->bw_latest = cstat->delivery_rate_sec; + bbr->inflight_latest = bbr->rst->rs.delivered; +@@ -509,7 +527,7 @@ static void bbr_advance_latest_delivery_signals(ngtcp2_cc_bbr *bbr, + } + + static void bbr_update_congestion_signals(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat, ++ const ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { + bbr_update_max_bw(bbr, cstat, ack); + +@@ -527,8 +545,9 @@ static void bbr_update_congestion_signals(ngtcp2_cc_bbr *bbr, + bbr->loss_in_round = 0; + } + +-static void bbr_adapt_lower_bounds_from_congestion(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat) { ++static void ++bbr_adapt_lower_bounds_from_congestion(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat) { + if (bbr_is_probing_bw(bbr)) { + return; + } +@@ -539,7 +558,8 @@ static void bbr_adapt_lower_bounds_from_congestion(ngtcp2_cc_bbr *bbr, + } + } + +-static void bbr_init_lower_bounds(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { ++static void bbr_init_lower_bounds(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat) { + if (bbr->bw_shortterm == UINT64_MAX) { + bbr->bw_shortterm = bbr->max_bw; + } +@@ -562,11 +582,12 @@ static void bbr_bound_bw_for_model(ngtcp2_cc_bbr *bbr) { + bbr->bw = ngtcp2_min_uint64(bbr->max_bw, bbr->bw_shortterm); + } + +-static void bbr_update_max_bw(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, ++static void bbr_update_max_bw(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { + bbr_update_round(bbr, ack); + +- if (cstat->delivery_rate_sec >= bbr->max_bw || !bbr->rst->rs.is_app_limited) { ++ if (cstat->delivery_rate_sec && (cstat->delivery_rate_sec >= bbr->max_bw || ++ !bbr->rst->rs.is_app_limited)) { + ngtcp2_window_filter_update(&bbr->max_bw_filter, cstat->delivery_rate_sec, + bbr->cycle_count); + +@@ -597,7 +618,7 @@ static void bbr_start_round(ngtcp2_cc_bbr *bbr) { + bbr->next_round_delivered = bbr->rst->delivered; + } + +-static int bbr_is_in_probe_bw_state(ngtcp2_cc_bbr *bbr) { ++static int bbr_is_in_probe_bw_state(const ngtcp2_cc_bbr *bbr) { + switch (bbr->state) { + case NGTCP2_BBR_STATE_PROBE_BW_DOWN: + case NGTCP2_BBR_STATE_PROBE_BW_CRUISE: +@@ -609,7 +630,7 @@ static int bbr_is_in_probe_bw_state(ngtcp2_cc_bbr *bbr) { + } + } + +-static int bbr_is_probing_bw(ngtcp2_cc_bbr *bbr) { ++static int bbr_is_probing_bw(const ngtcp2_cc_bbr *bbr) { + switch (bbr->state) { + case NGTCP2_BBR_STATE_STARTUP: + case NGTCP2_BBR_STATE_PROBE_BW_REFILL: +@@ -621,7 +642,7 @@ static int bbr_is_probing_bw(ngtcp2_cc_bbr *bbr) { + } + + static void bbr_update_ack_aggregation(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat, ++ const ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts) { + ngtcp2_duration interval = ts - bbr->extra_acked_interval_start; +@@ -663,7 +684,8 @@ static void bbr_enter_drain(ngtcp2_cc_bbr *bbr) { + bbr->cwnd_gain_h = NGTCP2_BBR_DEFAULT_CWND_GAIN_H; + } + +-static void bbr_check_drain_done(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, ++static void bbr_check_drain_done(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + if (bbr->state == NGTCP2_BBR_STATE_DRAIN && + cstat->bytes_in_flight <= bbr_inflight(bbr, cstat, 100)) { +@@ -707,7 +729,7 @@ static void bbr_start_probe_bw_refill(ngtcp2_cc_bbr *bbr) { + ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, + "bbr start ProbeBW_REFILL"); + +- bbr_reset_lower_bounds(bbr); ++ bbr_reset_shortterm_model(bbr); + + bbr->bw_probe_up_rounds = 0; + bbr->bw_probe_up_acks = 0; +@@ -720,7 +742,8 @@ static void bbr_start_probe_bw_refill(ngtcp2_cc_bbr *bbr) { + bbr->cwnd_gain_h = NGTCP2_BBR_DEFAULT_CWND_GAIN_H; + } + +-static void bbr_start_probe_bw_up(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { ++static void bbr_start_probe_bw_up(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat) { + ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, "bbr start ProbeBW_UP"); + + bbr->ack_phase = NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STARTING; +@@ -737,14 +760,14 @@ static void bbr_start_probe_bw_up(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { + } + + static void bbr_update_probe_bw_cycle_phase(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat, ++ const ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts) { + if (!bbr->full_bw_reached) { + return; + } + +- bbr_adapt_upper_bounds(bbr, cstat, ack); ++ bbr_adapt_longterm_model(bbr, cstat, ack); + + if (!bbr_is_in_probe_bw_state(bbr)) { + return; +@@ -756,7 +779,7 @@ static void bbr_update_probe_bw_cycle_phase(ngtcp2_cc_bbr *bbr, + return; + } + +- if (bbr_is_time_to_cruise(bbr, cstat, ts)) { ++ if (bbr_is_time_to_cruise(bbr, cstat)) { + bbr_start_probe_bw_cruise(bbr); + } + +@@ -785,40 +808,34 @@ static void bbr_update_probe_bw_cycle_phase(ngtcp2_cc_bbr *bbr, + } + } + +-static int bbr_is_time_to_cruise(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, +- ngtcp2_tstamp ts) { +- (void)ts; +- +- if (cstat->bytes_in_flight > bbr_inflight_with_headroom(bbr, cstat)) { +- return 0; +- } +- +- if (cstat->bytes_in_flight <= bbr_inflight(bbr, cstat, 100)) { +- return 1; +- } ++static int bbr_is_time_to_cruise(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat) { ++ uint64_t inflight = ngtcp2_min_uint64(bbr_inflight_with_headroom(bbr, cstat), ++ bbr_inflight(bbr, cstat, 100)); + +- return 0; ++ return cstat->bytes_in_flight <= inflight; + } + +-static int bbr_is_time_to_go_down(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { ++static int bbr_is_time_to_go_down(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat) { + if (bbr->rst->is_cwnd_limited && cstat->cwnd >= bbr->inflight_longterm) { + bbr_reset_full_bw(bbr); + bbr->full_bw = cstat->delivery_rate_sec; +- } else if (bbr->full_bw_now) { +- return 1; ++ ++ return 0; + } + +- return 0; ++ return bbr->full_bw_now; + } + +-static int bbr_has_elapsed_in_phase(ngtcp2_cc_bbr *bbr, ++static int bbr_has_elapsed_in_phase(const ngtcp2_cc_bbr *bbr, + ngtcp2_duration interval, + ngtcp2_tstamp ts) { + return ts > bbr->cycle_stamp + interval; + } + +-static uint64_t bbr_inflight_with_headroom(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat) { ++static uint64_t bbr_inflight_with_headroom(const ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat) { + uint64_t headroom; + uint64_t mpcwnd; + if (bbr->inflight_longterm == UINT64_MAX) { +@@ -839,7 +856,7 @@ static uint64_t bbr_inflight_with_headroom(ngtcp2_cc_bbr *bbr, + } + + static void bbr_raise_inflight_longterm_slope(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat) { ++ const ngtcp2_conn_stat *cstat) { + uint64_t growth_this_round = cstat->max_tx_udp_payload_size + << bbr->bw_probe_up_rounds; + +@@ -848,7 +865,7 @@ static void bbr_raise_inflight_longterm_slope(ngtcp2_cc_bbr *bbr, + } + + static void bbr_probe_inflight_longterm_upward(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat, ++ const ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { + uint64_t delta; + +@@ -871,8 +888,9 @@ static void bbr_probe_inflight_longterm_upward(ngtcp2_cc_bbr *bbr, + } + } + +-static void bbr_adapt_upper_bounds(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, +- const ngtcp2_cc_ack *ack) { ++static void bbr_adapt_longterm_model(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat, ++ const ngtcp2_cc_ack *ack) { + if (bbr->ack_phase == NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STARTING && + bbr->round_start) { + bbr->ack_phase = NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_FEEDBACK; +@@ -885,7 +903,7 @@ static void bbr_adapt_upper_bounds(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + } + } + +- if (!bbr_is_inflight_too_high(bbr)) { ++ if (!bbr_is_inflight_too_high(bbr, &bbr->rst->rs)) { + if (bbr->inflight_longterm == UINT64_MAX) { + return; + } +@@ -900,7 +918,8 @@ static void bbr_adapt_upper_bounds(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + } + } + +-static int bbr_is_time_to_probe_bw(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, ++static int bbr_is_time_to_probe_bw(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + if (bbr_has_elapsed_in_phase(bbr, bbr->bw_probe_wait, ts) || + bbr_is_reno_coexistence_probe_time(bbr, cstat)) { +@@ -913,41 +932,35 @@ static int bbr_is_time_to_probe_bw(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + } + + static void bbr_pick_probe_wait(ngtcp2_cc_bbr *bbr) { +- uint8_t rand; +- +- bbr->rand(&rand, 1, &bbr->rand_ctx); +- +- bbr->rounds_since_bw_probe = (uint64_t)(rand * 2 / 256); +- +- bbr->rand(&rand, 1, &bbr->rand_ctx); +- +- bbr->bw_probe_wait = 2 * NGTCP2_SECONDS + NGTCP2_SECONDS * rand / 255; ++ bbr->rounds_since_bw_probe = ngtcp2_pcg32_rand_n(bbr->pcg, 2); ++ bbr->bw_probe_wait = ++ 2 * NGTCP2_SECONDS + ngtcp2_pcg32_rand_n(bbr->pcg, NGTCP2_SECONDS + 1); + } + +-static int bbr_is_reno_coexistence_probe_time(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat) { ++static int bbr_is_reno_coexistence_probe_time(const ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat) { + uint64_t reno_rounds = + bbr_target_inflight(bbr, cstat) / cstat->max_tx_udp_payload_size; + + return bbr->rounds_since_bw_probe >= ngtcp2_min_uint64(reno_rounds, 63); + } + +-static uint64_t bbr_target_inflight(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat) { ++static uint64_t bbr_target_inflight(const ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat) { + return ngtcp2_min_uint64(bbr->bdp, cstat->cwnd); + } + +-static int bbr_is_inflight_too_high(ngtcp2_cc_bbr *bbr) { +- const ngtcp2_rs *rs = &bbr->rst->rs; ++static int bbr_is_inflight_too_high(const ngtcp2_cc_bbr *bbr, ++ const ngtcp2_rs *rs) { ++ (void)bbr; + return rs->lost * NGTCP2_BBR_LOSS_THRESH_DENOM > + rs->tx_in_flight * NGTCP2_BBR_LOSS_THRESH_NUMER; + } + + static void bbr_handle_inflight_too_high(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat, ++ const ngtcp2_conn_stat *cstat, ++ const ngtcp2_rs *rs, + ngtcp2_tstamp ts) { +- const ngtcp2_rs *rs = &bbr->rst->rs; +- + bbr->bw_probe_samples = 0; + + if (!rs->is_app_limited) { +@@ -964,14 +977,51 @@ static void bbr_handle_inflight_too_high(ngtcp2_cc_bbr *bbr, + static void bbr_note_loss(ngtcp2_cc_bbr *bbr) { + if (!bbr->loss_in_round) { + bbr->loss_round_delivered = bbr->rst->delivered; ++ bbr_save_state_upon_loss(bbr); + } + + bbr->loss_in_round = 1; + } + +-static void bbr_handle_lost_packet(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, ++static void bbr_save_state_upon_loss(ngtcp2_cc_bbr *bbr) { ++ bbr->undo_state = bbr->state; ++ bbr->undo_bw_shortterm = bbr->bw_shortterm; ++ bbr->undo_inflight_shortterm = bbr->inflight_shortterm; ++ bbr->undo_inflight_longterm = bbr->inflight_longterm; ++} ++ ++static void bbr_handle_spurious_loss_detection(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat) { ++ bbr->loss_in_round = 0; ++ ++ bbr_reset_full_bw(bbr); ++ ++ bbr->bw_shortterm = ++ ngtcp2_max_uint64(bbr->bw_shortterm, bbr->undo_bw_shortterm); ++ bbr->inflight_shortterm = ++ ngtcp2_max_uint64(bbr->inflight_shortterm, bbr->undo_inflight_shortterm); ++ bbr->inflight_longterm = ++ ngtcp2_max_uint64(bbr->inflight_longterm, bbr->undo_inflight_longterm); ++ ++ if (bbr->state != NGTCP2_BBR_STATE_PROBE_RTT && ++ bbr->state != bbr->undo_state) { ++ switch (bbr->undo_state) { ++ case NGTCP2_BBR_STATE_STARTUP: ++ bbr_enter_startup(bbr); ++ break; ++ case NGTCP2_BBR_STATE_PROBE_BW_UP: ++ bbr_start_probe_bw_up(bbr, cstat); ++ break; ++ default: ++ break; ++ } ++ } ++} ++ ++static void bbr_handle_lost_packet(ngtcp2_cc_bbr *bbr, ++ const ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts) { +- ngtcp2_rs *rs = &bbr->rst->rs; ++ ngtcp2_rs rs = {0}; + + bbr_note_loss(bbr); + +@@ -979,23 +1029,21 @@ static void bbr_handle_lost_packet(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + return; + } + +- rs->tx_in_flight = pkt->tx_in_flight; +- /* bbr->rst->lost is not incremented for pkt yet */ +- assert(bbr->rst->lost + pkt->pktlen >= pkt->lost); +- rs->lost = bbr->rst->lost + pkt->pktlen - pkt->lost; +- rs->is_app_limited = pkt->is_app_limited; ++ rs.tx_in_flight = pkt->tx_in_flight; ++ assert(bbr->rst->lost >= pkt->lost); ++ rs.lost = bbr->rst->lost - pkt->lost; ++ rs.is_app_limited = pkt->is_app_limited; + +- if (bbr_is_inflight_too_high(bbr)) { +- rs->tx_in_flight = bbr_inflight_longterm_from_lost_packet(bbr, pkt); ++ if (bbr_is_inflight_too_high(bbr, &rs)) { ++ rs.tx_in_flight = bbr_inflight_at_loss(bbr, pkt, &rs); + +- bbr_handle_inflight_too_high(bbr, cstat, ts); ++ bbr_handle_inflight_too_high(bbr, cstat, &rs, ts); + } + } + +-static uint64_t +-bbr_inflight_longterm_from_lost_packet(ngtcp2_cc_bbr *bbr, +- const ngtcp2_cc_pkt *pkt) { +- ngtcp2_rs *rs = &bbr->rst->rs; ++static uint64_t bbr_inflight_at_loss(const ngtcp2_cc_bbr *bbr, ++ const ngtcp2_cc_pkt *pkt, ++ const ngtcp2_rs *rs) { + uint64_t inflight_prev, lost_prev, lost_prefix; + (void)bbr; + +@@ -1038,8 +1086,8 @@ static void bbr_update_min_rtt(ngtcp2_cc_bbr *bbr, const ngtcp2_cc_ack *ack, + bbr->min_rtt = bbr->probe_rtt_min_delay; + bbr->min_rtt_stamp = bbr->probe_rtt_min_stamp; + +- ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, +- "bbr update min_rtt=%" PRIu64, bbr->min_rtt); ++ ngtcp2_log_infof(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, ++ "bbr update min_rtt=%" PRIu64, bbr->min_rtt); + } + } + +@@ -1110,18 +1158,13 @@ static void bbr_check_probe_rtt_done(ngtcp2_cc_bbr *bbr, + } + + static void bbr_mark_connection_app_limited(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat) { +- uint64_t app_limited = bbr->rst->delivered + cstat->bytes_in_flight; +- +- if (app_limited) { +- bbr->rst->app_limited = app_limited; +- } else { +- bbr->rst->app_limited = cstat->max_tx_udp_payload_size; +- } ++ const ngtcp2_conn_stat *cstat) { ++ bbr->rst->app_limited = ++ ngtcp2_max_uint64(bbr->rst->delivered + cstat->bytes_in_flight, 1); + } + + static void bbr_exit_probe_rtt(ngtcp2_cc_bbr *bbr, ngtcp2_tstamp ts) { +- bbr_reset_lower_bounds(bbr); ++ bbr_reset_shortterm_model(bbr); + + if (bbr->full_bw_reached) { + bbr_start_probe_bw_down(bbr, ts); +@@ -1163,7 +1206,7 @@ static uint64_t min_pipe_cwnd(size_t max_udp_payload_size) { + } + + static uint64_t bbr_quantization_budget(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat, ++ const ngtcp2_conn_stat *cstat, + uint64_t inflight) { + bbr_update_offload_budget(bbr, cstat); + +@@ -1178,7 +1221,7 @@ static uint64_t bbr_quantization_budget(ngtcp2_cc_bbr *bbr, + return inflight; + } + +-static uint64_t bbr_inflight(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, ++static uint64_t bbr_inflight(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat, + uint64_t gain_h) { + uint64_t inflight = bbr_bdp_multiple(bbr, gain_h); + +@@ -1186,7 +1229,7 @@ static uint64_t bbr_inflight(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + } + + static void bbr_update_max_inflight(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat) { ++ const ngtcp2_conn_stat *cstat) { + uint64_t inflight; + + inflight = bbr_bdp_multiple(bbr, bbr->cwnd_gain_h) + bbr->extra_acked; +@@ -1194,7 +1237,7 @@ static void bbr_update_max_inflight(ngtcp2_cc_bbr *bbr, + } + + static void bbr_update_offload_budget(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat) { ++ const ngtcp2_conn_stat *cstat) { + bbr->offload_budget = 3 * cstat->send_quantum; + } + +@@ -1202,7 +1245,7 @@ static void bbr_advance_max_bw_filter(ngtcp2_cc_bbr *bbr) { + ++bbr->cycle_count; + } + +-static void bbr_save_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { ++static void bbr_save_cwnd(ngtcp2_cc_bbr *bbr, const ngtcp2_conn_stat *cstat) { + if (!bbr->in_loss_recovery && bbr->state != NGTCP2_BBR_STATE_PROBE_RTT) { + bbr->prior_cwnd = cstat->cwnd; + return; +@@ -1211,12 +1254,13 @@ static void bbr_save_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { + bbr->prior_cwnd = ngtcp2_max_uint64(bbr->prior_cwnd, cstat->cwnd); + } + +-static void bbr_restore_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { ++static void bbr_restore_cwnd(const ngtcp2_cc_bbr *bbr, ++ ngtcp2_conn_stat *cstat) { + cstat->cwnd = ngtcp2_max_uint64(cstat->cwnd, bbr->prior_cwnd); + } + + static uint64_t bbr_probe_rtt_cwnd(ngtcp2_cc_bbr *bbr, +- ngtcp2_conn_stat *cstat) { ++ const ngtcp2_conn_stat *cstat) { + uint64_t probe_rtt_cwnd = + bbr_bdp_multiple(bbr, NGTCP2_BBR_PROBE_RTT_CWND_GAIN_H); + uint64_t mpcwnd = min_pipe_cwnd(cstat->max_tx_udp_payload_size); +@@ -1226,12 +1270,9 @@ static uint64_t bbr_probe_rtt_cwnd(ngtcp2_cc_bbr *bbr, + + static void bbr_bound_cwnd_for_probe_rtt(ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat) { +- uint64_t probe_rtt_cwnd; +- + if (bbr->state == NGTCP2_BBR_STATE_PROBE_RTT) { +- probe_rtt_cwnd = bbr_probe_rtt_cwnd(bbr, cstat); +- +- cstat->cwnd = ngtcp2_min_uint64(cstat->cwnd, probe_rtt_cwnd); ++ cstat->cwnd = ++ ngtcp2_min_uint64(cstat->cwnd, bbr_probe_rtt_cwnd(bbr, cstat)); + } + } + +@@ -1256,7 +1297,7 @@ static void bbr_set_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + bbr_bound_cwnd_for_model(bbr, cstat); + } + +-static void bbr_bound_cwnd_for_model(ngtcp2_cc_bbr *bbr, ++static void bbr_bound_cwnd_for_model(const ngtcp2_cc_bbr *bbr, + ngtcp2_conn_stat *cstat) { + uint64_t cap = UINT64_MAX; + uint64_t mpcwnd = min_pipe_cwnd(cstat->max_tx_udp_payload_size); +@@ -1275,7 +1316,8 @@ static void bbr_bound_cwnd_for_model(ngtcp2_cc_bbr *bbr, + cstat->cwnd = ngtcp2_min_uint64(cstat->cwnd, cap); + } + +-static void bbr_set_send_quantum(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) { ++static void bbr_set_send_quantum(const ngtcp2_cc_bbr *bbr, ++ ngtcp2_conn_stat *cstat) { + size_t send_quantum = 64 * 1024; + (void)bbr; + +@@ -1295,28 +1337,15 @@ static int in_congestion_recovery(const ngtcp2_conn_stat *cstat, + + static void bbr_handle_recovery(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { +- if (bbr->in_loss_recovery) { +- if (ack->largest_pkt_sent_ts != UINT64_MAX && +- !in_congestion_recovery(cstat, ack->largest_pkt_sent_ts)) { +- bbr->in_loss_recovery = 0; +- bbr->round_count_at_recovery = UINT64_MAX; +- bbr_restore_cwnd(bbr, cstat); +- } +- ++ if (!bbr->in_loss_recovery) { + return; + } + +- if (bbr->congestion_recovery_start_ts != UINT64_MAX) { +- bbr->in_loss_recovery = 1; +- bbr->round_count_at_recovery = +- bbr->round_start ? bbr->round_count : bbr->round_count + 1; +- bbr_save_cwnd(bbr, cstat); +- cstat->cwnd = +- cstat->bytes_in_flight + +- ngtcp2_max_uint64(ack->bytes_delivered, cstat->max_tx_udp_payload_size); +- +- cstat->congestion_recovery_start_ts = bbr->congestion_recovery_start_ts; +- bbr->congestion_recovery_start_ts = UINT64_MAX; ++ if (ack->largest_pkt_sent_ts != UINT64_MAX && ++ !in_congestion_recovery(cstat, ack->largest_pkt_sent_ts)) { ++ bbr->in_loss_recovery = 0; ++ bbr->round_count_at_recovery = UINT64_MAX; ++ bbr_restore_cwnd(bbr, cstat); + } + } + +@@ -1324,22 +1353,26 @@ static void bbr_cc_on_pkt_lost(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts) { + ngtcp2_cc_bbr *bbr = ngtcp2_struct_of(cc, ngtcp2_cc_bbr, cc); + +- bbr_update_on_loss(bbr, cstat, pkt, ts); ++ bbr_handle_lost_packet(bbr, cstat, pkt, ts); + } + + static void bbr_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, +- ngtcp2_tstamp sent_ts, uint64_t bytes_lost, ++ ngtcp2_tstamp sent_ts, ++ const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts) { + ngtcp2_cc_bbr *bbr = ngtcp2_struct_of(cc, ngtcp2_cc_bbr, cc); +- (void)bytes_lost; ++ (void)ack; + +- if (bbr->in_loss_recovery || +- bbr->congestion_recovery_start_ts != UINT64_MAX || +- in_congestion_recovery(cstat, sent_ts)) { ++ if (bbr->in_loss_recovery || in_congestion_recovery(cstat, sent_ts)) { + return; + } + +- bbr->congestion_recovery_start_ts = ts; ++ bbr->in_loss_recovery = 1; ++ bbr->round_count_at_recovery = ++ bbr->round_start ? bbr->round_count : bbr->round_count + 1; ++ bbr_save_cwnd(bbr, cstat); ++ ++ cstat->congestion_recovery_start_ts = ts; + } + + static void bbr_cc_on_spurious_congestion(ngtcp2_cc *cc, +@@ -1348,14 +1381,13 @@ static void bbr_cc_on_spurious_congestion(ngtcp2_cc *cc, + ngtcp2_cc_bbr *bbr = ngtcp2_struct_of(cc, ngtcp2_cc_bbr, cc); + (void)ts; + +- bbr->congestion_recovery_start_ts = UINT64_MAX; + cstat->congestion_recovery_start_ts = UINT64_MAX; + +- if (bbr->in_loss_recovery) { +- bbr->in_loss_recovery = 0; +- bbr->round_count_at_recovery = UINT64_MAX; +- bbr_restore_cwnd(bbr, cstat); +- } ++ bbr->in_loss_recovery = 0; ++ bbr->round_count_at_recovery = UINT64_MAX; ++ ++ bbr_restore_cwnd(bbr, cstat); ++ bbr_handle_spurious_loss_detection(bbr, cstat); + } + + static void bbr_cc_on_persistent_congestion(ngtcp2_cc *cc, +@@ -1365,7 +1397,6 @@ static void bbr_cc_on_persistent_congestion(ngtcp2_cc *cc, + (void)ts; + + cstat->congestion_recovery_start_ts = UINT64_MAX; +- bbr->congestion_recovery_start_ts = UINT64_MAX; + bbr->in_loss_recovery = 0; + bbr->round_count_at_recovery = UINT64_MAX; + +@@ -1380,6 +1411,11 @@ static void bbr_cc_on_ack_recv(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_cc_bbr *bbr = ngtcp2_struct_of(cc, ngtcp2_cc_bbr, cc); + + bbr_handle_recovery(bbr, cstat, ack); ++ ++ if (ack->bytes_delivered == 0) { ++ return; ++ } ++ + bbr_update_on_ack(bbr, cstat, ack, ts); + } + +@@ -1399,23 +1435,23 @@ static void bbr_cc_reset(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + + void ngtcp2_cc_bbr_init(ngtcp2_cc_bbr *bbr, ngtcp2_log *log, + ngtcp2_conn_stat *cstat, ngtcp2_rst *rst, +- ngtcp2_tstamp initial_ts, ngtcp2_rand rand, +- const ngtcp2_rand_ctx *rand_ctx) { +- memset(bbr, 0, sizeof(*bbr)); +- +- bbr->cc.log = log; +- bbr->cc.on_pkt_lost = bbr_cc_on_pkt_lost; +- bbr->cc.congestion_event = bbr_cc_congestion_event; +- bbr->cc.on_spurious_congestion = bbr_cc_on_spurious_congestion; +- bbr->cc.on_persistent_congestion = bbr_cc_on_persistent_congestion; +- bbr->cc.on_ack_recv = bbr_cc_on_ack_recv; +- bbr->cc.on_pkt_sent = bbr_cc_on_pkt_sent; +- bbr->cc.reset = bbr_cc_reset; +- +- bbr->rst = rst; +- bbr->rand = rand; +- bbr->rand_ctx = *rand_ctx; +- bbr->initial_cwnd = cstat->cwnd; ++ ngtcp2_tstamp initial_ts, ngtcp2_pcg32 *pcg) { ++ *bbr = (ngtcp2_cc_bbr){ ++ .cc = ++ { ++ .log = log, ++ .on_pkt_lost = bbr_cc_on_pkt_lost, ++ .congestion_event = bbr_cc_congestion_event, ++ .on_spurious_congestion = bbr_cc_on_spurious_congestion, ++ .on_persistent_congestion = bbr_cc_on_persistent_congestion, ++ .on_ack_recv = bbr_cc_on_ack_recv, ++ .on_pkt_sent = bbr_cc_on_pkt_sent, ++ .reset = bbr_cc_reset, ++ }, ++ .rst = rst, ++ .pcg = pcg, ++ .initial_cwnd = cstat->cwnd, ++ }; + + bbr_on_init(bbr, cstat, initial_ts); + } +diff --git a/third_party/ngtcp2/lib/ngtcp2_bbr.h b/third_party/ngtcp2/lib/ngtcp2_bbr.h +index e823711a500..5177944b290 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_bbr.h ++++ b/third_party/ngtcp2/lib/ngtcp2_bbr.h +@@ -35,6 +35,7 @@ + #include "ngtcp2_window_filter.h" + + typedef struct ngtcp2_rst ngtcp2_rst; ++typedef struct ngtcp2_pcg32 ngtcp2_pcg32; + + typedef enum ngtcp2_bbr_state { + NGTCP2_BBR_STATE_STARTUP, +@@ -62,8 +63,7 @@ typedef struct ngtcp2_cc_bbr { + + uint64_t initial_cwnd; + ngtcp2_rst *rst; +- ngtcp2_rand rand; +- ngtcp2_rand_ctx rand_ctx; ++ ngtcp2_pcg32 *pcg; + + /* max_bw_filter for tracking the maximum recent delivery rate + samples for estimating max_bw. */ +@@ -75,8 +75,8 @@ typedef struct ngtcp2_cc_bbr { + ngtcp2_tstamp min_rtt_stamp; + ngtcp2_tstamp probe_rtt_done_stamp; + int probe_rtt_round_done; +- uint64_t prior_cwnd; + int idle_restart; ++ uint64_t prior_cwnd; + ngtcp2_tstamp extra_acked_interval_start; + uint64_t extra_acked_delivered; + +@@ -91,8 +91,8 @@ typedef struct ngtcp2_cc_bbr { + + /* Round counting */ + uint64_t next_round_delivered; +- int round_start; + uint64_t round_count; ++ int round_start; + + /* Full pipe */ + uint64_t full_bw; +@@ -106,7 +106,12 @@ typedef struct ngtcp2_cc_bbr { + ngtcp2_bbr_state state; + uint64_t cwnd_gain_h; + +- int loss_round_start; ++ /* Backup for spurious losses */ ++ ngtcp2_bbr_state undo_state; ++ uint64_t undo_bw_shortterm; ++ uint64_t undo_inflight_shortterm; ++ uint64_t undo_inflight_longterm; ++ + uint64_t loss_round_delivered; + uint64_t rounds_since_bw_probe; + uint64_t max_bw; +@@ -120,23 +125,22 @@ typedef struct ngtcp2_cc_bbr { + ngtcp2_tstamp cycle_stamp; + ngtcp2_bbr_ack_phase ack_phase; + ngtcp2_duration bw_probe_wait; +- int bw_probe_samples; + size_t bw_probe_up_rounds; + uint64_t bw_probe_up_acks; + uint64_t inflight_longterm; +- int probe_rtt_expired; + ngtcp2_duration probe_rtt_min_delay; + ngtcp2_tstamp probe_rtt_min_stamp; +- int in_loss_recovery; + uint64_t round_count_at_recovery; + uint64_t max_inflight; +- ngtcp2_tstamp congestion_recovery_start_ts; + uint64_t bdp; ++ int loss_round_start; ++ int bw_probe_samples; ++ int probe_rtt_expired; ++ int in_loss_recovery; + } ngtcp2_cc_bbr; + + void ngtcp2_cc_bbr_init(ngtcp2_cc_bbr *bbr, ngtcp2_log *log, + ngtcp2_conn_stat *cstat, ngtcp2_rst *rst, +- ngtcp2_tstamp initial_ts, ngtcp2_rand rand, +- const ngtcp2_rand_ctx *rand_ctx); ++ ngtcp2_tstamp initial_ts, ngtcp2_pcg32 *pcg); + + #endif /* !defined(NGTCP2_BBR_H) */ +diff --git a/third_party/ngtcp2/lib/ngtcp2_buf.c b/third_party/ngtcp2/lib/ngtcp2_buf.c +index 75326d6b76b..083a766281b 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_buf.c ++++ b/third_party/ngtcp2/lib/ngtcp2_buf.c +@@ -26,8 +26,12 @@ + #include "ngtcp2_mem.h" + + void ngtcp2_buf_init(ngtcp2_buf *buf, uint8_t *begin, size_t len) { +- buf->begin = buf->pos = buf->last = begin; +- buf->end = begin + len; ++ *buf = (ngtcp2_buf){ ++ .begin = begin, ++ .end = begin + len, ++ .pos = begin, ++ .last = begin, ++ }; + } + + void ngtcp2_buf_reset(ngtcp2_buf *buf) { buf->pos = buf->last = buf->begin; } +@@ -36,6 +40,12 @@ size_t ngtcp2_buf_cap(const ngtcp2_buf *buf) { + return (size_t)(buf->end - buf->begin); + } + ++void ngtcp2_buf_trunc(ngtcp2_buf *buf, size_t len) { ++ if (ngtcp2_buf_len(buf) > len) { ++ buf->last = buf->pos + len; ++ } ++} ++ + int ngtcp2_buf_chain_new(ngtcp2_buf_chain **pbufchain, size_t len, + const ngtcp2_mem *mem) { + *pbufchain = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_buf_chain) + len); +diff --git a/third_party/ngtcp2/lib/ngtcp2_buf.h b/third_party/ngtcp2/lib/ngtcp2_buf.h +index e87adb11991..b59ac9a54b3 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_buf.h ++++ b/third_party/ngtcp2/lib/ngtcp2_buf.h +@@ -62,7 +62,9 @@ void ngtcp2_buf_reset(ngtcp2_buf *buf); + * written to the underlying buffer. In other words, it returns + * buf->end - buf->last. + */ +-#define ngtcp2_buf_left(BUF) (size_t)((BUF)->end - (BUF)->last) ++static inline size_t ngtcp2_buf_left(const ngtcp2_buf *buf) { ++ return (size_t)(buf->end - buf->last); ++} + + /* + * ngtcp2_buf_len returns the number of bytes left to read. In other +@@ -76,6 +78,13 @@ void ngtcp2_buf_reset(ngtcp2_buf *buf); + */ + size_t ngtcp2_buf_cap(const ngtcp2_buf *buf); + ++/* ++ * ngtcp2_buf_trunc truncates the number of bytes to read to at most ++ * |len|. In other words, it sets buf->last = buf->pos + len if ++ * ngtcp2_buf_len(buf) > len. ++ */ ++void ngtcp2_buf_trunc(ngtcp2_buf *buf, size_t len); ++ + /* + * ngtcp2_buf_chain is a linked list of ngtcp2_buf. + */ +diff --git a/third_party/ngtcp2/lib/ngtcp2_callbacks.c b/third_party/ngtcp2/lib/ngtcp2_callbacks.c +new file mode 100644 +index 00000000000..1d65d93d566 +--- /dev/null ++++ b/third_party/ngtcp2/lib/ngtcp2_callbacks.c +@@ -0,0 +1,75 @@ ++/* ++ * ngtcp2 ++ * ++ * Copyright (c) 2025 ngtcp2 contributors ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ */ ++#include "ngtcp2_callbacks.h" ++ ++#include ++#include ++ ++#include "ngtcp2_unreachable.h" ++ ++static void callbacks_copy(ngtcp2_callbacks *dest, const ngtcp2_callbacks *src, ++ int callbacks_version) { ++ assert(callbacks_version != NGTCP2_CALLBACKS_VERSION); ++ ++ memcpy(dest, src, ngtcp2_callbackslen_version(callbacks_version)); ++} ++ ++const ngtcp2_callbacks *ngtcp2_callbacks_convert_to_latest( ++ ngtcp2_callbacks *dest, int callbacks_version, const ngtcp2_callbacks *src) { ++ if (callbacks_version == NGTCP2_CALLBACKS_VERSION) { ++ return src; ++ } ++ ++ *dest = (ngtcp2_callbacks){0}; ++ ++ callbacks_copy(dest, src, callbacks_version); ++ ++ return dest; ++} ++ ++void ngtcp2_callbacks_convert_to_old(int callbacks_version, ++ ngtcp2_callbacks *dest, ++ const ngtcp2_callbacks *src) { ++ assert(callbacks_version != NGTCP2_CALLBACKS_VERSION); ++ ++ callbacks_copy(dest, src, callbacks_version); ++} ++ ++size_t ngtcp2_callbackslen_version(int callbacks_version) { ++ ngtcp2_callbacks callbacks; ++ ++ switch (callbacks_version) { ++ case NGTCP2_CALLBACKS_VERSION: ++ return sizeof(callbacks); ++ case NGTCP2_CALLBACKS_V2: ++ return offsetof(ngtcp2_callbacks, begin_path_validation) + ++ sizeof(callbacks.begin_path_validation); ++ case NGTCP2_CALLBACKS_V1: ++ return offsetof(ngtcp2_callbacks, tls_early_data_rejected) + ++ sizeof(callbacks.tls_early_data_rejected); ++ default: ++ ngtcp2_unreachable(); ++ } ++} +diff --git a/third_party/ngtcp2/lib/ngtcp2_callbacks.h b/third_party/ngtcp2/lib/ngtcp2_callbacks.h +new file mode 100644 +index 00000000000..751766bb83e +--- /dev/null ++++ b/third_party/ngtcp2/lib/ngtcp2_callbacks.h +@@ -0,0 +1,73 @@ ++/* ++ * ngtcp2 ++ * ++ * Copyright (c) 2025 ngtcp2 contributors ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ */ ++#ifndef NGTCP2_CALLBACKS_H ++#define NGTCP2_CALLBACKS_H ++ ++#ifdef HAVE_CONFIG_H ++# include ++#endif /* defined(HAVE_CONFIG_H) */ ++ ++#include ++ ++/* ++ * ngtcp2_callbacks_convert_to_latest converts |src| of version ++ * |callbacks_version| to the latest version NGTCP2_CALLBACKS_VERSION. ++ * ++ * |dest| must point to the latest version. |src| may be the older ++ * version, and if so, it may have fewer fields. Accessing those ++ * fields causes undefined behavior. ++ * ++ * If |callbacks_version| == NGTCP2_CALLBACKS_VERSION, no conversion ++ * is made, and |src| is returned. Otherwise, first |dest| is ++ * zero-initialized, and then all valid fields in |src| are copied ++ * into |dest|. Finally, |dest| is returned. ++ */ ++const ngtcp2_callbacks *ngtcp2_callbacks_convert_to_latest( ++ ngtcp2_callbacks *dest, int callbacks_version, const ngtcp2_callbacks *src); ++ ++/* ++ * ngtcp2_callbacks_convert_to_old converts |src| of the latest ++ * version to |dest| of version |callbacks_version|. ++ * ++ * |callbacks_version| must not be the latest version ++ * NGTCP2_CALLBACKS_VERSION. ++ * ++ * |dest| points to the older version, and it may have fewer fields. ++ * Accessing those fields causes undefined behavior. ++ * ++ * This function copies all valid fields in version ++ * |callbacks_version| from |src| to |dest|. ++ */ ++void ngtcp2_callbacks_convert_to_old(int callbacks_version, ++ ngtcp2_callbacks *dest, ++ const ngtcp2_callbacks *src); ++ ++/* ++ * ngtcp2_callbackslen_version returns the effective length of ++ * ngtcp2_callbacks at the version |callbacks_version|. ++ */ ++size_t ngtcp2_callbackslen_version(int callbacks_version); ++ ++#endif /* !defined(NGTCP2_CALLBACKS_H) */ +diff --git a/third_party/ngtcp2/lib/ngtcp2_cc.c b/third_party/ngtcp2/lib/ngtcp2_cc.c +index ad3665b6cf6..73dfde6904b 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_cc.c ++++ b/third_party/ngtcp2/lib/ngtcp2_cc.c +@@ -41,6 +41,40 @@ uint64_t ngtcp2_cc_compute_initcwnd(size_t max_udp_payload_size) { + return ngtcp2_min_uint64(10 * max_udp_payload_size, n); + } + ++/* 1.25 is the under-utilization avoidance factor described in ++ https://datatracker.ietf.org/doc/html/rfc9002#section-7.7 */ ++#define NGTCP2_CC_PACING_GAIN_H 125 ++ ++static void init_pacing_rate(ngtcp2_conn_stat *cstat) { ++ assert(cstat->cwnd); ++ ++ cstat->pacing_interval_m = ngtcp2_max_uint64( ++ (NGTCP2_MILLISECONDS << 10) * 100 / NGTCP2_CC_PACING_GAIN_H / cstat->cwnd, ++ 1); ++ cstat->send_quantum = 10 * cstat->max_tx_udp_payload_size; ++} ++ ++static void set_pacing_rate(ngtcp2_conn_stat *cstat) { ++ size_t send_quantum = 64 * 1024; ++ ++ assert(cstat->cwnd); ++ ++ cstat->pacing_interval_m = ++ ((cstat->first_rtt_sample_ts == UINT64_MAX ? NGTCP2_MILLISECONDS ++ : cstat->smoothed_rtt) ++ << 10) * ++ 100 / NGTCP2_CC_PACING_GAIN_H / cstat->cwnd; ++ ++ cstat->pacing_interval_m = ngtcp2_max_uint64(cstat->pacing_interval_m, 1); ++ ++ send_quantum = ++ ngtcp2_min_size(send_quantum, (size_t)((NGTCP2_MILLISECONDS << 10) / ++ cstat->pacing_interval_m)); ++ ++ cstat->send_quantum = ++ ngtcp2_max_size(send_quantum, 10 * cstat->max_tx_udp_payload_size); ++} ++ + ngtcp2_cc_pkt *ngtcp2_cc_pkt_init(ngtcp2_cc_pkt *pkt, int64_t pkt_num, + size_t pktlen, ngtcp2_pktns_id pktns_id, + ngtcp2_tstamp sent_ts, uint64_t lost, +@@ -56,19 +90,26 @@ ngtcp2_cc_pkt *ngtcp2_cc_pkt_init(ngtcp2_cc_pkt *pkt, int64_t pkt_num, + return pkt; + } + +-static void reno_cc_reset(ngtcp2_cc_reno *reno) { reno->pending_add = 0; } +- +-void ngtcp2_cc_reno_init(ngtcp2_cc_reno *reno, ngtcp2_log *log) { +- memset(reno, 0, sizeof(*reno)); ++static void reno_cc_reset(ngtcp2_cc_reno *reno, ngtcp2_conn_stat *cstat) { ++ reno->pending_add = 0; + +- reno->cc.log = log; +- reno->cc.on_pkt_acked = ngtcp2_cc_reno_cc_on_pkt_acked; +- reno->cc.congestion_event = ngtcp2_cc_reno_cc_congestion_event; +- reno->cc.on_persistent_congestion = +- ngtcp2_cc_reno_cc_on_persistent_congestion; +- reno->cc.reset = ngtcp2_cc_reno_cc_reset; ++ init_pacing_rate(cstat); ++} + +- reno_cc_reset(reno); ++void ngtcp2_cc_reno_init(ngtcp2_cc_reno *reno, ngtcp2_log *log, ++ ngtcp2_conn_stat *cstat) { ++ *reno = (ngtcp2_cc_reno){ ++ .cc = ++ { ++ .log = log, ++ .on_pkt_acked = ngtcp2_cc_reno_cc_on_pkt_acked, ++ .congestion_event = ngtcp2_cc_reno_cc_congestion_event, ++ .on_persistent_congestion = ngtcp2_cc_reno_cc_on_persistent_congestion, ++ .reset = ngtcp2_cc_reno_cc_reset, ++ }, ++ }; ++ ++ reno_cc_reset(reno, cstat); + } + + static int in_congestion_recovery(const ngtcp2_conn_stat *cstat, +@@ -90,9 +131,12 @@ void ngtcp2_cc_reno_cc_on_pkt_acked(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + + if (cstat->cwnd < cstat->ssthresh) { + cstat->cwnd += pkt->pktlen; +- ngtcp2_log_info(reno->cc.log, NGTCP2_LOG_EVENT_CCA, +- "pkn=%" PRId64 " acked, slow start cwnd=%" PRIu64, +- pkt->pkt_num, cstat->cwnd); ++ ++ set_pacing_rate(cstat); ++ ++ ngtcp2_log_infof(reno->cc.log, NGTCP2_LOG_EVENT_CCA, ++ "pkn=%" PRId64 " acked, slow start cwnd=%" PRIu64, ++ pkt->pkt_num, cstat->cwnd); + return; + } + +@@ -100,14 +144,17 @@ void ngtcp2_cc_reno_cc_on_pkt_acked(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + reno->pending_add = m % cstat->cwnd; + + cstat->cwnd += m / cstat->cwnd; ++ ++ set_pacing_rate(cstat); + } + + void ngtcp2_cc_reno_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_ts, +- uint64_t bytes_lost, ngtcp2_tstamp ts) { ++ const ngtcp2_cc_ack *ack, ++ ngtcp2_tstamp ts) { + ngtcp2_cc_reno *reno = ngtcp2_struct_of(cc, ngtcp2_cc_reno, cc); + uint64_t min_cwnd; +- (void)bytes_lost; ++ (void)ack; + + if (in_congestion_recovery(cstat, sent_ts)) { + return; +@@ -121,9 +168,11 @@ void ngtcp2_cc_reno_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + + reno->pending_add = 0; + +- ngtcp2_log_info(reno->cc.log, NGTCP2_LOG_EVENT_CCA, +- "reduce cwnd because of packet loss cwnd=%" PRIu64, +- cstat->cwnd); ++ set_pacing_rate(cstat); ++ ++ ngtcp2_log_infof(reno->cc.log, NGTCP2_LOG_EVENT_CCA, ++ "reduce cwnd because of packet loss cwnd=%" PRIu64, ++ cstat->cwnd); + } + + void ngtcp2_cc_reno_cc_on_persistent_congestion(ngtcp2_cc *cc, +@@ -134,31 +183,32 @@ void ngtcp2_cc_reno_cc_on_persistent_congestion(ngtcp2_cc *cc, + + cstat->cwnd = 2 * cstat->max_tx_udp_payload_size; + cstat->congestion_recovery_start_ts = UINT64_MAX; ++ ++ set_pacing_rate(cstat); + } + + void ngtcp2_cc_reno_cc_reset(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + ngtcp2_cc_reno *reno = ngtcp2_struct_of(cc, ngtcp2_cc_reno, cc); +- (void)cstat; + (void)ts; + +- reno_cc_reset(reno); ++ reno_cc_reset(reno, cstat); + } + + static void cubic_vars_reset(ngtcp2_cubic_vars *v) { + v->cwnd_prior = 0; + v->w_max = 0; +- v->k = 0; ++ v->k_m = 0; + v->epoch_start = UINT64_MAX; + v->w_est = 0; + + v->app_limited_start_ts = UINT64_MAX; + v->app_limited_duration = 0; +- v->pending_bytes_delivered = 0; +- v->pending_est_bytes_delivered = 0; ++ v->pending_bytes_acked = 0; ++ v->pending_est_bytes_acked = 0; + } + +-static void cubic_cc_reset(ngtcp2_cc_cubic *cubic) { ++static void cubic_cc_reset(ngtcp2_cc_cubic *cubic, ngtcp2_conn_stat *cstat) { + cubic_vars_reset(&cubic->current); + cubic_vars_reset(&cubic->undo.v); + cubic->undo.cwnd = 0; +@@ -172,23 +222,26 @@ static void cubic_cc_reset(ngtcp2_cc_cubic *cubic) { + cubic->hs.css_round = 0; + + cubic->next_round_delivered = 0; ++ ++ init_pacing_rate(cstat); + } + + void ngtcp2_cc_cubic_init(ngtcp2_cc_cubic *cubic, ngtcp2_log *log, +- ngtcp2_rst *rst) { +- memset(cubic, 0, sizeof(*cubic)); +- +- cubic->cc.log = log; +- cubic->cc.on_ack_recv = ngtcp2_cc_cubic_cc_on_ack_recv; +- cubic->cc.congestion_event = ngtcp2_cc_cubic_cc_congestion_event; +- cubic->cc.on_spurious_congestion = ngtcp2_cc_cubic_cc_on_spurious_congestion; +- cubic->cc.on_persistent_congestion = +- ngtcp2_cc_cubic_cc_on_persistent_congestion; +- cubic->cc.reset = ngtcp2_cc_cubic_cc_reset; +- +- cubic->rst = rst; +- +- cubic_cc_reset(cubic); ++ ngtcp2_conn_stat *cstat, ngtcp2_rst *rst) { ++ *cubic = (ngtcp2_cc_cubic){ ++ .cc = ++ { ++ .log = log, ++ .on_ack_recv = ngtcp2_cc_cubic_cc_on_ack_recv, ++ .congestion_event = ngtcp2_cc_cubic_cc_congestion_event, ++ .on_spurious_congestion = ngtcp2_cc_cubic_cc_on_spurious_congestion, ++ .on_persistent_congestion = ngtcp2_cc_cubic_cc_on_persistent_congestion, ++ .reset = ngtcp2_cc_cubic_cc_reset, ++ }, ++ .rst = rst, ++ }; ++ ++ cubic_cc_reset(cubic, cstat); + } + + uint64_t ngtcp2_cbrt(uint64_t n) { +@@ -208,7 +261,6 @@ uint64_t ngtcp2_cbrt(uint64_t n) { + y <<= 1; + b = 3 * y * (y + 1) + 1; + if (n >= b) { +- n -= b; + y++; + } + +@@ -223,30 +275,54 @@ uint64_t ngtcp2_cbrt(uint64_t n) { + #define NGTCP2_HS_CSS_GROWTH_DIVISOR 4 + #define NGTCP2_HS_CSS_ROUNDS 5 + +-static int64_t cubic_cc_compute_w_cubic(ngtcp2_cc_cubic *cubic, +- const ngtcp2_conn_stat *cstat, +- ngtcp2_tstamp ts) { ++static uint64_t cubic_cc_compute_w_cubic(ngtcp2_cc_cubic *cubic, ++ const ngtcp2_conn_stat *cstat, ++ ngtcp2_tstamp ts) { + ngtcp2_duration t = ts - cubic->current.epoch_start; +- int64_t tx = (int64_t)((t << 10) / NGTCP2_SECONDS); +- int64_t time_delta = ngtcp2_min_int64(tx - cubic->current.k, 3600 << 10); +- int64_t delta = ((((time_delta * time_delta) >> 10) * time_delta) >> 20) * +- (int64_t)cstat->max_tx_udp_payload_size * 4 / 10; ++ uint64_t tx_m = (t << 10) / NGTCP2_SECONDS; ++ int neg = tx_m < cubic->current.k_m; ++ uint64_t time_delta_m; ++ uint64_t delta; ++ ++ /* Avoid signed bit-shift */ ++ if (neg) { ++ time_delta_m = cubic->current.k_m - tx_m; ++ } else { ++ time_delta_m = tx_m - cubic->current.k_m; ++ } ++ ++ time_delta_m = ngtcp2_min_uint64(time_delta_m, 3600 << 10); ++ ++ delta = ((((time_delta_m * time_delta_m) >> 10) * time_delta_m) >> 10) * ++ cstat->max_tx_udp_payload_size * 4 / 10; ++ delta >>= 10; ++ ++ if (neg) { ++ if (cubic->current.w_max < delta) { ++ /* Negative w_cubic is not interesting. */ ++ return 0; ++ } + +- return (int64_t)cubic->current.w_max + delta; ++ return cubic->current.w_max - delta; ++ } ++ ++ return cubic->current.w_max + delta; + } + + void ngtcp2_cc_cubic_cc_on_ack_recv(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts) { + ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc); +- int64_t w_cubic, w_cubic_next; ++ uint64_t w_cubic, w_cubic_next; + uint64_t target, m; ++ uint64_t bytes_acked; + ngtcp2_duration rtt_thresh; + int round_start; + int is_app_limited = + cubic->rst->rs.is_app_limited && !cubic->rst->is_cwnd_limited; + +- if (in_congestion_recovery(cstat, ack->largest_pkt_sent_ts)) { ++ if (ack->bytes_delivered == 0 || ++ in_congestion_recovery(cstat, ack->largest_pkt_sent_ts)) { + return; + } + +@@ -266,9 +342,11 @@ void ngtcp2_cc_cubic_cc_on_ack_recv(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + cstat->cwnd += ack->bytes_delivered; + } + +- ngtcp2_log_info(cubic->cc.log, NGTCP2_LOG_EVENT_CCA, +- "%" PRIu64 " bytes acked, slow start cwnd=%" PRIu64, +- ack->bytes_delivered, cstat->cwnd); ++ set_pacing_rate(cstat); ++ ++ ngtcp2_log_infof(cubic->cc.log, NGTCP2_LOG_EVENT_CCA, ++ "%" PRIu64 " bytes acked, slow start cwnd=%" PRIu64, ++ ack->bytes_delivered, cstat->cwnd); + } + + if (round_start) { +@@ -346,44 +424,54 @@ void ngtcp2_cc_cubic_cc_on_ack_recv(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + cubic, cstat, + ts - cubic->current.app_limited_duration + cstat->smoothed_rtt); + +- if (w_cubic_next < (int64_t)cstat->cwnd) { ++ if (w_cubic_next < cstat->cwnd) { + target = cstat->cwnd; +- } else if (2 * w_cubic_next > 3 * (int64_t)cstat->cwnd) { ++ } else if (2 * w_cubic_next > 3 * cstat->cwnd) { + target = cstat->cwnd * 3 / 2; + } else { +- assert(w_cubic_next >= 0); +- target = (uint64_t)w_cubic_next; ++ target = w_cubic_next; + } + +- m = ack->bytes_delivered * cstat->max_tx_udp_payload_size + +- cubic->current.pending_est_bytes_delivered; +- cubic->current.pending_est_bytes_delivered = m % cstat->cwnd; ++ bytes_acked = ack->bytes_delivered * cstat->max_tx_udp_payload_size; ++ m = (bytes_acked + cubic->current.pending_est_bytes_acked) / cstat->cwnd; ++ ++ cubic->current.pending_est_bytes_acked += bytes_acked; ++ cubic->current.pending_est_bytes_acked -= m * cstat->cwnd; ++ ++ assert(cubic->current.pending_est_bytes_acked < cstat->cwnd); + + if (cubic->current.w_est < cubic->current.cwnd_prior) { +- cubic->current.w_est += m * 9 / 17 / cstat->cwnd; ++ cubic->current.w_est += m * 9 / 17; + } else { +- cubic->current.w_est += m / cstat->cwnd; ++ cubic->current.w_est += m; + } + +- if ((int64_t)cubic->current.w_est > w_cubic) { ++ if (cubic->current.w_est > w_cubic) { + cstat->cwnd = cubic->current.w_est; + } else { +- m = (target - cstat->cwnd) * cstat->max_tx_udp_payload_size + +- cubic->current.pending_bytes_delivered; +- cstat->cwnd += m / cstat->cwnd; +- cubic->current.pending_bytes_delivered = m % cstat->cwnd; ++ bytes_acked = (target - cstat->cwnd) * cstat->max_tx_udp_payload_size; ++ m = (bytes_acked + cubic->current.pending_bytes_acked) / cstat->cwnd; ++ ++ cubic->current.pending_bytes_acked += bytes_acked; ++ cubic->current.pending_bytes_acked -= m * cstat->cwnd; ++ ++ assert(cubic->current.pending_bytes_acked < cstat->cwnd); ++ ++ cstat->cwnd += m; + } + +- ngtcp2_log_info(cubic->cc.log, NGTCP2_LOG_EVENT_CCA, +- "%" PRIu64 " bytes acked, cubic-ca cwnd=%" PRIu64 +- " k=%" PRIi64 " target=%" PRIu64 " w_est=%" PRIu64, +- ack->bytes_delivered, cstat->cwnd, cubic->current.k, target, +- cubic->current.w_est); ++ set_pacing_rate(cstat); ++ ++ ngtcp2_log_infof(cubic->cc.log, NGTCP2_LOG_EVENT_CCA, ++ "%" PRIu64 " bytes acked, cubic-ca cwnd=%" PRIu64 ++ " k_m=%" PRIu64 " target=%" PRIu64 " w_est=%" PRIu64, ++ ack->bytes_delivered, cstat->cwnd, cubic->current.k_m, ++ target, cubic->current.w_est); + } + + void ngtcp2_cc_cubic_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_ts, +- uint64_t bytes_lost, ++ const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts) { + ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc); + uint64_t flight_size; +@@ -404,8 +492,8 @@ void ngtcp2_cc_cubic_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + cubic->current.epoch_start = ts; + cubic->current.app_limited_start_ts = UINT64_MAX; + cubic->current.app_limited_duration = 0; +- cubic->current.pending_bytes_delivered = 0; +- cubic->current.pending_est_bytes_delivered = 0; ++ cubic->current.pending_bytes_acked = 0; ++ cubic->current.pending_est_bytes_acked = 0; + + if (cstat->cwnd < cubic->current.w_max) { + cubic->current.w_max = cstat->cwnd * 17 / 20; +@@ -413,10 +501,13 @@ void ngtcp2_cc_cubic_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + cubic->current.w_max = cstat->cwnd; + } + ++ cubic->current.w_max = ++ ngtcp2_max_uint64(cubic->current.w_max, 2 * cstat->max_tx_udp_payload_size); ++ + cstat->ssthresh = cstat->cwnd * 7 / 10; + + if (cubic->rst->rs.delivered * 2 < cstat->cwnd) { +- flight_size = cstat->bytes_in_flight + bytes_lost; ++ flight_size = cstat->bytes_in_flight + ack->bytes_lost; + cstat->ssthresh = ngtcp2_min_uint64( + cstat->ssthresh, + ngtcp2_max_uint64(cubic->rst->rs.delivered, flight_size)); +@@ -430,21 +521,18 @@ void ngtcp2_cc_cubic_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + + cubic->current.w_est = cstat->cwnd; + +- if (cstat->cwnd < cubic->current.w_max) { +- cwnd_delta = cubic->current.w_max - cstat->cwnd; +- } else { +- cwnd_delta = cstat->cwnd - cubic->current.w_max; +- } ++ assert(cubic->current.w_max >= cstat->cwnd); + +- cubic->current.k = (int64_t)ngtcp2_cbrt((cwnd_delta << 30) * 10 / 4 / +- cstat->max_tx_udp_payload_size); +- if (cstat->cwnd >= cubic->current.w_max) { +- cubic->current.k = -cubic->current.k; +- } ++ cwnd_delta = cubic->current.w_max - cstat->cwnd; ++ ++ cubic->current.k_m = ++ ngtcp2_cbrt((cwnd_delta << 30) * 10 / 4 / cstat->max_tx_udp_payload_size); + +- ngtcp2_log_info(cubic->cc.log, NGTCP2_LOG_EVENT_CCA, +- "reduce cwnd because of packet loss cwnd=%" PRIu64, +- cstat->cwnd); ++ set_pacing_rate(cstat); ++ ++ ngtcp2_log_infof(cubic->cc.log, NGTCP2_LOG_EVENT_CCA, ++ "reduce cwnd because of packet loss cwnd=%" PRIu64, ++ cstat->cwnd); + } + + void ngtcp2_cc_cubic_cc_on_spurious_congestion(ngtcp2_cc *cc, +@@ -460,10 +548,12 @@ void ngtcp2_cc_cubic_cc_on_spurious_congestion(ngtcp2_cc *cc, + cstat->cwnd = cubic->undo.cwnd; + cstat->ssthresh = cubic->undo.ssthresh; + +- ngtcp2_log_info(cubic->cc.log, NGTCP2_LOG_EVENT_CCA, +- "spurious congestion is detected and congestion state is " +- "restored cwnd=%" PRIu64, +- cstat->cwnd); ++ set_pacing_rate(cstat); ++ ++ ngtcp2_log_infof(cubic->cc.log, NGTCP2_LOG_EVENT_CCA, ++ "spurious congestion is detected and congestion state is " ++ "restored cwnd=%" PRIu64, ++ cstat->cwnd); + } + + cubic_vars_reset(&cubic->undo.v); +@@ -477,17 +567,18 @@ void ngtcp2_cc_cubic_cc_on_persistent_congestion(ngtcp2_cc *cc, + ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc); + (void)ts; + +- cubic_cc_reset(cubic); ++ cubic_cc_reset(cubic, cstat); + + cstat->cwnd = 2 * cstat->max_tx_udp_payload_size; + cstat->congestion_recovery_start_ts = UINT64_MAX; ++ ++ set_pacing_rate(cstat); + } + + void ngtcp2_cc_cubic_cc_reset(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc); +- (void)cstat; + (void)ts; + +- cubic_cc_reset(cubic); ++ cubic_cc_reset(cubic, cstat); + } +diff --git a/third_party/ngtcp2/lib/ngtcp2_cc.h b/third_party/ngtcp2/lib/ngtcp2_cc.h +index 296a1a433f1..a19e90b1d0d 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_cc.h ++++ b/third_party/ngtcp2/lib/ngtcp2_cc.h +@@ -88,11 +88,6 @@ typedef struct ngtcp2_cc_pkt { + * acknowledged and lost bytes. + */ + typedef struct ngtcp2_cc_ack { +- /** +- * :member:`prior_bytes_in_flight` is the in-flight bytes before +- * processing this ACK. +- */ +- uint64_t prior_bytes_in_flight; + /** + * :member:`bytes_delivered` is the number of bytes acknowledged. + */ +@@ -143,13 +138,16 @@ typedef void (*ngtcp2_cc_on_pkt_lost)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + * @functypedef + * + * :type:`ngtcp2_cc_congestion_event` is a callback function which is +- * called when congestion event happens (e.g., when packet is lost). +- * |bytes_lost| is the number of bytes lost in this congestion event. ++ * called when congestion event happens (e.g., when packet is lost or ++ * due to ECN). |ack| contains information after ACK processing. ++ * This callback may be called from non-ACK processing context. In ++ * that case, the information only taken from |ack| processing has ++ * default values, like 0 or UINT64_MAX; + */ + typedef void (*ngtcp2_cc_congestion_event)(ngtcp2_cc *cc, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_ts, +- uint64_t bytes_lost, ++ const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts); + + /** +@@ -186,20 +184,12 @@ typedef void (*ngtcp2_cc_on_ack_recv)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + * @functypedef + * + * :type:`ngtcp2_cc_on_pkt_sent` is a callback function which is +- * called when an ack-eliciting packet is sent. ++ * called when an ack-eliciting packet is sent. The lost, ++ * tx_in_flight, and is_app_limited fields in |pkt| are set to 0. + */ + typedef void (*ngtcp2_cc_on_pkt_sent)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt); + +-/** +- * @functypedef +- * +- * :type:`ngtcp2_cc_new_rtt_sample` is a callback function which is +- * called when new RTT sample is obtained. +- */ +-typedef void (*ngtcp2_cc_new_rtt_sample)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, +- ngtcp2_tstamp ts); +- + /** + * @functypedef + * +@@ -209,28 +199,6 @@ typedef void (*ngtcp2_cc_new_rtt_sample)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + typedef void (*ngtcp2_cc_reset)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +-/** +- * @enum +- * +- * :type:`ngtcp2_cc_event_type` defines congestion control events. +- */ +-typedef enum ngtcp2_cc_event_type { +- /** +- * :enum:`NGTCP2_CC_EVENT_TX_START` occurs when ack-eliciting packet +- * is sent and no other ack-eliciting packet is present. +- */ +- NGTCP2_CC_EVENT_TYPE_TX_START +-} ngtcp2_cc_event_type; +- +-/** +- * @functypedef +- * +- * :type:`ngtcp2_cc_event` is a callback function which is called when +- * a specific event happens. +- */ +-typedef void (*ngtcp2_cc_event)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, +- ngtcp2_cc_event_type event, ngtcp2_tstamp ts); +- + /** + * @struct + * +@@ -254,7 +222,7 @@ typedef struct ngtcp2_cc { + ngtcp2_cc_on_pkt_lost on_pkt_lost; + /** + * :member:`congestion_event` is a callback function which is called +- * when congestion event happens (.e.g, packet is lost). ++ * when congestion event happens (e.g., packet is lost). + */ + ngtcp2_cc_congestion_event congestion_event; + /** +@@ -277,21 +245,11 @@ typedef struct ngtcp2_cc { + * ack-eliciting packet is sent. + */ + ngtcp2_cc_on_pkt_sent on_pkt_sent; +- /** +- * :member:`new_rtt_sample` is a callback function which is called +- * when new RTT sample is obtained. +- */ +- ngtcp2_cc_new_rtt_sample new_rtt_sample; + /** + * :member:`reset` is a callback function which is called when + * congestion control state must be reset. + */ + ngtcp2_cc_reset reset; +- /** +- * :member:`event` is a callback function which is called when a +- * specific event happens. +- */ +- ngtcp2_cc_event event; + } ngtcp2_cc; + + /* +@@ -310,14 +268,16 @@ typedef struct ngtcp2_cc_reno { + uint64_t pending_add; + } ngtcp2_cc_reno; + +-void ngtcp2_cc_reno_init(ngtcp2_cc_reno *reno, ngtcp2_log *log); ++void ngtcp2_cc_reno_init(ngtcp2_cc_reno *reno, ngtcp2_log *log, ++ ngtcp2_conn_stat *cstat); + + void ngtcp2_cc_reno_cc_on_pkt_acked(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts); + + void ngtcp2_cc_reno_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_ts, +- uint64_t bytes_lost, ngtcp2_tstamp ts); ++ const ngtcp2_cc_ack *ack, ++ ngtcp2_tstamp ts); + + void ngtcp2_cc_reno_cc_on_persistent_congestion(ngtcp2_cc *cc, + ngtcp2_conn_stat *cstat, +@@ -329,7 +289,8 @@ void ngtcp2_cc_reno_cc_reset(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + typedef struct ngtcp2_cubic_vars { + uint64_t cwnd_prior; + uint64_t w_max; +- int64_t k; ++ /* CUBIC K with 10 bits extra precision. */ ++ uint64_t k_m; + ngtcp2_tstamp epoch_start; + uint64_t w_est; + +@@ -339,8 +300,8 @@ typedef struct ngtcp2_cubic_vars { + /* app_limited_duration is the cumulative duration where a + connection is under app limited when ACK is received. */ + ngtcp2_duration app_limited_duration; +- uint64_t pending_bytes_delivered; +- uint64_t pending_est_bytes_delivered; ++ uint64_t pending_bytes_acked; ++ uint64_t pending_est_bytes_acked; + } ngtcp2_cubic_vars; + + /* ngtcp2_cc_cubic is CUBIC congestion controller. */ +@@ -370,14 +331,15 @@ typedef struct ngtcp2_cc_cubic { + } ngtcp2_cc_cubic; + + void ngtcp2_cc_cubic_init(ngtcp2_cc_cubic *cc, ngtcp2_log *log, +- ngtcp2_rst *rst); ++ ngtcp2_conn_stat *cstat, ngtcp2_rst *rst); + + void ngtcp2_cc_cubic_cc_on_ack_recv(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts); + + void ngtcp2_cc_cubic_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_ts, +- uint64_t bytes_lost, ngtcp2_tstamp ts); ++ const ngtcp2_cc_ack *ack, ++ ngtcp2_tstamp ts); + + void ngtcp2_cc_cubic_cc_on_spurious_congestion(ngtcp2_cc *ccx, + ngtcp2_conn_stat *cstat, +diff --git a/third_party/ngtcp2/lib/ngtcp2_cid.c b/third_party/ngtcp2/lib/ngtcp2_cid.c +index acbee78aaf4..4eba2aaca5d 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_cid.c ++++ b/third_party/ngtcp2/lib/ngtcp2_cid.c +@@ -29,8 +29,9 @@ + + #include "ngtcp2_path.h" + #include "ngtcp2_str.h" ++#include "ngtcp2_pkt.h" + +-void ngtcp2_cid_zero(ngtcp2_cid *cid) { memset(cid, 0, sizeof(*cid)); } ++void ngtcp2_cid_zero(ngtcp2_cid *cid) { *cid = (ngtcp2_cid){0}; } + + void ngtcp2_cid_init(ngtcp2_cid *cid, const uint8_t *data, size_t datalen) { + assert(datalen <= NGTCP2_MAX_CIDLEN); +@@ -58,11 +59,13 @@ int ngtcp2_cid_less(const ngtcp2_cid *lhs, const ngtcp2_cid *rhs) { + int ngtcp2_cid_empty(const ngtcp2_cid *cid) { return cid->datalen == 0; } + + void ngtcp2_scid_init(ngtcp2_scid *scid, uint64_t seq, const ngtcp2_cid *cid) { +- scid->pe.index = NGTCP2_PQ_BAD_INDEX; +- scid->seq = seq; +- scid->cid = *cid; +- scid->retired_ts = UINT64_MAX; +- scid->flags = NGTCP2_SCID_FLAG_NONE; ++ *scid = (ngtcp2_scid){ ++ .pe.index = NGTCP2_PQ_BAD_INDEX, ++ .seq = seq, ++ .cid = *cid, ++ .retired_ts = UINT64_MAX, ++ .flags = NGTCP2_SCID_FLAG_NONE, ++ }; + } + + void ngtcp2_scid_copy(ngtcp2_scid *dest, const ngtcp2_scid *src) { +@@ -72,12 +75,12 @@ void ngtcp2_scid_copy(ngtcp2_scid *dest, const ngtcp2_scid *src) { + } + + void ngtcp2_dcid_init(ngtcp2_dcid *dcid, uint64_t seq, const ngtcp2_cid *cid, +- const uint8_t *token) { ++ const ngtcp2_stateless_reset_token *token) { + dcid->seq = seq; + dcid->cid = *cid; + + if (token) { +- memcpy(dcid->token, token, NGTCP2_STATELESS_RESET_TOKENLEN); ++ dcid->token = *token; + dcid->flags = NGTCP2_DCID_FLAG_TOKEN_PRESENT; + } else { + dcid->flags = NGTCP2_DCID_FLAG_NONE; +@@ -91,11 +94,12 @@ void ngtcp2_dcid_init(ngtcp2_dcid *dcid, uint64_t seq, const ngtcp2_cid *cid, + dcid->max_udp_payload_size = NGTCP2_MAX_UDP_PAYLOAD_SIZE; + } + +-void ngtcp2_dcid_set_token(ngtcp2_dcid *dcid, const uint8_t *token) { ++void ngtcp2_dcid_set_token(ngtcp2_dcid *dcid, ++ const ngtcp2_stateless_reset_token *token) { + assert(token); + + dcid->flags |= NGTCP2_DCID_FLAG_TOKEN_PRESENT; +- memcpy(dcid->token, token, NGTCP2_STATELESS_RESET_TOKENLEN); ++ dcid->token = *token; + } + + void ngtcp2_dcid_set_path(ngtcp2_dcid *dcid, const ngtcp2_path *path) { +@@ -104,7 +108,7 @@ void ngtcp2_dcid_set_path(ngtcp2_dcid *dcid, const ngtcp2_path *path) { + + void ngtcp2_dcid_copy(ngtcp2_dcid *dest, const ngtcp2_dcid *src) { + ngtcp2_dcid_init(dest, src->seq, &src->cid, +- (src->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) ? src->token ++ (src->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) ? &src->token + : NULL); + ngtcp2_path_copy(&dest->ps.path, &src->ps.path); + dest->retired_ts = src->retired_ts; +@@ -121,18 +125,19 @@ void ngtcp2_dcid_copy_cid_token(ngtcp2_dcid *dest, const ngtcp2_dcid *src) { + + if (src->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) { + dest->flags |= NGTCP2_DCID_FLAG_TOKEN_PRESENT; +- memcpy(dest->token, src->token, NGTCP2_STATELESS_RESET_TOKENLEN); ++ dest->token = src->token; + } else if (dest->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) { + dest->flags &= (uint8_t)~NGTCP2_DCID_FLAG_TOKEN_PRESENT; + } + } + + int ngtcp2_dcid_verify_uniqueness(const ngtcp2_dcid *dcid, uint64_t seq, +- const ngtcp2_cid *cid, const uint8_t *token) { ++ const ngtcp2_cid *cid, ++ const ngtcp2_stateless_reset_token *token) { + if (dcid->seq == seq) { + return ngtcp2_cid_eq(&dcid->cid, cid) && + (dcid->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) && +- memcmp(dcid->token, token, NGTCP2_STATELESS_RESET_TOKENLEN) == 0 ++ ngtcp2_stateless_reset_token_eq(&dcid->token, token) + ? 0 + : NGTCP2_ERR_PROTO; + } +@@ -140,12 +145,12 @@ int ngtcp2_dcid_verify_uniqueness(const ngtcp2_dcid *dcid, uint64_t seq, + return !ngtcp2_cid_eq(&dcid->cid, cid) ? 0 : NGTCP2_ERR_PROTO; + } + +-int ngtcp2_dcid_verify_stateless_reset_token(const ngtcp2_dcid *dcid, +- const ngtcp2_path *path, +- const uint8_t *token) { ++int ngtcp2_dcid_verify_stateless_reset_token( ++ const ngtcp2_dcid *dcid, const ngtcp2_path *path, ++ const ngtcp2_stateless_reset_token *token) { + return ngtcp2_path_eq(&dcid->ps.path, path) && + (dcid->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) && +- ngtcp2_cmemeq(dcid->token, token, NGTCP2_STATELESS_RESET_TOKENLEN) ++ ngtcp2_cmemeq(dcid->token.data, token->data, sizeof(token->data)) + ? 0 + : NGTCP2_ERR_INVALID_ARGUMENT; + } +diff --git a/third_party/ngtcp2/lib/ngtcp2_cid.h b/third_party/ngtcp2/lib/ngtcp2_cid.h +index 9321cfb64e6..f4956b0bac0 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_cid.h ++++ b/third_party/ngtcp2/lib/ngtcp2_cid.h +@@ -35,13 +35,13 @@ + #include "ngtcp2_path.h" + + /* NGTCP2_SCID_FLAG_NONE indicates that no flag is set. */ +-#define NGTCP2_SCID_FLAG_NONE 0x00u ++#define NGTCP2_SCID_FLAG_NONE 0x00U + /* NGTCP2_SCID_FLAG_USED indicates that a local endpoint observed that + a remote endpoint uses this particular Connection ID. */ +-#define NGTCP2_SCID_FLAG_USED 0x01u ++#define NGTCP2_SCID_FLAG_USED 0x01U + /* NGTCP2_SCID_FLAG_RETIRED indicates that this particular Connection + ID is retired. */ +-#define NGTCP2_SCID_FLAG_RETIRED 0x02u ++#define NGTCP2_SCID_FLAG_RETIRED 0x02U + + typedef struct ngtcp2_scid { + ngtcp2_pq_entry pe; +@@ -57,13 +57,13 @@ typedef struct ngtcp2_scid { + } ngtcp2_scid; + + /* NGTCP2_DCID_FLAG_NONE indicates that no flag is set. */ +-#define NGTCP2_DCID_FLAG_NONE 0x00u ++#define NGTCP2_DCID_FLAG_NONE 0x00U + /* NGTCP2_DCID_FLAG_PATH_VALIDATED indicates that an associated path + has been validated. */ +-#define NGTCP2_DCID_FLAG_PATH_VALIDATED 0x01u ++#define NGTCP2_DCID_FLAG_PATH_VALIDATED 0x01U + /* NGTCP2_DCID_FLAG_TOKEN_PRESENT indicates that a stateless reset + token is set in token field. */ +-#define NGTCP2_DCID_FLAG_TOKEN_PRESENT 0x02u ++#define NGTCP2_DCID_FLAG_TOKEN_PRESENT 0x02U + + typedef struct ngtcp2_dcid { + /* seq is the sequence number associated to the Connection ID. */ +@@ -93,7 +93,7 @@ typedef struct ngtcp2_dcid { + /* token is a stateless reset token received along with this + Connection ID. The stateless reset token is tied to the + connection, not to the particular Connection ID. */ +- uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN]; ++ ngtcp2_stateless_reset_token token; + } ngtcp2_dcid; + + /* ngtcp2_cid_zero makes |cid| zero-length. */ +@@ -123,17 +123,18 @@ void ngtcp2_scid_copy(ngtcp2_scid *dest, const ngtcp2_scid *src); + + /* + * ngtcp2_dcid_init initializes |dcid| with the given parameters. If +- * |token| is NULL, the function fills dcid->token with 0. |token| +- * must be NGTCP2_STATELESS_RESET_TOKENLEN bytes long. ++ * |token| is not NULL, the function sets ++ * NGTCP2_DCID_FLAG_TOKEN_PRESENT flag. + */ + void ngtcp2_dcid_init(ngtcp2_dcid *dcid, uint64_t seq, const ngtcp2_cid *cid, +- const uint8_t *token); ++ const ngtcp2_stateless_reset_token *token); + + /* + * ngtcp2_dcid_set_token sets |token| to |dcid|. |token| must not be +- * NULL, and must be NGTCP2_STATELESS_RESET_TOKENLEN bytes long. ++ * NULL. + */ +-void ngtcp2_dcid_set_token(ngtcp2_dcid *dcid, const uint8_t *token); ++void ngtcp2_dcid_set_token(ngtcp2_dcid *dcid, ++ const ngtcp2_stateless_reset_token *token); + + /* + * ngtcp2_dcid_set_path sets |path| to |dcid|. It sets +@@ -160,7 +161,8 @@ void ngtcp2_dcid_copy_cid_token(ngtcp2_dcid *dest, const ngtcp2_dcid *src); + * |token|) tuple against |dcid|. + */ + int ngtcp2_dcid_verify_uniqueness(const ngtcp2_dcid *dcid, uint64_t seq, +- const ngtcp2_cid *cid, const uint8_t *token); ++ const ngtcp2_cid *cid, ++ const ngtcp2_stateless_reset_token *token); + + /* + * ngtcp2_dcid_verify_stateless_reset_token verifies stateless reset +@@ -172,9 +174,9 @@ int ngtcp2_dcid_verify_uniqueness(const ngtcp2_dcid *dcid, uint64_t seq, + * NGTCP2_ERR_INVALID_ARGUMENT + * Tokens do not match; or |dcid| does not contain a token. + */ +-int ngtcp2_dcid_verify_stateless_reset_token(const ngtcp2_dcid *dcid, +- const ngtcp2_path *path, +- const uint8_t *token); ++int ngtcp2_dcid_verify_stateless_reset_token( ++ const ngtcp2_dcid *dcid, const ngtcp2_path *path, ++ const ngtcp2_stateless_reset_token *token); + + /* TODO It might be performance win if we store congestion state in + this entry, and restore it when migrate back to this path. */ +diff --git a/third_party/ngtcp2/lib/ngtcp2_conn.c b/third_party/ngtcp2/lib/ngtcp2_conn.c +index 6abebee33a8..c470eb05b06 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_conn.c ++++ b/third_party/ngtcp2/lib/ngtcp2_conn.c +@@ -39,8 +39,10 @@ + #include "ngtcp2_net.h" + #include "ngtcp2_transport_params.h" + #include "ngtcp2_settings.h" ++#include "ngtcp2_callbacks.h" + #include "ngtcp2_tstamp.h" + #include "ngtcp2_frame_chain.h" ++#include "ngtcp2_conn_info.h" + + /* NGTCP2_FLOW_WINDOW_RTT_FACTOR is the factor of RTT when flow + control window auto-tuning is triggered. */ +@@ -253,13 +255,20 @@ static int conn_call_extend_max_local_streams_uni(ngtcp2_conn *conn, + } + + static int conn_call_get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, +- uint8_t *token, size_t cidlen) { ++ ngtcp2_stateless_reset_token *token, ++ size_t cidlen) { + int rv; + +- assert(conn->callbacks.get_new_connection_id); ++ if (conn->callbacks.get_new_connection_id2) { ++ rv = conn->callbacks.get_new_connection_id2(conn, cid, token, cidlen, ++ conn->user_data); ++ } else { ++ assert(conn->callbacks.get_new_connection_id); ++ ++ rv = conn->callbacks.get_new_connection_id(conn, cid, token->data, cidlen, ++ conn->user_data); ++ } + +- rv = conn->callbacks.get_new_connection_id(conn, cid, token, cidlen, +- conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } +@@ -283,11 +292,38 @@ static int conn_call_remove_connection_id(ngtcp2_conn *conn, + return 0; + } + ++static int conn_call_begin_path_validation(ngtcp2_conn *conn, ++ const ngtcp2_pv *pv) { ++ int rv; ++ uint32_t flags = NGTCP2_PATH_VALIDATION_FLAG_NONE; ++ const ngtcp2_path *fallback_path = NULL; ++ ++ if (!pv || !conn->callbacks.begin_path_validation) { ++ return 0; ++ } ++ ++ if (pv->flags & NGTCP2_PV_FLAG_PREFERRED_ADDR) { ++ flags |= NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR; ++ } ++ ++ if (pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) { ++ fallback_path = &pv->fallback_dcid.ps.path; ++ } ++ ++ rv = conn->callbacks.begin_path_validation(conn, flags, &pv->dcid.ps.path, ++ fallback_path, conn->user_data); ++ if (rv != 0) { ++ return NGTCP2_ERR_CALLBACK_FAILURE; ++ } ++ ++ return 0; ++} ++ + static int conn_call_path_validation(ngtcp2_conn *conn, const ngtcp2_pv *pv, + ngtcp2_path_validation_result res) { + int rv; + uint32_t flags = NGTCP2_PATH_VALIDATION_FLAG_NONE; +- const ngtcp2_path *old_path = NULL; ++ const ngtcp2_path *fallback_path = NULL; + + if (!conn->callbacks.path_validation) { + return 0; +@@ -298,17 +334,17 @@ static int conn_call_path_validation(ngtcp2_conn *conn, const ngtcp2_pv *pv, + } + + if (pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) { +- old_path = &pv->fallback_dcid.ps.path; ++ fallback_path = &pv->fallback_dcid.ps.path; + } + +- if (conn->server && old_path && +- (ngtcp2_addr_cmp(&pv->dcid.ps.path.remote, &old_path->remote) & ++ if (conn->server && fallback_path && ++ (ngtcp2_addr_cmp(&pv->dcid.ps.path.remote, &fallback_path->remote) & + (NGTCP2_ADDR_CMP_FLAG_ADDR | NGTCP2_ADDR_CMP_FLAG_FAMILY))) { + flags |= NGTCP2_PATH_VALIDATION_FLAG_NEW_TOKEN; + } + +- rv = conn->callbacks.path_validation(conn, flags, &pv->dcid.ps.path, old_path, +- res, conn->user_data); ++ rv = conn->callbacks.path_validation(conn, flags, &pv->dcid.ps.path, ++ fallback_path, res, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } +@@ -395,14 +431,20 @@ static int conn_call_dcid_status(ngtcp2_conn *conn, + const ngtcp2_dcid *dcid) { + int rv; + +- if (!conn->callbacks.dcid_status) { ++ if (conn->callbacks.dcid_status2) { ++ rv = conn->callbacks.dcid_status2( ++ conn, type, dcid->seq, &dcid->cid, ++ (dcid->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) ? &dcid->token : NULL, ++ conn->user_data); ++ } else if (conn->callbacks.dcid_status) { ++ rv = conn->callbacks.dcid_status( ++ conn, type, dcid->seq, &dcid->cid, ++ (dcid->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) ? dcid->token.data : NULL, ++ conn->user_data); ++ } else { + return 0; + } + +- rv = conn->callbacks.dcid_status( +- conn, type, dcid->seq, &dcid->cid, +- (dcid->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) ? dcid->token : NULL, +- conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } +@@ -475,12 +517,19 @@ static int conn_call_client_initial(ngtcp2_conn *conn) { + return 0; + } + +-static int conn_call_get_path_challenge_data(ngtcp2_conn *conn, uint8_t *data) { ++static int conn_call_get_path_challenge_data(ngtcp2_conn *conn, ++ ngtcp2_path_challenge_data *data) { + int rv; + +- assert(conn->callbacks.get_path_challenge_data); ++ if (conn->callbacks.get_path_challenge_data2) { ++ rv = conn->callbacks.get_path_challenge_data2(conn, data, conn->user_data); ++ } else { ++ assert(conn->callbacks.get_path_challenge_data); ++ ++ rv = conn->callbacks.get_path_challenge_data(conn, data->data, ++ conn->user_data); ++ } + +- rv = conn->callbacks.get_path_challenge_data(conn, data, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } +@@ -521,14 +570,24 @@ static int conn_call_recv_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd) { + + static int + conn_call_recv_stateless_reset(ngtcp2_conn *conn, +- const ngtcp2_pkt_stateless_reset *sr) { ++ const ngtcp2_pkt_stateless_reset2 *sr) { + int rv; ++ ngtcp2_pkt_stateless_reset legacy_sr; + +- if (!conn->callbacks.recv_stateless_reset) { ++ if (conn->callbacks.recv_stateless_reset2) { ++ rv = conn->callbacks.recv_stateless_reset2(conn, sr, conn->user_data); ++ } else if (conn->callbacks.recv_stateless_reset) { ++ memcpy(legacy_sr.stateless_reset_token, sr->token.data, ++ sizeof(legacy_sr.stateless_reset_token)); ++ legacy_sr.rand = sr->rand; ++ legacy_sr.randlen = sr->randlen; ++ ++ rv = ++ conn->callbacks.recv_stateless_reset(conn, &legacy_sr, conn->user_data); ++ } else { + return 0; + } + +- rv = conn->callbacks.recv_stateless_reset(conn, sr, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } +@@ -668,13 +727,15 @@ static int conn_call_recv_tx_key(ngtcp2_conn *conn, + return 0; + } + ++/* ++ * pktns_init initializes |pktns|. It assumes that the object pointed ++ * by |pktns| is zero-cleared. ++ */ + static void pktns_init(ngtcp2_pktns *pktns, ngtcp2_pktns_id pktns_id, + ngtcp2_rst *rst, ngtcp2_cc *cc, int64_t initial_pkt_num, + ngtcp2_log *log, ngtcp2_qlog *qlog, + ngtcp2_objalloc *rtb_entry_objalloc, + ngtcp2_objalloc *frc_objalloc, const ngtcp2_mem *mem) { +- memset(pktns, 0, sizeof(*pktns)); +- + ngtcp2_gaptr_init(&pktns->rx.pngap, mem); + + pktns->tx.last_pkt_num = initial_pkt_num - 1; +@@ -696,7 +757,7 @@ static int pktns_new(ngtcp2_pktns **ppktns, ngtcp2_pktns_id pktns_id, + ngtcp2_log *log, ngtcp2_qlog *qlog, + ngtcp2_objalloc *rtb_entry_objalloc, + ngtcp2_objalloc *frc_objalloc, const ngtcp2_mem *mem) { +- *ppktns = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_pktns)); ++ *ppktns = ngtcp2_mem_calloc(mem, 1, sizeof(ngtcp2_pktns)); + if (*ppktns == NULL) { + return NGTCP2_ERR_NOMEM; + } +@@ -809,8 +870,8 @@ static void conn_reset_conn_stat_cc(ngtcp2_conn *conn, + */ + static void reset_conn_stat_recovery(ngtcp2_conn_stat *cstat) { + /* Initializes them with UINT64_MAX. */ +- memset(cstat->loss_time, 0xff, sizeof(cstat->loss_time)); +- memset(cstat->last_tx_pkt_ts, 0xff, sizeof(cstat->last_tx_pkt_ts)); ++ memset(cstat->loss_time, 0xFF, sizeof(cstat->loss_time)); ++ memset(cstat->last_tx_pkt_ts, 0xFF, sizeof(cstat->last_tx_pkt_ts)); + } + + /* +@@ -1033,16 +1094,31 @@ conn_set_local_transport_params(ngtcp2_conn *conn, + } + + static void conn_update_skip_pkt(ngtcp2_conn *conn, ngtcp2_pktns *pktns) { +- uint8_t gap; ++ const int64_t min_gap = 3; ++ uint8_t r; ++ int64_t gap; ++ ++ assert(INT64_MAX != pktns->tx.skip_pkt.next_pkt_num); ++ ++ conn->callbacks.rand(&r, 1, &conn->local.settings.rand_ctx); ++ ++ if (1LL << pktns->tx.skip_pkt.exponent > ++ (NGTCP2_MAX_PKT_NUM - min_gap) / ((int64_t)r + 1)) { ++ pktns->tx.skip_pkt.next_pkt_num = INT64_MAX; ++ return; ++ } ++ ++ gap = ((int64_t)r + 1) * (1LL << pktns->tx.skip_pkt.exponent++) + min_gap; + +- conn->callbacks.rand(&gap, 1, &conn->local.settings.rand_ctx); ++ if (pktns->tx.last_pkt_num > NGTCP2_MAX_PKT_NUM - gap) { ++ pktns->tx.skip_pkt.next_pkt_num = INT64_MAX; ++ return; ++ } + +- pktns->tx.skip_pkt.next_pkt_num = +- pktns->tx.last_pkt_num + 3 + +- (int64_t)gap * (1ll << pktns->tx.skip_pkt.exponent++); ++ pktns->tx.skip_pkt.next_pkt_num = pktns->tx.last_pkt_num + gap; + +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "next skip pkn=%" PRId64, +- pktns->tx.skip_pkt.next_pkt_num); ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_CON, "next skip pkn=%" PRId64, ++ pktns->tx.skip_pkt.next_pkt_num); + } + + static int conn_handle_skip_pkt(ngtcp2_conn *conn, ngtcp2_pktns *pktns, +@@ -1109,18 +1185,21 @@ static int conn_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid, + uint32_t *preferred_versions; + ngtcp2_settings settingsbuf; + ngtcp2_transport_params paramsbuf; +- (void)callbacks_version; ++ ngtcp2_callbacks callbacksbuf; ++ uint64_t seed; + (void)settings_version; + + settings = + ngtcp2_settings_convert_to_latest(&settingsbuf, settings_version, settings); + params = ngtcp2_transport_params_convert_to_latest( + ¶msbuf, transport_params_version, params); ++ callbacks = ngtcp2_callbacks_convert_to_latest(&callbacksbuf, ++ callbacks_version, callbacks); + + assert(settings->max_window <= NGTCP2_MAX_VARINT); + assert(settings->max_stream_window <= NGTCP2_MAX_VARINT); +- assert(settings->max_tx_udp_payload_size); +- assert(settings->max_tx_udp_payload_size <= NGTCP2_HARD_MAX_UDP_PAYLOAD_SIZE); ++ assert(settings->max_tx_udp_payload_size >= NGTCP2_MAX_UDP_PAYLOAD_SIZE); ++ assert(settings->max_tx_udp_payload_size <= NGTCP2_MAX_TX_UDP_PAYLOAD_SIZE); + assert(settings->initial_pkt_num <= INT32_MAX); + assert(settings->initial_rtt); + assert(params->active_connection_id_limit >= +@@ -1147,15 +1226,17 @@ static int conn_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid, + assert(callbacks->hp_mask); + assert(server || callbacks->recv_retry); + assert(callbacks->rand); +- assert(callbacks->get_new_connection_id); ++ assert(callbacks->get_new_connection_id2 || callbacks->get_new_connection_id); + assert(callbacks->update_key); + assert(callbacks->delete_crypto_aead_ctx); + assert(callbacks->delete_crypto_cipher_ctx); +- assert(callbacks->get_path_challenge_data); ++ assert(callbacks->get_path_challenge_data2 || ++ callbacks->get_path_challenge_data); + assert(!server || !ngtcp2_is_reserved_version(client_chosen_version)); + + for (i = 0; i < settings->pmtud_probeslen; ++i) { + assert(settings->pmtud_probes[i] > NGTCP2_MAX_UDP_PAYLOAD_SIZE); ++ assert(settings->pmtud_probes[i] <= NGTCP2_MAX_TX_UDP_PAYLOAD_SIZE); + } + + if (mem == NULL) { +@@ -1217,7 +1298,11 @@ static int conn_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid, + + ngtcp2_pq_init(&(*pconn)->scid.used, retired_ts_less, mem); + +- ngtcp2_map_init(&(*pconn)->strms, mem); ++ callbacks->rand((uint8_t *)&seed, sizeof(seed), &settings->rand_ctx); ++ ngtcp2_map_init(&(*pconn)->strms, seed, mem); ++ ++ callbacks->rand((uint8_t *)&seed, sizeof(seed), &settings->rand_ctx); ++ ngtcp2_pcg32_init(&(*pconn)->pcg, seed); + + ngtcp2_pq_init(&(*pconn)->tx.strmq, cycle_less, mem); + +@@ -1284,17 +1369,17 @@ static int conn_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid, + + switch (settings->cc_algo) { + case NGTCP2_CC_ALGO_RENO: +- ngtcp2_cc_reno_init(&(*pconn)->reno, &(*pconn)->log); ++ ngtcp2_cc_reno_init(&(*pconn)->reno, &(*pconn)->log, &(*pconn)->cstat); + + break; + case NGTCP2_CC_ALGO_CUBIC: +- ngtcp2_cc_cubic_init(&(*pconn)->cubic, &(*pconn)->log, &(*pconn)->rst); ++ ngtcp2_cc_cubic_init(&(*pconn)->cubic, &(*pconn)->log, &(*pconn)->cstat, ++ &(*pconn)->rst); + + break; + case NGTCP2_CC_ALGO_BBR: + ngtcp2_cc_bbr_init(&(*pconn)->bbr, &(*pconn)->log, &(*pconn)->cstat, +- &(*pconn)->rst, settings->initial_ts, callbacks->rand, +- &settings->rand_ctx); ++ &(*pconn)->rst, settings->initial_ts, &(*pconn)->pcg); + + break; + default: +@@ -1303,6 +1388,9 @@ static int conn_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid, + + ngtcp2_static_ringbuf_path_history_init(&(*pconn)->path_history); + ++ ngtcp2_ratelim_init(&(*pconn)->glitch_rlim, settings->glitch_ratelim_burst, ++ settings->glitch_ratelim_rate, settings->initial_ts); ++ + (*pconn)->callbacks = *callbacks; + + rv = pktns_new(&(*pconn)->in_pktns, NGTCP2_PKTNS_ID_INITIAL, &(*pconn)->rst, +@@ -1470,6 +1558,8 @@ fail_seqgap_push: + fail_token: + ngtcp2_mem_free(mem, *pconn); + ++ *pconn = NULL; ++ + return rv; + } + +@@ -1492,10 +1582,13 @@ int ngtcp2_conn_client_new_versioned( + (*pconn)->state = NGTCP2_CS_CLIENT_INITIAL; + (*pconn)->local.bidi.next_stream_id = 0; + (*pconn)->local.uni.next_stream_id = 2; ++ (*pconn)->flags |= NGTCP2_CONN_FLAG_CRUMBLE_INITIAL_CRYPTO; + + rv = ngtcp2_conn_commit_local_transport_params(*pconn); + if (rv != 0) { + ngtcp2_conn_del(*pconn); ++ *pconn = NULL; ++ + return rv; + } + +@@ -1753,8 +1846,18 @@ static int conn_ppe_write_frame(ngtcp2_conn *conn, ngtcp2_ppe *ppe, + static int conn_on_pkt_sent(ngtcp2_conn *conn, ngtcp2_pktns *pktns, + ngtcp2_rtb_entry *ent) { + ngtcp2_rtb *rtb = &pktns->rtb; ++ ngtcp2_cc_pkt cc_pkt; + int rv; + ++ if ((ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) && ++ conn->cc.on_pkt_sent) { ++ conn->cc.on_pkt_sent( ++ &conn->cc, &conn->cstat, ++ ngtcp2_cc_pkt_init(&cc_pkt, ent->hd.pkt_num, ent->pktlen, pktns->id, ++ ent->ts, /* lost = */ 0, ++ /* tx_in_flight = */ 0, /* is_app_limited = */ 0)); ++ } ++ + /* This function implements OnPacketSent, but it handles only + non-ACK-only packet. */ + rv = ngtcp2_rtb_add(rtb, ent, &conn->cstat); +@@ -1788,13 +1891,13 @@ static size_t pktns_select_pkt_numlen(ngtcp2_pktns *pktns) { + + n = n * 2 - 1; + +- if (n > 0xffffff) { ++ if (n > 0xFFFFFF) { + return 4; + } +- if (n > 0xffff) { ++ if (n > 0xFFFF) { + return 3; + } +- if (n > 0xff) { ++ if (n > 0xFF) { + return 2; + } + return 1; +@@ -1809,9 +1912,9 @@ static int conn_cwnd_is_zero(ngtcp2_conn *conn) { + uint64_t cwnd = conn->cstat.cwnd; + + if (bytes_in_flight >= cwnd) { +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_LDC, +- "cwnd limited bytes_in_flight=%lu cwnd=%lu", +- bytes_in_flight, cwnd); ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_LDC, ++ "cwnd limited bytes_in_flight=%lu cwnd=%lu", ++ bytes_in_flight, cwnd); + } + + return bytes_in_flight >= cwnd; +@@ -1902,6 +2005,16 @@ static int conn_verify_dcid(ngtcp2_conn *conn, int *pnew_cid_used, + return 0; + } + ++static int conn_can_send_next_pkt(ngtcp2_conn *conn, size_t left, ++ uint64_t min_payloadlen) { ++ /* TODO the next packet type should be taken into account */ ++ return left >= ++ /* TODO Assuming that pkt_num is encoded in 1 byte. */ ++ NGTCP2_MIN_LONG_HEADERLEN + conn->dcid.current.cid.datalen + ++ conn->oscid.datalen + NGTCP2_PKT_LENGTHLEN - 1 + min_payloadlen + ++ NGTCP2_MAX_AEAD_OVERHEAD; ++} ++ + /* + * conn_should_pad_pkt returns nonzero if the packet should be padded. + * |type| is the type of packet. |left| is the space left in packet +@@ -1948,26 +2061,30 @@ static int conn_should_pad_pkt(ngtcp2_conn *conn, uint8_t type, size_t left, + return 1; + } + } +- } else { +- assert(type == NGTCP2_PKT_HANDSHAKE); + +- if (!require_padding) { +- return 0; +- } ++ return !conn_can_send_next_pkt(conn, left, min_payloadlen); ++ } + +- if (!conn->pktns.crypto.tx.ckm) { +- return 1; +- } ++ assert(type == NGTCP2_PKT_HANDSHAKE); + +- min_payloadlen = NGTCP2_MIN_COALESCED_PAYLOADLEN; ++ if (!require_padding) { ++ /* If we have 1RTT key, pad this Handshake packet so that the next ++ 1RTT packet can be squeezed into the same GSO buffer. */ ++ return conn->pktns.crypto.tx.ckm && ++ !conn_can_send_next_pkt(conn, left, NGTCP2_MIN_COALESCED_PAYLOADLEN); + } + +- /* TODO the next packet type should be taken into account */ +- return left < +- /* TODO Assuming that pkt_num is encoded in 1 byte. */ +- NGTCP2_MIN_LONG_HEADERLEN + conn->dcid.current.cid.datalen + +- conn->oscid.datalen + NGTCP2_PKT_LENGTHLEN - 1 + min_payloadlen + +- NGTCP2_MAX_AEAD_OVERHEAD; ++ if (!conn->pktns.crypto.tx.ckm) { ++ return 1; ++ } ++ ++ /* We might send Handshake packet even if exceeding CWND. In that ++ case, we do not write non-probe 1RTT packet. */ ++ if (conn_cwnd_is_zero(conn) && conn->pktns.rtb.probe_pkt_left == 0) { ++ return 1; ++ } ++ ++ return !conn_can_send_next_pkt(conn, left, NGTCP2_MIN_COALESCED_PAYLOADLEN); + } + + static void conn_restart_timer_on_write(ngtcp2_conn *conn, ngtcp2_tstamp ts) { +@@ -2095,6 +2212,215 @@ static uint8_t conn_pkt_flags_short(ngtcp2_conn *conn) { + : NGTCP2_PKT_FLAG_NONE)); + } + ++/* ++ * conn_cut_crypto_frame splits frc->fr.stream by removing ++ * |removed_data| from frc->fr.stream.data[0]. frc->fr.stream.data[0] ++ * must contain |removed_data|, and frc->fr.stream.datacnt >= 1. New ++ * ngtcp2_frame_chain object that contains |removed_data| is created, ++ * and pushed to |crypto_strm| via ngtcp2_strm_streamfrq_push. If ++ * there are data following the removed part of data, new ++ * ngtcp2_frame_chain object is created for it, and frc->next points ++ * to the object. ++ * ++ * This function returns 0 if it succeeds, or one of the following ++ * negative error codes: ++ * ++ * NGTCP2_ERR_NOMEM ++ * Out of memory ++ */ ++static int conn_cut_crypto_frame(ngtcp2_conn *conn, ngtcp2_frame_chain *frc, ++ ngtcp2_strm *crypto_strm, ++ const ngtcp2_vec *removed_data) { ++ ngtcp2_vec *data = frc->fr.stream.data; ++ size_t datacnt = frc->fr.stream.datacnt; ++ size_t ndatacnt; ++ ngtcp2_frame_chain *right_frc = NULL, *removed_frc; ++ size_t offset; ++ int rv; ++ ++ assert(datacnt); ++ assert(data[0].base < removed_data->base); ++ assert(ngtcp2_vec_end(removed_data) <= ngtcp2_vec_end(&data[0])); ++ ++ offset = (size_t)(removed_data->base - data->base); ++ ++ rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new( ++ &removed_frc, 1, &conn->frc_objalloc, conn->mem); ++ if (rv != 0) { ++ return rv; ++ } ++ ++ /* ngtcp2_frame_chain for the removed data */ ++ removed_frc->fr.stream.type = NGTCP2_FRAME_CRYPTO; ++ removed_frc->fr.stream.flags = 0; ++ removed_frc->fr.stream.fin = 0; ++ removed_frc->fr.stream.stream_id = 0; ++ removed_frc->fr.stream.offset = frc->fr.stream.offset + offset; ++ removed_frc->fr.stream.datacnt = 1; ++ removed_frc->fr.stream.data[0] = (ngtcp2_vec){ ++ .base = data->base + offset, ++ .len = removed_data->len, ++ }; ++ ++ rv = ngtcp2_strm_streamfrq_push(crypto_strm, removed_frc); ++ if (rv != 0) { ++ ngtcp2_frame_chain_objalloc_del(removed_frc, &conn->frc_objalloc, ++ conn->mem); ++ return rv; ++ } ++ ++ if (data[0].len == offset + removed_data->len) { ++ ndatacnt = datacnt - 1; ++ } else { ++ ndatacnt = datacnt; ++ } ++ ++ if (ndatacnt) { ++ /* ngtcp2_frame_chain after the removed data */ ++ rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new( ++ &right_frc, ndatacnt, &conn->frc_objalloc, conn->mem); ++ if (rv != 0) { ++ return rv; ++ } ++ ++ right_frc->fr.stream.type = NGTCP2_FRAME_CRYPTO; ++ right_frc->fr.stream.offset = ++ removed_frc->fr.stream.offset + removed_frc->fr.stream.data->len; ++ right_frc->fr.stream.datacnt = 0; ++ ngtcp2_vec_split(right_frc->fr.stream.data, &right_frc->fr.stream.datacnt, ++ data, &datacnt, offset + removed_data->len, ndatacnt); ++ ++ assert(ndatacnt == right_frc->fr.stream.datacnt); ++ assert(1 == datacnt); ++ } ++ ++ frc->fr.stream.datacnt = 1; ++ frc->fr.stream.data[0].len = offset; ++ frc->next = right_frc; ++ ++ return 0; ++} ++ ++/* ++ * conn_crumble_initial_crypto splits CRYPTO frame (*pfrc)->fr.stream ++ * into pieces and adds PADDING and PING frames, and reorder those ++ * frames. Those frames are encoded in the buffer pointed by |data| ++ * and |offsets|. |data| is the pointer to the array of ngtcp2_vec of ++ * at least NGTCP2_MAX_STREAM_DATACNT. |offsets| contains the CRYPTO ++ * offset of the corresponding ngtcp2_vec in |data|, and it also ++ * should have the capacity at least NGTCP2_MAX_STREAM_DATACNT ++ * uint64_t. |left| is the number of bytes available for the current ++ * packet. |crypto_offset| is the next smallest CRYPTO offset. ++ * |crypto_strm| is the CRYPTO stream. ++ * ++ * This function returns the number of objects written to |data| and ++ * |offsets|, or one of the following negative error codes: ++ * ++ * NGTCP2_ERR_NOMEM ++ * Out of memory ++ */ ++static ngtcp2_ssize ++conn_crumble_initial_crypto(ngtcp2_conn *conn, ngtcp2_frame_chain **pfrc, ++ ngtcp2_vec *data, uint64_t *offsets, ++ ngtcp2_strm *crypto_strm, size_t left, ++ uint64_t crypto_offset) { ++ ngtcp2_vec server_name; ++ ngtcp2_vec removed_data; ++ size_t max_add_frames = 10; ++ size_t single_crypto_overhead = ++ 1 + ngtcp2_put_uvarintlen(crypto_offset + left - 1) + ++ ngtcp2_put_uvarintlen(left); ++ size_t total_crypto_overhead = single_crypto_overhead * max_add_frames; ++ size_t datacnt; ++ size_t i; ++ int rv; ++ ++ if (left <= total_crypto_overhead) { ++ return 0; ++ } ++ ++ left -= total_crypto_overhead; ++ ++ left = ngtcp2_pkt_crypto_max_datalen(crypto_offset, left, left); ++ if (left == (size_t)-1) { ++ return 0; ++ } ++ ++ rv = ngtcp2_strm_streamfrq_pop(crypto_strm, pfrc, left); ++ if (rv != 0) { ++ assert(ngtcp2_err_is_fatal(rv)); ++ return rv; ++ } ++ ++ if (*pfrc == NULL) { ++ return 0; ++ } ++ ++ assert(crypto_offset == (*pfrc)->fr.stream.offset); ++ ++ ngtcp2_vec_copy(data, (*pfrc)->fr.stream.data, (*pfrc)->fr.stream.datacnt); ++ datacnt = (*pfrc)->fr.stream.datacnt; ++ ++ offsets[0] = (*pfrc)->fr.stream.offset; ++ ++ for (i = 1; i < datacnt; ++i) { ++ offsets[i] = offsets[i - 1] + data[i - 1].len; ++ } ++ ++ if (datacnt < NGTCP2_MAX_STREAM_DATACNT && ++ ngtcp2_pkt_find_server_name(&server_name, data) && server_name.len > 1) { ++ if (ngtcp2_strm_streamfrq_empty(crypto_strm) || ++ ngtcp2_strm_streamfrq_unacked_offset(crypto_strm) == (uint64_t)-1) { ++ datacnt = ngtcp2_pkt_split_vec_at( ++ data, datacnt, offsets, ++ (size_t)(server_name.base - data[0].base) + server_name.len / 2); ++ } else { ++ /* If we have another data to send (most likely in the another ++ packet), remove the part of SNI from this packet. */ ++ datacnt = ngtcp2_pkt_remove_vec_partial( ++ &removed_data, data, datacnt, offsets, &conn->pcg, &server_name); ++ ++ rv = conn_cut_crypto_frame(conn, *pfrc, crypto_strm, &removed_data); ++ if (rv != 0) { ++ ngtcp2_frame_chain_objalloc_del(*pfrc, &conn->frc_objalloc, conn->mem); ++ return rv; ++ } ++ ++ /* Add the length of removed data to total_crypto_overhead so ++ that we can use them for inter CRYPTO frames padding. */ ++ total_crypto_overhead += removed_data.len; ++ } ++ } ++ ++ if (datacnt < max_add_frames + 1) { ++ max_add_frames -= datacnt - 1; ++ ++ datacnt = ngtcp2_pkt_split_vec_rand(data, datacnt, offsets, &conn->pcg, ++ max_add_frames); ++ } ++ ++ for (i = 1; i < datacnt; ++i) { ++ total_crypto_overhead -= 1 + ngtcp2_put_uvarintlen(offsets[i]) + ++ ngtcp2_put_uvarintlen(data[i].len); ++ } ++ ++ datacnt = ngtcp2_pkt_append_ping_and_padding(data, datacnt, &conn->pcg, ++ total_crypto_overhead); ++ ++ ngtcp2_pkt_permutate_vec(data, datacnt, offsets, &conn->pcg); ++ ++ return (ngtcp2_ssize)datacnt; ++} ++ ++static size_t conn_dgram_padding(ngtcp2_conn *conn, ngtcp2_ppe *ppe) { ++ if (conn->local.settings.no_tx_udp_payload_size_shaping) { ++ return ngtcp2_ppe_dgram_padding_size( ++ ppe, conn->local.settings.max_tx_udp_payload_size); ++ } ++ ++ return ngtcp2_ppe_dgram_padding(ppe); ++} ++ + static size_t conn_min_pktlen(ngtcp2_conn *conn); + + /* +@@ -2125,8 +2451,8 @@ conn_write_handshake_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, + ngtcp2_pkt_hd hd; + ngtcp2_frame_chain *frq = NULL, **pfrc = &frq; + ngtcp2_frame_chain *nfrc; +- ngtcp2_max_frame mfr; +- ngtcp2_frame *ackfr = NULL, lfr; ++ ngtcp2_ack_range ack_ranges[NGTCP2_MAX_ACK_RANGES]; ++ ngtcp2_frame lfr; + ngtcp2_ssize spktlen; + ngtcp2_crypto_cc cc; + ngtcp2_rtb_entry *rtbent; +@@ -2201,16 +2527,16 @@ conn_write_handshake_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, + return 0; + } + +- ackfr = ngtcp2_acktr_create_ack_frame(&pktns->acktr, &mfr.fr, type, ts, +- /* ack_delay = */ 0, +- NGTCP2_DEFAULT_ACK_DELAY_EXPONENT); +- if (ackfr) { +- rv = conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd, ackfr); ++ lfr.ack.ranges = ack_ranges; ++ if (ngtcp2_acktr_create_ack_frame(&pktns->acktr, &lfr.ack, type, ts, ++ /* ack_delay = */ 0, ++ NGTCP2_DEFAULT_ACK_DELAY_EXPONENT) == 0) { ++ rv = conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd, &lfr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + } else { + ngtcp2_acktr_commit_ack(&pktns->acktr); +- ngtcp2_acktr_add_ack(&pktns->acktr, hd.pkt_num, ackfr->ack.largest_ack); ++ ngtcp2_acktr_add_ack(&pktns->acktr, hd.pkt_num, lfr.ack.largest_ack); + pkt_empty = 0; + } + } +@@ -2221,38 +2547,94 @@ conn_write_handshake_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, + destlen >= NGTCP2_MAX_UDP_PAYLOAD_SIZE) { + build_pkt: + for (; !ngtcp2_strm_streamfrq_empty(&pktns->crypto.strm);) { +- left = ngtcp2_ppe_left(&ppe); +- + crypto_offset = ngtcp2_strm_streamfrq_unacked_offset(&pktns->crypto.strm); + if (crypto_offset == (uint64_t)-1) { + ngtcp2_strm_streamfrq_clear(&pktns->crypto.strm); + break; + } + +- left = ngtcp2_pkt_crypto_max_datalen(crypto_offset, left, left); +- if (left == (size_t)-1) { ++ left = ngtcp2_ppe_left(&ppe); ++ if (left == 0) { + break; + } + +- rv = ngtcp2_strm_streamfrq_pop(&pktns->crypto.strm, &nfrc, left); +- if (rv != 0) { +- assert(ngtcp2_err_is_fatal(rv)); +- ngtcp2_frame_chain_list_objalloc_del(frq, &conn->frc_objalloc, +- conn->mem); +- return rv; +- } ++ if (type == NGTCP2_PKT_INITIAL && ++ (conn->flags & NGTCP2_CONN_FLAG_CRUMBLE_INITIAL_CRYPTO)) { ++ ngtcp2_vec data[NGTCP2_MAX_STREAM_DATACNT]; ++ uint64_t offsets[NGTCP2_MAX_STREAM_DATACNT]; ++ ngtcp2_ssize datacnt; ++ size_t i; + +- if (nfrc == NULL) { +- break; +- } ++ datacnt = conn_crumble_initial_crypto( ++ conn, &nfrc, data, offsets, &pktns->crypto.strm, left, crypto_offset); ++ if (datacnt < 0) { ++ assert(ngtcp2_err_is_fatal((int)datacnt)); ++ ngtcp2_frame_chain_list_objalloc_del(frq, &conn->frc_objalloc, ++ conn->mem); + +- rv = conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd, &nfrc->fr); +- if (rv != 0) { +- ngtcp2_unreachable(); ++ return datacnt; ++ } ++ ++ if (datacnt == 0) { ++ break; ++ } ++ ++ for (i = 0; i < (size_t)datacnt; ++i) { ++ if (data[i].base == NULL) { ++ if (data[i].len == 0) { ++ lfr.ping.type = NGTCP2_FRAME_PING; ++ } else { ++ lfr.padding = (ngtcp2_padding){ ++ .type = NGTCP2_FRAME_PADDING, ++ .len = data[i].len, ++ }; ++ } ++ } else { ++ lfr.stream = (ngtcp2_stream){ ++ .type = NGTCP2_FRAME_CRYPTO, ++ .offset = offsets[i], ++ .datacnt = 1, ++ .data = &data[i], ++ }; ++ } ++ ++ rv = conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd, &lfr); ++ if (rv != 0) { ++ ngtcp2_unreachable(); ++ } ++ } ++ } else { ++ left = ngtcp2_pkt_crypto_max_datalen(crypto_offset, left, left); ++ if (left == (size_t)-1) { ++ break; ++ } ++ ++ rv = ngtcp2_strm_streamfrq_pop(&pktns->crypto.strm, &nfrc, left); ++ if (rv != 0) { ++ assert(ngtcp2_err_is_fatal(rv)); ++ ngtcp2_frame_chain_list_objalloc_del(frq, &conn->frc_objalloc, ++ conn->mem); ++ return rv; ++ } ++ ++ if (nfrc == NULL) { ++ break; ++ } ++ ++ rv = ++ conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd, &nfrc->fr); ++ if (rv != 0) { ++ ngtcp2_unreachable(); ++ } + } + + *pfrc = nfrc; +- pfrc = &(*pfrc)->next; ++ ++ for (; nfrc->next;) { ++ nfrc = nfrc->next; ++ } ++ ++ pfrc = &nfrc->next; + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | +@@ -2262,7 +2644,9 @@ conn_write_handshake_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, + + if (!(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) && + pktns->rtb.num_retransmittable && pktns->rtb.probe_pkt_left) { +- num_reclaimed = ngtcp2_rtb_reclaim_on_pto(&pktns->rtb, conn, pktns, 1); ++ num_reclaimed = ngtcp2_rtb_reclaim_on_pto( ++ &pktns->rtb, conn, pktns, ++ !conn->server && type == NGTCP2_PKT_INITIAL ? 2 : 1); + if (num_reclaimed < 0) { + ngtcp2_frame_chain_list_objalloc_del(frq, &conn->frc_objalloc, + conn->mem); +@@ -2289,7 +2673,7 @@ conn_write_handshake_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, + + if (!(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) && + pktns->rtb.probe_pkt_left) { +- lfr.type = NGTCP2_FRAME_PING; ++ lfr.ping.type = NGTCP2_FRAME_PING; + + rv = conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd, &lfr); + if (rv != 0) { +@@ -2305,7 +2689,7 @@ conn_write_handshake_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, + if (!(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) { + if (ngtcp2_tstamp_elapsed(pktns->tx.non_ack_pkt_start_ts, + conn->cstat.smoothed_rtt, ts)) { +- lfr.type = NGTCP2_FRAME_PING; ++ lfr.ping.type = NGTCP2_FRAME_PING; + + rv = conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd, &lfr); + if (rv != 0) { +@@ -2334,12 +2718,12 @@ conn_write_handshake_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, + conn, type, ngtcp2_ppe_left(&ppe), write_datalen, + (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) != 0, + require_padding)) { +- lfr.type = NGTCP2_FRAME_PADDING; +- lfr.padding.len = ngtcp2_ppe_dgram_padding(&ppe); ++ lfr.padding.type = NGTCP2_FRAME_PADDING; ++ lfr.padding.len = conn_dgram_padding(conn, &ppe); + } else if (pkt_empty) { + return 0; + } else { +- lfr.type = NGTCP2_FRAME_PADDING; ++ lfr.padding.type = NGTCP2_FRAME_PADDING; + lfr.padding.len = ngtcp2_ppe_padding_size(&ppe, conn_min_pktlen(conn)); + min_padded = 1; + } +@@ -2402,6 +2786,9 @@ conn_write_handshake_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, + + conn->tx.pacing.pktlen += (size_t)spktlen; + ++ ++conn->cstat.pkt_sent; ++ conn->cstat.bytes_sent += (uint64_t)spktlen; ++ + ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat); + + ++pktns->tx.last_pkt_num; +@@ -2425,12 +2812,12 @@ conn_write_handshake_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, + static ngtcp2_ssize conn_write_ack_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + uint8_t type, ngtcp2_tstamp ts) { +- ngtcp2_frame *ackfr; + ngtcp2_pktns *pktns; + ngtcp2_duration ack_delay; + uint64_t ack_delay_exponent; + ngtcp2_ssize spktlen; +- ngtcp2_max_frame mfr; ++ ngtcp2_ack_range ack_ranges[NGTCP2_MAX_ACK_RANGES]; ++ ngtcp2_frame fr; + + assert(!(conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING)); + +@@ -2459,15 +2846,15 @@ static ngtcp2_ssize conn_write_ack_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + return 0; + } + +- ackfr = ngtcp2_acktr_create_ack_frame(&pktns->acktr, &mfr.fr, type, ts, +- ack_delay, ack_delay_exponent); +- if (!ackfr) { ++ fr.ack.ranges = ack_ranges; ++ if (ngtcp2_acktr_create_ack_frame(&pktns->acktr, &fr.ack, type, ts, ack_delay, ++ ack_delay_exponent) != 0) { + return 0; + } + + spktlen = ngtcp2_conn_write_single_frame_pkt( + conn, pi, dest, destlen, type, NGTCP2_WRITE_PKT_FLAG_NONE, +- &conn->dcid.current.cid, ackfr, NGTCP2_RTB_ENTRY_FLAG_NONE, NULL, ts); ++ &conn->dcid.current.cid, &fr, NGTCP2_RTB_ENTRY_FLAG_NONE, NULL, ts); + + if (spktlen <= 0) { + return spktlen; +@@ -2540,7 +2927,7 @@ static void conn_discard_early_key(ngtcp2_conn *conn) { + + conn_call_delete_crypto_aead_ctx(conn, &conn->early.ckm->aead_ctx); + conn_call_delete_crypto_cipher_ctx(conn, &conn->early.hp_ctx); +- memset(&conn->early.hp_ctx, 0, sizeof(conn->early.hp_ctx)); ++ conn->early.hp_ctx = (ngtcp2_crypto_cipher_ctx){0}; + + ngtcp2_crypto_km_del(conn->early.ckm, conn->mem); + conn->early.ckm = NULL; +@@ -2730,21 +3117,16 @@ static ngtcp2_ssize conn_write_handshake_pkts(ngtcp2_conn *conn, + dest += nwrite; + destlen -= (size_t)nwrite; + +- /* If initial packet size is at least +- NGTCP2_MAX_UDP_PAYLOAD_SIZE, no extra padding is needed in a +- subsequent packet. */ +- if (nwrite < NGTCP2_MAX_UDP_PAYLOAD_SIZE) { +- if (conn->server) { +- it = ngtcp2_rtb_head(&conn->in_pktns->rtb); +- if (!ngtcp2_ksl_it_end(&it)) { +- rtbent = ngtcp2_ksl_it_get(&it); +- if (rtbent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) { +- wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; +- } ++ if (conn->server) { ++ it = ngtcp2_rtb_head(&conn->in_pktns->rtb); ++ if (!ngtcp2_ksl_it_end(&it)) { ++ rtbent = ngtcp2_ksl_it_get(&it); ++ if (rtbent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) { ++ wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; + } +- } else { +- wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; + } ++ } else { ++ wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; + } + } + } +@@ -2862,58 +3244,59 @@ static size_t conn_required_num_new_connection_id(ngtcp2_conn *conn) { + static int conn_enqueue_new_connection_id(ngtcp2_conn *conn) { + size_t i, need = conn_required_num_new_connection_id(conn); + size_t cidlen = conn->oscid.datalen; +- ngtcp2_cid cid; +- uint64_t seq; + int rv; +- uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN]; + ngtcp2_frame_chain *nfrc; + ngtcp2_pktns *pktns = &conn->pktns; + ngtcp2_scid *scid; + ngtcp2_ksl_it it; + + for (i = 0; i < need; ++i) { +- rv = conn_call_get_new_connection_id(conn, &cid, token, cidlen); ++ rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + return rv; + } + +- if (cid.datalen != cidlen) { +- return NGTCP2_ERR_CALLBACK_FAILURE; ++ nfrc->fr.new_connection_id.type = NGTCP2_FRAME_NEW_CONNECTION_ID; ++ nfrc->fr.new_connection_id.seq = ++conn->scid.last_seq; ++ nfrc->fr.new_connection_id.retire_prior_to = 0; ++ ++ rv = conn_call_get_new_connection_id(conn, &nfrc->fr.new_connection_id.cid, ++ &nfrc->fr.new_connection_id.token, ++ cidlen); ++ if (rv != 0) { ++ goto fail; ++ } ++ ++ if (nfrc->fr.new_connection_id.cid.datalen != cidlen) { ++ rv = NGTCP2_ERR_CALLBACK_FAILURE; ++ goto fail; + } + + /* Assert uniqueness */ +- it = ngtcp2_ksl_lower_bound(&conn->scid.set, &cid); ++ it = ++ ngtcp2_ksl_lower_bound(&conn->scid.set, &nfrc->fr.new_connection_id.cid); + if (!ngtcp2_ksl_it_end(&it) && +- ngtcp2_cid_eq(ngtcp2_ksl_it_key(&it), &cid)) { +- return NGTCP2_ERR_CALLBACK_FAILURE; ++ ngtcp2_cid_eq(ngtcp2_ksl_it_key(&it), ++ &nfrc->fr.new_connection_id.cid)) { ++ rv = NGTCP2_ERR_CALLBACK_FAILURE; ++ goto fail; + } + +- seq = ++conn->scid.last_seq; +- + scid = ngtcp2_mem_malloc(conn->mem, sizeof(*scid)); + if (scid == NULL) { +- return NGTCP2_ERR_NOMEM; ++ rv = NGTCP2_ERR_NOMEM; ++ goto fail; + } + +- ngtcp2_scid_init(scid, seq, &cid); ++ ngtcp2_scid_init(scid, nfrc->fr.new_connection_id.seq, ++ &nfrc->fr.new_connection_id.cid); + + rv = ngtcp2_ksl_insert(&conn->scid.set, NULL, &scid->cid, scid); + if (rv != 0) { + ngtcp2_mem_free(conn->mem, scid); +- return rv; +- } +- +- rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); +- if (rv != 0) { +- return rv; ++ goto fail; + } + +- nfrc->fr.type = NGTCP2_FRAME_NEW_CONNECTION_ID; +- nfrc->fr.new_connection_id.seq = seq; +- nfrc->fr.new_connection_id.retire_prior_to = 0; +- nfrc->fr.new_connection_id.cid = cid; +- memcpy(nfrc->fr.new_connection_id.stateless_reset_token, token, +- sizeof(token)); + nfrc->next = pktns->tx.frq; + pktns->tx.frq = nfrc; + +@@ -2923,6 +3306,11 @@ static int conn_enqueue_new_connection_id(ngtcp2_conn *conn) { + } + + return 0; ++ ++fail: ++ ngtcp2_frame_chain_objalloc_del(nfrc, &conn->frc_objalloc, conn->mem); ++ ++ return rv; + } + + static int dcidtr_on_deactivate(const ngtcp2_dcid *dcid, void *user_data) { +@@ -3082,14 +3470,15 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + ngtcp2_crypto_cc *cc = &conn->pkt.cc; + ngtcp2_ppe *ppe = &conn->pkt.ppe; + ngtcp2_pkt_hd *hd = &conn->pkt.hd; +- ngtcp2_max_frame mfr; +- ngtcp2_frame *ackfr = NULL, lfr; ++ ngtcp2_ack_range ack_ranges[NGTCP2_MAX_ACK_RANGES]; ++ ngtcp2_frame lfr; + ngtcp2_ssize nwrite; + ngtcp2_frame_chain **pfrc, *nfrc, *frc; + ngtcp2_rtb_entry *ent; + ngtcp2_strm *strm; + int pkt_empty = 1; + uint64_t ndatalen = 0; ++ uint64_t wdatalen; + int send_stream = 0; + int stream_blocked = 0; + int send_datagram = 0; +@@ -3108,11 +3497,9 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + size_t min_pktlen = conn_min_pktlen(conn); + int min_padded = 0; + int padded = 0; +- ngtcp2_cc_pkt cc_pkt; + uint64_t crypto_offset; + uint64_t stream_offset; + ngtcp2_ssize num_reclaimed; +- int fin; + uint64_t target_max_data; + ngtcp2_conn_stat *cstat = &conn->cstat; + uint64_t delta; +@@ -3225,8 +3612,10 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + + conn->tx.last_max_data_ts = ts; + +- nfrc->fr.type = NGTCP2_FRAME_MAX_DATA; +- nfrc->fr.max_data.max_data = conn->rx.unsent_max_offset + delta; ++ nfrc->fr.max_data = (ngtcp2_max_data){ ++ .type = NGTCP2_FRAME_MAX_DATA, ++ .max_data = conn->rx.unsent_max_offset + delta, ++ }; + nfrc->next = pktns->tx.frq; + pktns->tx.frq = nfrc; + +@@ -3261,9 +3650,10 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + /* PATH_RESPONSE is bound to the path that the corresponding + PATH_CHALLENGE is received. */ + if (ngtcp2_path_eq(&conn->dcid.current.ps.path, &pcent->ps.path)) { +- lfr.type = NGTCP2_FRAME_PATH_RESPONSE; +- memcpy(lfr.path_response.data, pcent->data, +- sizeof(lfr.path_response.data)); ++ lfr.path_response = (ngtcp2_path_response){ ++ .type = NGTCP2_FRAME_PATH_RESPONSE, ++ .data = pcent->data, ++ }; + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &lfr); + if (rv != 0) { +@@ -3282,7 +3672,7 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + + An endpoint that receives a PATH_CHALLENGE on an active + path SHOULD send a non-probing packet in response. */ +- lfr.type = NGTCP2_FRAME_PING; ++ lfr.ping.type = NGTCP2_FRAME_PING; + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &lfr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); +@@ -3291,20 +3681,19 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + } + } + +- ackfr = ngtcp2_acktr_create_ack_frame( +- &pktns->acktr, &mfr.fr, type, ts, conn_compute_ack_delay(conn), +- conn->local.transport_params.ack_delay_exponent); +- if (ackfr) { +- rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, ackfr); ++ lfr.ack.ranges = ack_ranges; ++ if (ngtcp2_acktr_create_ack_frame( ++ &pktns->acktr, &lfr.ack, type, ts, conn_compute_ack_delay(conn), ++ conn->local.transport_params.ack_delay_exponent) == 0) { ++ rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &lfr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + } else { + ngtcp2_acktr_commit_ack(&pktns->acktr); +- ngtcp2_acktr_add_ack(&pktns->acktr, hd->pkt_num, +- ackfr->ack.largest_ack); ++ ngtcp2_acktr_add_ack(&pktns->acktr, hd->pkt_num, lfr.ack.largest_ack); + assert(NGTCP2_PKT_1RTT == type); +- conn_handle_unconfirmed_key_update_from_remote( +- conn, ackfr->ack.largest_ack, ts); ++ conn_handle_unconfirmed_key_update_from_remote(conn, ++ lfr.ack.largest_ack, ts); + pkt_empty = 0; + } + } +@@ -3319,7 +3708,7 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + continue; + } + +- switch ((*pfrc)->fr.type) { ++ switch ((*pfrc)->fr.hd.type) { + case NGTCP2_FRAME_RESET_STREAM: + strm = + ngtcp2_conn_find_stream(conn, (*pfrc)->fr.reset_stream.stream_id); +@@ -3469,11 +3858,12 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + return rv; + } + +- nfrc->fr.type = NGTCP2_FRAME_RESET_STREAM; +- nfrc->fr.reset_stream.stream_id = strm->stream_id; +- nfrc->fr.reset_stream.app_error_code = +- strm->tx.reset_stream_app_error_code; +- nfrc->fr.reset_stream.final_size = strm->tx.offset; ++ nfrc->fr.reset_stream = (ngtcp2_reset_stream){ ++ .type = NGTCP2_FRAME_RESET_STREAM, ++ .stream_id = strm->stream_id, ++ .app_error_code = strm->tx.reset_stream_app_error_code, ++ .final_size = strm->tx.offset, ++ }; + *pfrc = nfrc; + + strm->flags &= ~NGTCP2_STRM_FLAG_SEND_RESET_STREAM; +@@ -3512,10 +3902,11 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + return rv; + } + +- nfrc->fr.type = NGTCP2_FRAME_STOP_SENDING; +- nfrc->fr.stop_sending.stream_id = strm->stream_id; +- nfrc->fr.stop_sending.app_error_code = +- strm->tx.stop_sending_app_error_code; ++ nfrc->fr.stop_sending = (ngtcp2_stop_sending){ ++ .type = NGTCP2_FRAME_STOP_SENDING, ++ .stream_id = strm->stream_id, ++ .app_error_code = strm->tx.stop_sending_app_error_code, ++ }; + *pfrc = nfrc; + + strm->flags &= ~NGTCP2_STRM_FLAG_SEND_STOP_SENDING; +@@ -3543,9 +3934,11 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + return rv; + } + +- nfrc->fr.type = NGTCP2_FRAME_STREAM_DATA_BLOCKED; +- nfrc->fr.stream_data_blocked.stream_id = strm->stream_id; +- nfrc->fr.stream_data_blocked.offset = strm->tx.max_offset; ++ nfrc->fr.stream_data_blocked = (ngtcp2_stream_data_blocked){ ++ .type = NGTCP2_FRAME_STREAM_DATA_BLOCKED, ++ .stream_id = strm->stream_id, ++ .offset = strm->tx.max_offset, ++ }; + *pfrc = nfrc; + + strm->tx.last_blocked_offset = strm->tx.max_offset; +@@ -3597,10 +3990,11 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + + strm->tx.last_max_stream_data_ts = ts; + +- nfrc->fr.type = NGTCP2_FRAME_MAX_STREAM_DATA; +- nfrc->fr.max_stream_data.stream_id = strm->stream_id; +- nfrc->fr.max_stream_data.max_stream_data = +- strm->rx.unsent_max_offset + delta; ++ nfrc->fr.max_stream_data = (ngtcp2_max_stream_data){ ++ .type = NGTCP2_FRAME_MAX_STREAM_DATA, ++ .stream_id = strm->stream_id, ++ .max_stream_data = strm->rx.unsent_max_offset + delta, ++ }; + *pfrc = nfrc; + + strm->rx.max_offset = strm->rx.unsent_max_offset = +@@ -3696,8 +4090,10 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } +- nfrc->fr.type = NGTCP2_FRAME_MAX_STREAMS_BIDI; +- nfrc->fr.max_streams.max_streams = conn->remote.bidi.unsent_max_streams; ++ nfrc->fr.max_streams = (ngtcp2_max_streams){ ++ .type = NGTCP2_FRAME_MAX_STREAMS_BIDI, ++ .max_streams = conn->remote.bidi.unsent_max_streams, ++ }; + *pfrc = nfrc; + + conn->remote.bidi.max_streams = conn->remote.bidi.unsent_max_streams; +@@ -3728,8 +4124,10 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } +- nfrc->fr.type = NGTCP2_FRAME_MAX_STREAMS_UNI; +- nfrc->fr.max_streams.max_streams = conn->remote.uni.unsent_max_streams; ++ nfrc->fr.max_streams = (ngtcp2_max_streams){ ++ .type = NGTCP2_FRAME_MAX_STREAMS_UNI, ++ .max_streams = conn->remote.uni.unsent_max_streams, ++ }; + *pfrc = nfrc; + + conn->remote.uni.max_streams = conn->remote.uni.unsent_max_streams; +@@ -3780,10 +4178,12 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + left = ngtcp2_ppe_left(ppe); + + if (*pfrc == NULL && send_stream && ngtcp2_pq_empty(&conn->tx.strmq) && +- (ndatalen = ngtcp2_pkt_stream_max_datalen( ++ (wdatalen = ngtcp2_pkt_stream_max_datalen( + vmsg->stream.strm->stream_id, vmsg->stream.strm->tx.offset, ndatalen, + left)) != (size_t)-1 && +- (ndatalen || datalen == 0)) { ++ (wdatalen == ndatalen || wdatalen >= NGTCP2_MIN_STREAM_DATALEN) && ++ (wdatalen || datalen == 0)) { ++ ndatalen = wdatalen; + datacnt = ngtcp2_vec_copy_at_most(data, NGTCP2_MAX_STREAM_DATACNT, + vmsg->stream.data, vmsg->stream.datacnt, + (size_t)ndatalen); +@@ -3800,14 +4200,14 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + + nfrc->fr.stream.type = NGTCP2_FRAME_STREAM; + nfrc->fr.stream.flags = 0; ++ nfrc->fr.stream.fin = 0; + nfrc->fr.stream.stream_id = vmsg->stream.strm->stream_id; + nfrc->fr.stream.offset = vmsg->stream.strm->tx.offset; + nfrc->fr.stream.datacnt = datacnt; + ngtcp2_vec_copy(nfrc->fr.stream.data, data, datacnt); + +- fin = (vmsg->stream.flags & NGTCP2_WRITE_STREAM_FLAG_FIN) && +- ndatalen == datalen; +- nfrc->fr.stream.fin = (uint8_t)fin; ++ nfrc->fr.stream.fin = (vmsg->stream.flags & NGTCP2_WRITE_STREAM_FLAG_FIN) && ++ ndatalen == datalen; + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr); + if (rv != 0) { +@@ -3826,7 +4226,7 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + conn->tx.offset += ndatalen; + vmsg->stream.strm->flags |= NGTCP2_STRM_FLAG_ANY_SENT; + +- if (fin) { ++ if (nfrc->fr.stream.fin) { + ngtcp2_strm_shutdown(vmsg->stream.strm, NGTCP2_STRM_FLAG_SHUT_WR); + } + +@@ -3849,8 +4249,10 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + return rv; + } + +- nfrc->fr.type = NGTCP2_FRAME_DATA_BLOCKED; +- nfrc->fr.data_blocked.offset = conn->tx.offset; ++ nfrc->fr.data_blocked = (ngtcp2_data_blocked){ ++ .type = NGTCP2_FRAME_DATA_BLOCKED, ++ .offset = conn->tx.offset, ++ }; + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr); + if (rv != 0) { +@@ -3881,9 +4283,11 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + return rv; + } + +- nfrc->fr.type = NGTCP2_FRAME_STREAM_DATA_BLOCKED; +- nfrc->fr.stream_data_blocked.stream_id = strm->stream_id; +- nfrc->fr.stream_data_blocked.offset = strm->tx.max_offset; ++ nfrc->fr.stream_data_blocked = (ngtcp2_stream_data_blocked){ ++ .type = NGTCP2_FRAME_STREAM_DATA_BLOCKED, ++ .stream_id = strm->stream_id, ++ .offset = strm->tx.max_offset, ++ }; + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr); + if (rv != 0) { +@@ -4026,7 +4430,7 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + if (ngtcp2_tstamp_elapsed(pktns->tx.non_ack_pkt_start_ts, + cstat->smoothed_rtt, ts) || + keep_alive_expired || conn->pktns.rtb.probe_pkt_left) { +- lfr.type = NGTCP2_FRAME_PING; ++ lfr.ping.type = NGTCP2_FRAME_PING; + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &lfr); + if (rv != 0) { +@@ -4041,7 +4445,6 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING; + } + pktns->tx.non_ack_pkt_start_ts = UINT64_MAX; +- pkt_empty = 0; + } + } else if (pktns->tx.non_ack_pkt_start_ts == UINT64_MAX) { + pktns->tx.non_ack_pkt_start_ts = ts; +@@ -4056,7 +4459,7 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) { + lfr.padding.len = ngtcp2_ppe_padding_size(ppe, destlen); + } else if (require_padding) { +- lfr.padding.len = ngtcp2_ppe_dgram_padding(ppe); ++ lfr.padding.len = conn_dgram_padding(conn, ppe); + } else { + lfr.padding.len = ngtcp2_ppe_padding_size(ppe, min_pktlen); + min_padded = 1; +@@ -4067,7 +4470,7 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) { + padded = 1; + } +- lfr.type = NGTCP2_FRAME_PADDING; ++ lfr.padding.type = NGTCP2_FRAME_PADDING; + ngtcp2_log_tx_fr(&conn->log, hd, &lfr); + ngtcp2_qlog_write_frame(&conn->qlog, &lfr); + } +@@ -4103,12 +4506,6 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + *pfrc = NULL; + } + +- if ((rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) && +- pktns->rtb.num_ack_eliciting == 0 && conn->cc.event) { +- conn->cc.event(&conn->cc, &conn->cstat, NGTCP2_CC_EVENT_TYPE_TX_START, +- ts); +- } +- + rv = conn_on_pkt_sent(conn, pktns, ent); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); +@@ -4117,18 +4514,9 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + return rv; + } + +- if (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) { +- if (conn->cc.on_pkt_sent) { +- conn->cc.on_pkt_sent( +- &conn->cc, &conn->cstat, +- ngtcp2_cc_pkt_init(&cc_pkt, hd->pkt_num, (size_t)nwrite, +- NGTCP2_PKTNS_ID_APPLICATION, ts, ent->rst.lost, +- ent->rst.tx_in_flight, ent->rst.is_app_limited)); +- } +- +- if (conn->flags & NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE) { +- conn_restart_timer_on_write(conn, ts); +- } ++ if ((rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) && ++ (conn->flags & NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE)) { ++ conn_restart_timer_on_write(conn, ts); + } + } else if (pi && conn->tx.ecn.state == NGTCP2_ECN_STATE_CAPABLE) { + conn_handle_tx_ecn(conn, pi, NULL, pktns, hd, ts); +@@ -4140,8 +4528,8 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) { + --pktns->rtb.probe_pkt_left; + +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "probe pkt size=%td", +- nwrite); ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_CON, "probe pkt size=%td", ++ nwrite); + } + + conn_update_keep_alive_last_ts(conn, ts); +@@ -4150,6 +4538,9 @@ static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + + conn->tx.pacing.pktlen += (size_t)nwrite; + ++ ++conn->cstat.pkt_sent; ++ conn->cstat.bytes_sent += (uint64_t)nwrite; ++ + ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat); + + ++pktns->tx.last_pkt_num; +@@ -4248,17 +4639,17 @@ ngtcp2_ssize ngtcp2_conn_write_single_frame_pkt( + return 0; + } + +- lfr.type = NGTCP2_FRAME_PADDING; ++ lfr.padding.type = NGTCP2_FRAME_PADDING; + if (flags & NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING_FULL) { + lfr.padding.len = ngtcp2_ppe_dgram_padding_size(&ppe, destlen); + } else if (flags & NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING) { +- lfr.padding.len = ngtcp2_ppe_dgram_padding(&ppe); ++ lfr.padding.len = conn_dgram_padding(conn, &ppe); + } else { +- switch (fr->type) { ++ switch (fr->hd.type) { + case NGTCP2_FRAME_PATH_CHALLENGE: + case NGTCP2_FRAME_PATH_RESPONSE: + if (!conn->server || destlen >= NGTCP2_MAX_UDP_PAYLOAD_SIZE) { +- lfr.padding.len = ngtcp2_ppe_dgram_padding(&ppe); ++ lfr.padding.len = conn_dgram_padding(conn, &ppe); + } else { + lfr.padding.len = 0; + } +@@ -4285,7 +4676,7 @@ ngtcp2_ssize ngtcp2_conn_write_single_frame_pkt( + ngtcp2_qlog_pkt_sent_end(&conn->qlog, &hd, (size_t)nwrite); + + /* Do this when we are sure that there is no error. */ +- switch (fr->type) { ++ switch (fr->hd.type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + ngtcp2_acktr_commit_ack(&pktns->acktr); +@@ -4300,6 +4691,13 @@ ngtcp2_ssize ngtcp2_conn_write_single_frame_pkt( + padded = 0; + } + ++ break; ++ case NGTCP2_FRAME_CONNECTION_CLOSE: ++ case NGTCP2_FRAME_CONNECTION_CLOSE_APP: ++ /* Clear padded so that we never store the terminal packet in ++ ngtcp2_rtb. */ ++ padded = 0; ++ + break; + } + +@@ -4333,8 +4731,8 @@ ngtcp2_ssize ngtcp2_conn_write_single_frame_pkt( + ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) { + --pktns->rtb.probe_pkt_left; + +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "probe pkt size=%td", +- nwrite); ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_CON, "probe pkt size=%td", ++ nwrite); + } + } + } else if (pi && conn->tx.ecn.state == NGTCP2_ECN_STATE_CAPABLE) { +@@ -4346,7 +4744,7 @@ ngtcp2_ssize ngtcp2_conn_write_single_frame_pkt( + } + + if (!padded) { +- switch (fr->type) { ++ switch (fr->hd.type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + break; +@@ -4357,6 +4755,9 @@ ngtcp2_ssize ngtcp2_conn_write_single_frame_pkt( + conn->tx.pacing.pktlen += (size_t)nwrite; + } + ++ ++conn->cstat.pkt_sent; ++ conn->cstat.bytes_sent += (uint64_t)nwrite; ++ + ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat); + + ++pktns->tx.last_pkt_num; +@@ -4423,8 +4824,10 @@ static int conn_enqueue_retire_connection_id(ngtcp2_conn *conn, uint64_t seq) { + return rv; + } + +- nfrc->fr.type = NGTCP2_FRAME_RETIRE_CONNECTION_ID; +- nfrc->fr.retire_connection_id.seq = seq; ++ nfrc->fr.retire_connection_id = (ngtcp2_retire_connection_id){ ++ .type = NGTCP2_FRAME_RETIRE_CONNECTION_ID, ++ .seq = seq, ++ }; + nfrc->next = pktns->tx.frq; + pktns->tx.frq = nfrc; + +@@ -4563,10 +4966,10 @@ static ngtcp2_ssize conn_write_pmtud_probe(ngtcp2_conn *conn, + return 0; + } + +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, +- "sending PMTUD probe packet len=%zu", probelen); ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_CON, ++ "sending PMTUD probe packet len=%zu", probelen); + +- lfr.type = NGTCP2_FRAME_PING; ++ lfr.ping.type = NGTCP2_FRAME_PING; + + nwrite = ngtcp2_conn_write_single_frame_pkt( + conn, pi, dest, probelen, NGTCP2_PKT_1RTT, +@@ -4764,12 +5167,12 @@ static ngtcp2_ssize conn_write_path_challenge(ngtcp2_conn *conn, + return 0; + } + +- rv = conn_call_get_path_challenge_data(conn, lfr.path_challenge.data); ++ rv = conn_call_get_path_challenge_data(conn, &lfr.path_challenge.data); + if (rv != 0) { + return rv; + } + +- lfr.type = NGTCP2_FRAME_PATH_CHALLENGE; ++ lfr.path_challenge.type = NGTCP2_FRAME_PATH_CHALLENGE; + + initial_pto = conn_compute_initial_pto(conn, &conn->pktns); + timeout = conn_compute_pto(conn, &conn->pktns); +@@ -4796,7 +5199,7 @@ static ngtcp2_ssize conn_write_path_challenge(ngtcp2_conn *conn, + flags = NGTCP2_PV_ENTRY_FLAG_NONE; + } + +- ngtcp2_pv_add_entry(pv, lfr.path_challenge.data, expiry, flags, ts); ++ ngtcp2_pv_add_entry(pv, &lfr.path_challenge.data, expiry, flags, ts); + + nwrite = ngtcp2_conn_write_single_frame_pkt( + conn, pi, dest, destlen, NGTCP2_PKT_1RTT, NGTCP2_WRITE_PKT_FLAG_NONE, +@@ -4902,8 +5305,10 @@ static ngtcp2_ssize conn_write_path_response(ngtcp2_conn *conn, + } + } + +- lfr.type = NGTCP2_FRAME_PATH_RESPONSE; +- memcpy(lfr.path_response.data, pcent->data, sizeof(lfr.path_response.data)); ++ lfr.path_response = (ngtcp2_path_response){ ++ .type = NGTCP2_FRAME_PATH_RESPONSE, ++ .data = pcent->data, ++ }; + + nwrite = ngtcp2_conn_write_single_frame_pkt( + conn, pi, dest, destlen, NGTCP2_PKT_1RTT, NGTCP2_WRITE_PKT_FLAG_NONE, +@@ -5060,11 +5465,11 @@ static int conn_on_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, + ngtcp2_pktns *in_pktns = conn->in_pktns; + ngtcp2_rtb *rtb = &conn->pktns.rtb; + ngtcp2_rtb *in_rtb; +- uint8_t cidbuf[sizeof(retry.odcid.data) * 2 + 1]; ++ char cidbuf[sizeof(retry.odcid.data) * 2 + 1]; + uint8_t *token; + +- if (!in_pktns || conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY) { +- return 0; ++ if (!in_pktns || (conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY)) { ++ return NGTCP2_ERR_DISCARD_PKT; + } + + in_rtb = &in_pktns->rtb; +@@ -5085,9 +5490,9 @@ static int conn_on_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, + return rv; + } + +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, "odcid=0x%s", +- (const char *)ngtcp2_encode_hex(cidbuf, retry.odcid.data, +- retry.odcid.datalen)); ++ ngtcp2_log_infof( ++ &conn->log, NGTCP2_LOG_EVENT_PKT, "odcid=0x%s", ++ ngtcp2_encode_hex_cstr(cidbuf, retry.odcid.data, retry.odcid.datalen)); + + if (retry.tokenlen == 0) { + return NGTCP2_ERR_PROTO; +@@ -5181,14 +5586,10 @@ static int conn_recv_ack(ngtcp2_conn *conn, ngtcp2_pktns *pktns, ngtcp2_ack *fr, + + num_acked = + ngtcp2_rtb_recv_ack(&pktns->rtb, fr, &conn->cstat, conn, pktns, pkt_ts, ts); +- if (num_acked < 0) { ++ if (num_acked <= 0) { + return (int)num_acked; + } + +- if (num_acked == 0) { +- return 0; +- } +- + pktns->rtb.probe_pkt_left = 0; + + if (cstat->pto_count && +@@ -5235,9 +5636,12 @@ static void assign_recved_ack_delay_unscaled(ngtcp2_ack *fr, + * Stream ID exceeds allowed limit. + * NGTCP2_ERR_NOMEM + * Out of memory. ++ * NGTCP2_ERR_INTERNAL ++ * Suspicious remote endpoint activity exceeded threshold. + */ + static int conn_recv_max_stream_data(ngtcp2_conn *conn, +- const ngtcp2_max_stream_data *fr) { ++ const ngtcp2_max_stream_data *fr, ++ ngtcp2_tstamp ts) { + ngtcp2_strm *strm; + ngtcp2_idtr *idtr; + int local_stream = conn_local_stream(conn, fr->stream_id); +@@ -5267,6 +5671,10 @@ static int conn_recv_max_stream_data(ngtcp2_conn *conn, + if (strm == NULL) { + if (local_stream) { + /* Stream has been closed. */ ++ if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { ++ return NGTCP2_ERR_INTERNAL; ++ } ++ + return 0; + } + +@@ -5277,6 +5685,10 @@ static int conn_recv_max_stream_data(ngtcp2_conn *conn, + } + assert(rv == NGTCP2_ERR_STREAM_IN_USE); + /* Stream has been closed. */ ++ if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { ++ return NGTCP2_ERR_INTERNAL; ++ } ++ + return 0; + } + +@@ -5296,19 +5708,29 @@ static int conn_recv_max_stream_data(ngtcp2_conn *conn, + } + } + +- if (strm->tx.max_offset < fr->max_stream_data) { +- strm->tx.max_offset = fr->max_stream_data; +- +- /* Don't call callback if stream is half-closed local */ +- if (strm->flags & NGTCP2_STRM_FLAG_SHUT_WR) { +- return 0; ++ if (strm->tx.max_offset >= fr->max_stream_data) { ++ if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { ++ return NGTCP2_ERR_INTERNAL; + } + +- rv = conn_call_extend_max_stream_data(conn, strm, fr->stream_id, +- fr->max_stream_data); +- if (rv != 0) { +- return rv; ++ return 0; ++ } ++ ++ strm->tx.max_offset = fr->max_stream_data; ++ ++ /* Don't call callback if stream is half-closed local */ ++ if (strm->flags & NGTCP2_STRM_FLAG_SHUT_WR) { ++ if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { ++ return NGTCP2_ERR_INTERNAL; + } ++ ++ return 0; ++ } ++ ++ rv = conn_call_extend_max_stream_data(conn, strm, fr->stream_id, ++ fr->max_stream_data); ++ if (rv != 0) { ++ return rv; + } + + return 0; +@@ -5503,9 +5925,9 @@ decrypt_hp(ngtcp2_pkt_hd *hd, uint8_t *dest, const ngtcp2_crypto_cipher *hp, + } + + if (hd->flags & NGTCP2_PKT_FLAG_LONG_FORM) { +- dest[0] = (uint8_t)(dest[0] ^ (mask[0] & 0x0f)); ++ dest[0] = (uint8_t)(dest[0] ^ (mask[0] & 0x0F)); + } else { +- dest[0] = (uint8_t)(dest[0] ^ (mask[0] & 0x1f)); ++ dest[0] = (uint8_t)(dest[0] ^ (mask[0] & 0x1F)); + if (dest[0] & NGTCP2_SHORT_KEY_PHASE_BIT) { + hd->flags |= NGTCP2_PKT_FLAG_KEY_PHASE; + } +@@ -5620,7 +6042,7 @@ static void conn_recv_path_challenge(ngtcp2_conn *conn, const ngtcp2_path *path, + } + + ent = ngtcp2_ringbuf_push_front(&conn->rx.path_challenge.rb); +- ngtcp2_path_challenge_entry_init(ent, path, fr->data); ++ ngtcp2_path_challenge_entry_init(ent, path, &fr->data); + } + + /* +@@ -5690,7 +6112,7 @@ static int conn_recv_path_response(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, + return 0; + } + +- rv = ngtcp2_pv_validate(pv, &ent_flags, fr->data); ++ rv = ngtcp2_pv_validate(pv, &ent_flags, &fr->data); + if (rv != 0) { + assert(!ngtcp2_err_is_fatal(rv)); + +@@ -5786,7 +6208,7 @@ static int conn_recv_path_response(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, + + conn->pv = npv; + +- return 0; ++ return conn_call_begin_path_validation(conn, conn->pv); + } + + /* +@@ -5941,7 +6363,8 @@ static int conn_verify_fixed_bit(ngtcp2_conn *conn, ngtcp2_pkt_hd *hd) { + + static int conn_recv_crypto(ngtcp2_conn *conn, + ngtcp2_encryption_level encryption_level, +- ngtcp2_strm *strm, const ngtcp2_stream *fr); ++ ngtcp2_strm *strm, const ngtcp2_stream *fr, ++ ngtcp2_tstamp ts); + + static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + const ngtcp2_pkt_info *pi, const uint8_t *pkt, +@@ -5980,6 +6403,8 @@ static int conn_process_buffered_protected_pkt(ngtcp2_conn *conn, + * TLS stack reported error. + * NGTCP2_ERR_PROTO + * Generic QUIC protocol error. ++ * NGTCP2_ERR_INTERNAL ++ * Suspicious remote endpoint activity exceeded threshold. + * + * In addition to the above error codes, error codes returned from + * conn_recv_pkt are also returned. +@@ -5991,8 +6416,8 @@ conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + ngtcp2_tstamp ts) { + ngtcp2_ssize nread; + ngtcp2_pkt_hd hd; +- ngtcp2_max_frame mfr; +- ngtcp2_frame *fr = &mfr.fr; ++ ngtcp2_frame_decoder frd; ++ ngtcp2_frame fr; + int rv; + int require_ack = 0; + size_t hdpktlen; +@@ -6023,8 +6448,8 @@ conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + return 0; + } + +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, +- "buffering 1RTT packet len=%zu", pktlen); ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_CON, ++ "buffering 1RTT packet len=%zu", pktlen); + + rv = + conn_buffer_pkt(conn, &conn->pktns, path, pi, pkt, pktlen, dgramlen, ts); +@@ -6171,8 +6596,8 @@ conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + } + + /* Buffer re-ordered 0-RTT packet. */ +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, +- "buffering 0-RTT packet len=%zu", pktlen); ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_CON, ++ "buffering 0-RTT packet len=%zu", pktlen); + + rv = conn_buffer_pkt(conn, conn->in_pktns, path, pi, pkt, pktlen, dgramlen, + ts); +@@ -6194,10 +6619,10 @@ conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + + if (conn->server) { + if (dgramlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE) { +- ngtcp2_log_info( ++ ngtcp2_log_infof( + &conn->log, NGTCP2_LOG_EVENT_PKT, + "Initial packet was ignored because it is included in UDP datagram " +- "less than %zu bytes: %zu bytes", ++ "less than %d bytes: %zu bytes", + NGTCP2_MAX_UDP_PAYLOAD_SIZE, dgramlen); + return NGTCP2_ERR_DISCARD_PKT; + } +@@ -6277,8 +6702,8 @@ conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + "Handshake packet at this point is unexpected and discarded"); + return (ngtcp2_ssize)pktlen; + } +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, +- "buffering Handshake packet len=%zu", pktlen); ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_CON, ++ "buffering Handshake packet len=%zu", pktlen); + + rv = conn_buffer_pkt(conn, conn->hs_pktns, path, pi, pkt, pktlen, + dgramlen, ts); +@@ -6332,8 +6757,8 @@ conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + hd.pkt_num = ngtcp2_pkt_adjust_pkt_num(pktns->acktr.max_pkt_num, hd.pkt_num, + hd.pkt_numlen); + if (hd.pkt_num > NGTCP2_MAX_PKT_NUM) { +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, +- "pkn=%" PRId64 " is greater than maximum pkn", hd.pkt_num); ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_PKT, ++ "pkn=%" PRId64 " is greater than maximum pkn", hd.pkt_num); + return NGTCP2_ERR_DISCARD_PKT; + } + +@@ -6380,9 +6805,9 @@ conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + !conn->negotiated_version) { + conn->negotiated_version = hd.version; + +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, +- "the negotiated version is 0x%08x", +- conn->negotiated_version); ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_CON, ++ "the negotiated version is 0x%08x", ++ conn->negotiated_version); + } + + payload = conn->crypto.decrypt_buf.base; +@@ -6438,7 +6863,7 @@ conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + ngtcp2_qlog_pkt_received_start(&conn->qlog); + + for (; payloadlen;) { +- nread = ngtcp2_pkt_decode_frame(fr, payload, payloadlen); ++ nread = ngtcp2_frame_decoder_decode(&frd, &fr, payload, payloadlen); + if (nread < 0) { + return nread; + } +@@ -6446,14 +6871,14 @@ conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + payload += nread; + payloadlen -= (size_t)nread; + +- switch (fr->type) { ++ switch (fr.hd.type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: +- fr->ack.ack_delay = 0; +- fr->ack.ack_delay_unscaled = 0; ++ fr.ack.ack_delay = 0; ++ fr.ack.ack_delay_unscaled = 0; + + rv = +- ngtcp2_pkt_validate_ack(&fr->ack, conn->local.settings.initial_pkt_num); ++ ngtcp2_pkt_validate_ack(&fr.ack, conn->local.settings.initial_pkt_num); + if (rv != 0) { + return rv; + } +@@ -6461,9 +6886,9 @@ conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + break; + } + +- ngtcp2_log_rx_fr(&conn->log, &hd, fr); ++ ngtcp2_log_rx_fr(&conn->log, &hd, &fr); + +- switch (fr->type) { ++ switch (fr.hd.type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + if (num_ack_processed >= NGTCP2_MAX_ACK_PER_PKT) { +@@ -6472,7 +6897,7 @@ conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + if (!conn->server && hd.type == NGTCP2_PKT_HANDSHAKE) { + conn->flags |= NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED; + } +- rv = conn_recv_ack(conn, pktns, &fr->ack, pkt_ts, ts); ++ rv = conn_recv_ack(conn, pktns, &fr.ack, pkt_ts, ts); + if (rv != 0) { + return rv; + } +@@ -6482,34 +6907,35 @@ conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + break; + case NGTCP2_FRAME_CRYPTO: + if (!conn->server && !conn->negotiated_version && +- ngtcp2_vec_len(fr->stream.data, fr->stream.datacnt)) { ++ ngtcp2_vec_len(fr.stream.data, fr.stream.datacnt)) { + conn->negotiated_version = hd.version; + +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, +- "the negotiated version is 0x%08x", +- conn->negotiated_version); ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_CON, ++ "the negotiated version is 0x%08x", ++ conn->negotiated_version); + } + +- rv = conn_recv_crypto(conn, encryption_level, crypto, &fr->stream); ++ rv = conn_recv_crypto(conn, encryption_level, crypto, &fr.stream, ts); + if (rv != 0) { + return rv; + } + require_ack = 1; + break; + case NGTCP2_FRAME_CONNECTION_CLOSE: +- rv = conn_recv_connection_close(conn, &fr->connection_close); ++ rv = conn_recv_connection_close(conn, &fr.connection_close); + if (rv != 0) { + return rv; + } + break; + case NGTCP2_FRAME_PING: ++ ++conn->cstat.ping_recv; + require_ack = 1; + break; + default: + return NGTCP2_ERR_PROTO; + } + +- ngtcp2_qlog_write_frame(&conn->qlog, fr); ++ ngtcp2_qlog_write_frame(&conn->qlog, &fr); + } + + if (hd.type == NGTCP2_PKT_HANDSHAKE) { +@@ -6543,6 +6969,7 @@ static int is_unrecoverable_error(int liberr) { + case NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM: + case NGTCP2_ERR_TRANSPORT_PARAM: + case NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE: ++ case NGTCP2_ERR_INTERNAL: + return 1; + } + +@@ -6568,6 +6995,10 @@ static ngtcp2_ssize conn_recv_handshake_cpkt(ngtcp2_conn *conn, + const uint8_t *origpkt = pkt; + uint32_t version; + ++ if (pktlen == 0) { ++ return 0; ++ } ++ + if (ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) { + conn->dcid.current.bytes_recv += dgramlen; + } +@@ -6595,6 +7026,8 @@ static ngtcp2_ssize conn_recv_handshake_cpkt(ngtcp2_conn *conn, + return nread; + } + ++ ++conn->cstat.pkt_discarded; ++ + /* If server discards first Initial, then drop connection + state. This is because SCID in packet might be corrupted + and the current connection state might wrongly discard +@@ -6612,11 +7045,15 @@ static ngtcp2_ssize conn_recv_handshake_cpkt(ngtcp2_conn *conn, + unrecoverable, therefore drop connection. */ + return nread; + } ++ ++ ++conn->cstat.pkt_discarded; ++ + return (ngtcp2_ssize)dgramlen; + } + } + + if (nread == NGTCP2_ERR_DISCARD_PKT) { ++ ++conn->cstat.pkt_discarded; + return (ngtcp2_ssize)dgramlen; + } + +@@ -6632,8 +7069,11 @@ static ngtcp2_ssize conn_recv_handshake_cpkt(ngtcp2_conn *conn, + pkt += nread; + pktlen -= (size_t)nread; + +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, +- "read packet %td left %zu", nread, pktlen); ++ ++conn->cstat.pkt_recv; ++ conn->cstat.bytes_recv += (uint64_t)nread; ++ ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_PKT, ++ "read packet %td left %zu", nread, pktlen); + } + + return (ngtcp2_ssize)dgramlen; +@@ -6778,15 +7218,24 @@ static int conn_emit_pending_stream_data(ngtcp2_conn *conn, ngtcp2_strm *strm, + * The end offset exceeds the maximum value. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. ++ * NGTCP2_ERR_INTERNAL ++ * Suspicious remote endpoint activity exceeded threshold. + */ + static int conn_recv_crypto(ngtcp2_conn *conn, + ngtcp2_encryption_level encryption_level, +- ngtcp2_strm *crypto, const ngtcp2_stream *fr) { ++ ngtcp2_strm *crypto, const ngtcp2_stream *fr, ++ ngtcp2_tstamp ts) { + uint64_t fr_end_offset; + uint64_t rx_offset; + int rv; ++ ngtcp2_ssize nwrite; + + if (fr->datacnt == 0) { ++ if (encryption_level != NGTCP2_ENCRYPTION_LEVEL_INITIAL && ++ ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { ++ return NGTCP2_ERR_INTERNAL; ++ } ++ + return 0; + } + +@@ -6799,6 +7248,11 @@ static int conn_recv_crypto(ngtcp2_conn *conn, + rx_offset = ngtcp2_strm_rx_offset(crypto); + + if (fr_end_offset <= rx_offset) { ++ if (encryption_level != NGTCP2_ENCRYPTION_LEVEL_INITIAL && ++ ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { ++ return NGTCP2_ERR_INTERNAL; ++ } ++ + if (conn->server && + !(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_EARLY_RETRANSMIT) && + encryption_level == NGTCP2_ENCRYPTION_LEVEL_INITIAL) { +@@ -6856,8 +7310,18 @@ static int conn_recv_crypto(ngtcp2_conn *conn, + return NGTCP2_ERR_CRYPTO_BUFFER_EXCEEDED; + } + +- return ngtcp2_strm_recv_reordering(crypto, fr->data[0].base, fr->data[0].len, +- fr->offset); ++ nwrite = ngtcp2_strm_recv_reordering(crypto, fr->data[0].base, ++ fr->data[0].len, fr->offset); ++ if (nwrite < 0) { ++ return (int)nwrite; ++ } ++ ++ if (encryption_level != NGTCP2_ENCRYPTION_LEVEL_INITIAL && nwrite == 0 && ++ ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { ++ return NGTCP2_ERR_INTERNAL; ++ } ++ ++ return 0; + } + + /* +@@ -6891,8 +7355,11 @@ static int conn_max_data_violated(ngtcp2_conn *conn, uint64_t datalen) { + * NGTCP2_ERR_FINAL_SIZE + * STREAM frame has strictly larger end offset than it is + * permitted. ++ * NGTCP2_ERR_INTERNAL ++ * Suspicious remote endpoint activity exceeded threshold. + */ +-static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr) { ++static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr, ++ ngtcp2_tstamp ts) { + int rv; + ngtcp2_strm *strm; + ngtcp2_idtr *idtr; +@@ -6901,6 +7368,8 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr) { + int bidi; + uint64_t datalen = ngtcp2_vec_len(fr->data, fr->datacnt); + uint32_t sdflags = NGTCP2_STREAM_DATA_FLAG_NONE; ++ ngtcp2_ssize nwrite; ++ int new_strm = 0; + + local_stream = conn_local_stream(conn, fr->stream_id); + bidi = bidi_stream(fr->stream_id); +@@ -6934,8 +7403,11 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr) { + strm = ngtcp2_conn_find_stream(conn, fr->stream_id); + if (strm == NULL) { + if (local_stream) { +- /* TODO The stream has been closed. This should be responded +- with RESET_STREAM, or simply ignored. */ ++ /* The stream has been closed. */ ++ if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { ++ return NGTCP2_ERR_INTERNAL; ++ } ++ + return 0; + } + +@@ -6945,8 +7417,11 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr) { + return rv; + } + assert(rv == NGTCP2_ERR_STREAM_IN_USE); +- /* TODO The stream has been closed. This should be responded +- with RESET_STREAM, or simply ignored. */ ++ /* The stream has been closed. */ ++ if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { ++ return NGTCP2_ERR_INTERNAL; ++ } ++ + return 0; + } + +@@ -6961,6 +7436,8 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr) { + return rv; + } + ++ new_strm = 1; ++ + if (!bidi) { + ngtcp2_strm_shutdown(strm, NGTCP2_STRM_FLAG_SHUT_WR); + strm->flags |= NGTCP2_STRM_FLAG_FIN_ACKED; +@@ -7001,10 +7478,18 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr) { + } + + if (strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM_RECVED) { ++ if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { ++ return NGTCP2_ERR_INTERNAL; ++ } ++ + return 0; + } + + if (rx_offset == fr_end_offset) { ++ if (!new_strm && ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { ++ return NGTCP2_ERR_INTERNAL; ++ } ++ + return 0; + } + } else if (strm->rx.last_offset > fr_end_offset) { +@@ -7024,10 +7509,18 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr) { + ngtcp2_max_uint64(strm->rx.last_offset, fr_end_offset); + + if (fr_end_offset <= rx_offset) { ++ if (!new_strm && ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { ++ return NGTCP2_ERR_INTERNAL; ++ } ++ + return 0; + } + + if (strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM_RECVED) { ++ if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { ++ return NGTCP2_ERR_INTERNAL; ++ } ++ + return 0; + } + } +@@ -7056,30 +7549,34 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr) { + fin = (strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) && + rx_offset == strm->rx.last_offset; + +- if (fin || datalen) { +- if (fin) { +- sdflags |= NGTCP2_STREAM_DATA_FLAG_FIN; +- } +- if (!conn_is_tls_handshake_completed(conn)) { +- sdflags |= NGTCP2_STREAM_DATA_FLAG_0RTT; +- } +- rv = conn_call_recv_stream_data(conn, strm, sdflags, offset, data, +- (size_t)datalen); +- if (rv != 0) { +- return rv; +- } ++ assert(fin || datalen); + +- rv = conn_emit_pending_stream_data(conn, strm, rx_offset); +- if (rv != 0) { +- return rv; +- } ++ if (fin) { ++ sdflags |= NGTCP2_STREAM_DATA_FLAG_FIN; + } +- } else if (fr->datacnt && !(strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING)) { +- rv = ngtcp2_strm_recv_reordering(strm, fr->data[0].base, fr->data[0].len, +- fr->offset); ++ if (!conn_is_tls_handshake_completed(conn)) { ++ sdflags |= NGTCP2_STREAM_DATA_FLAG_0RTT; ++ } ++ rv = conn_call_recv_stream_data(conn, strm, sdflags, offset, data, ++ (size_t)datalen); ++ if (rv != 0) { ++ return rv; ++ } ++ ++ rv = conn_emit_pending_stream_data(conn, strm, rx_offset); + if (rv != 0) { + return rv; + } ++ } else if (fr->datacnt && !(strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING)) { ++ nwrite = ngtcp2_strm_recv_reordering(strm, fr->data[0].base, ++ fr->data[0].len, fr->offset); ++ if (nwrite < 0) { ++ return (int)nwrite; ++ } ++ ++ if (nwrite == 0 && ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { ++ return NGTCP2_ERR_INTERNAL; ++ } + } + return ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm); + } +@@ -7175,9 +7672,12 @@ handle_max_remote_streams_extension(uint64_t *punsent_max_remote_streams, + * NGTCP2_MAX_VARINT. + * NGTCP2_ERR_FINAL_SIZE + * The final offset is strictly larger than it is permitted. ++ * NGTCP2_ERR_INTERNAL ++ * Suspicious remote endpoint activity exceeded threshold. + */ + static int conn_recv_reset_stream(ngtcp2_conn *conn, +- const ngtcp2_reset_stream *fr) { ++ const ngtcp2_reset_stream *fr, ++ ngtcp2_tstamp ts) { + ngtcp2_strm *strm; + int local_stream = conn_local_stream(conn, fr->stream_id); + int bidi = bidi_stream(fr->stream_id); +@@ -7215,6 +7715,10 @@ static int conn_recv_reset_stream(ngtcp2_conn *conn, + strm = ngtcp2_conn_find_stream(conn, fr->stream_id); + if (strm == NULL) { + if (local_stream) { ++ if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { ++ return NGTCP2_ERR_INTERNAL; ++ } ++ + return 0; + } + +@@ -7224,6 +7728,11 @@ static int conn_recv_reset_stream(ngtcp2_conn *conn, + return rv; + } + assert(rv == NGTCP2_ERR_STREAM_IN_USE); ++ ++ if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { ++ return NGTCP2_ERR_INTERNAL; ++ } ++ + return 0; + } + +@@ -7258,6 +7767,10 @@ static int conn_recv_reset_stream(ngtcp2_conn *conn, + } + + if (strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM_RECVED) { ++ if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { ++ return NGTCP2_ERR_INTERNAL; ++ } ++ + return 0; + } + +@@ -7313,9 +7826,12 @@ static int conn_recv_reset_stream(ngtcp2_conn *conn, + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. ++ * NGTCP2_ERR_INTERNAL ++ * Suspicious remote endpoint activity exceeded threshold. + */ + static int conn_recv_stop_sending(ngtcp2_conn *conn, +- const ngtcp2_stop_sending *fr) { ++ const ngtcp2_stop_sending *fr, ++ ngtcp2_tstamp ts) { + int rv; + ngtcp2_strm *strm; + ngtcp2_idtr *idtr; +@@ -7344,6 +7860,10 @@ static int conn_recv_stop_sending(ngtcp2_conn *conn, + strm = ngtcp2_conn_find_stream(conn, fr->stream_id); + if (strm == NULL) { + if (local_stream) { ++ if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { ++ return NGTCP2_ERR_INTERNAL; ++ } ++ + return 0; + } + rv = ngtcp2_idtr_open(idtr, fr->stream_id); +@@ -7352,6 +7872,11 @@ static int conn_recv_stop_sending(ngtcp2_conn *conn, + return rv; + } + assert(rv == NGTCP2_ERR_STREAM_IN_USE); ++ ++ if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { ++ return NGTCP2_ERR_INTERNAL; ++ } ++ + return 0; + } + +@@ -7374,6 +7899,10 @@ static int conn_recv_stop_sending(ngtcp2_conn *conn, + } + + if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING_RECVED) { ++ if (ngtcp2_ratelim_drain(&conn->glitch_rlim, 1, ts) != 0) { ++ return NGTCP2_ERR_INTERNAL; ++ } ++ + return 0; + } + +@@ -7405,9 +7934,8 @@ static int conn_recv_stop_sending(ngtcp2_conn *conn, + */ + static int check_stateless_reset(const ngtcp2_dcid *dcid, + const ngtcp2_path *path, +- const ngtcp2_pkt_stateless_reset *sr) { +- return ngtcp2_dcid_verify_stateless_reset_token( +- dcid, path, sr->stateless_reset_token) == 0; ++ const ngtcp2_pkt_stateless_reset2 *sr) { ++ return ngtcp2_dcid_verify_stateless_reset_token(dcid, path, &sr->token) == 0; + } + + /* +@@ -7431,7 +7959,7 @@ static int conn_on_stateless_reset(ngtcp2_conn *conn, const ngtcp2_path *path, + const uint8_t *payload, size_t payloadlen) { + int rv; + ngtcp2_pv *pv = conn->pv; +- ngtcp2_pkt_stateless_reset sr; ++ ngtcp2_pkt_stateless_reset2 sr; + + rv = ngtcp2_pkt_decode_stateless_reset(&sr, payload, payloadlen); + if (rv != 0) { +@@ -7442,8 +7970,7 @@ static int conn_on_stateless_reset(ngtcp2_conn *conn, const ngtcp2_path *path, + (!pv || (!check_stateless_reset(&pv->dcid, path, &sr) && + (!(pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) || + !check_stateless_reset(&pv->fallback_dcid, path, &sr))))) { +- rv = ngtcp2_dcidtr_verify_stateless_reset(&conn->dcid.dtr, path, +- sr.stateless_reset_token); ++ rv = ngtcp2_dcidtr_verify_stateless_reset(&conn->dcid.dtr, path, &sr.token); + if (rv != 0) { + return rv; + } +@@ -7523,7 +8050,7 @@ static int conn_recv_new_connection_id(ngtcp2_conn *conn, + } + + rv = ngtcp2_dcid_verify_uniqueness(&conn->dcid.current, fr->seq, &fr->cid, +- fr->stateless_reset_token); ++ &fr->token); + if (rv != 0) { + return rv; + } +@@ -7532,8 +8059,8 @@ static int conn_recv_new_connection_id(ngtcp2_conn *conn, + } + + if (pv) { +- rv = ngtcp2_dcid_verify_uniqueness(&pv->dcid, fr->seq, &fr->cid, +- fr->stateless_reset_token); ++ rv = ++ ngtcp2_dcid_verify_uniqueness(&pv->dcid, fr->seq, &fr->cid, &fr->token); + if (rv != 0) { + return rv; + } +@@ -7542,8 +8069,8 @@ static int conn_recv_new_connection_id(ngtcp2_conn *conn, + } + } + +- rv = ngtcp2_dcidtr_verify_token_uniqueness( +- &conn->dcid.dtr, &found, fr->seq, &fr->cid, fr->stateless_reset_token); ++ rv = ngtcp2_dcidtr_verify_token_uniqueness(&conn->dcid.dtr, &found, fr->seq, ++ &fr->cid, &fr->token); + if (rv != 0) { + return rv; + } +@@ -7621,8 +8148,7 @@ static int conn_recv_new_connection_id(ngtcp2_conn *conn, + return 0; + } + +- ngtcp2_dcidtr_push_unused(&conn->dcid.dtr, fr->seq, &fr->cid, +- fr->stateless_reset_token); ++ ngtcp2_dcidtr_push_unused(&conn->dcid.dtr, fr->seq, &fr->cid, &fr->token); + + return 0; + } +@@ -8030,7 +8556,12 @@ static int conn_select_preferred_addr(ngtcp2_conn *conn) { + + conn->pv = pv; + +- return conn_call_activate_dcid(conn, &pv->dcid); ++ rv = conn_call_activate_dcid(conn, &pv->dcid); ++ if (rv != 0) { ++ return rv; ++ } ++ ++ return conn_call_begin_path_validation(conn, conn->pv); + } + + /* +@@ -8258,7 +8789,6 @@ static int conn_recv_non_probing_pkt_on_new_path(ngtcp2_conn *conn, + ngtcp2_pv *pv = NULL; + int rv; + ngtcp2_duration pto; +- int require_new_cid; + int local_addr_eq; + int pref_addr_migration; + uint32_t remote_addr_cmp; +@@ -8316,9 +8846,6 @@ static int conn_recv_non_probing_pkt_on_new_path(ngtcp2_conn *conn, + * continue to use the current connection ID with the new remote + * address while still sending from the same local address. + */ +- require_new_cid = conn->dcid.current.cid.datalen && +- ((new_cid_used && remote_addr_cmp) || !local_addr_eq); +- + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "non-probing packet was received from new remote address"); + +@@ -8326,8 +8853,6 @@ static int conn_recv_non_probing_pkt_on_new_path(ngtcp2_conn *conn, + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "Found DCID which has already been bound to the new path"); + +- require_new_cid = 0; +- + if (dcid.cid.datalen) { + rv = conn_call_activate_dcid(conn, &dcid); + if (rv != 0) { +@@ -8335,7 +8860,9 @@ static int conn_recv_non_probing_pkt_on_new_path(ngtcp2_conn *conn, + } + } + } else { +- if (require_new_cid) { ++ if (conn->dcid.current.cid.datalen && ++ ((new_cid_used && remote_addr_cmp) || !local_addr_eq)) { ++ /* New DCID is required. */ + if (ngtcp2_dcidtr_unused_empty(&conn->dcid.dtr)) { + return NGTCP2_ERR_CONN_ID_BLOCKED; + } +@@ -8421,7 +8948,7 @@ static int conn_recv_non_probing_pkt_on_new_path(ngtcp2_conn *conn, + + conn->pv = pv; + +- return 0; ++ return conn_call_begin_path_validation(conn, conn->pv); + } + + /* +@@ -8500,8 +9027,8 @@ conn_recv_delayed_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_pkt_info *pi, + const uint8_t *payload, size_t payloadlen, + ngtcp2_tstamp pkt_ts, ngtcp2_tstamp ts) { + ngtcp2_ssize nread; +- ngtcp2_max_frame mfr; +- ngtcp2_frame *fr = &mfr.fr; ++ ngtcp2_frame_decoder frd; ++ ngtcp2_frame fr; + int rv; + int require_ack = 0; + ngtcp2_pktns *pktns; +@@ -8519,7 +9046,7 @@ conn_recv_delayed_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_pkt_info *pi, + ngtcp2_qlog_pkt_received_start(&conn->qlog); + + for (; payloadlen;) { +- nread = ngtcp2_pkt_decode_frame(fr, payload, payloadlen); ++ nread = ngtcp2_frame_decoder_decode(&frd, &fr, payload, payloadlen); + if (nread < 0) { + return (int)nread; + } +@@ -8527,14 +9054,14 @@ conn_recv_delayed_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_pkt_info *pi, + payload += nread; + payloadlen -= (size_t)nread; + +- switch (fr->type) { ++ switch (fr.hd.type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: +- fr->ack.ack_delay = 0; +- fr->ack.ack_delay_unscaled = 0; ++ fr.ack.ack_delay = 0; ++ fr.ack.ack_delay_unscaled = 0; + + rv = +- ngtcp2_pkt_validate_ack(&fr->ack, conn->local.settings.initial_pkt_num); ++ ngtcp2_pkt_validate_ack(&fr.ack, conn->local.settings.initial_pkt_num); + if (rv != 0) { + return rv; + } +@@ -8542,9 +9069,9 @@ conn_recv_delayed_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_pkt_info *pi, + break; + } + +- ngtcp2_log_rx_fr(&conn->log, hd, fr); ++ ngtcp2_log_rx_fr(&conn->log, hd, &fr); + +- switch (fr->type) { ++ switch (fr.hd.type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + if (num_ack_processed >= NGTCP2_MAX_ACK_PER_PKT) { +@@ -8553,7 +9080,7 @@ conn_recv_delayed_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_pkt_info *pi, + if (!conn->server) { + conn->flags |= NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED; + } +- rv = conn_recv_ack(conn, pktns, &fr->ack, pkt_ts, ts); ++ rv = conn_recv_ack(conn, pktns, &fr.ack, pkt_ts, ts); + if (rv != 0) { + return rv; + } +@@ -8562,20 +9089,22 @@ conn_recv_delayed_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_pkt_info *pi, + case NGTCP2_FRAME_PADDING: + break; + case NGTCP2_FRAME_CONNECTION_CLOSE: +- rv = conn_recv_connection_close(conn, &fr->connection_close); ++ rv = conn_recv_connection_close(conn, &fr.connection_close); + if (rv != 0) { + return rv; + } + break; +- case NGTCP2_FRAME_CRYPTO: + case NGTCP2_FRAME_PING: ++ ++conn->cstat.ping_recv; ++ /* fall through */ ++ case NGTCP2_FRAME_CRYPTO: + require_ack = 1; + break; + default: + return NGTCP2_ERR_PROTO; + } + +- ngtcp2_qlog_write_frame(&conn->qlog, fr); ++ ngtcp2_qlog_write_frame(&conn->qlog, &fr); + } + + ngtcp2_qlog_pkt_received_end(&conn->qlog, hd, pktlen); +@@ -8630,6 +9159,8 @@ conn_recv_delayed_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_pkt_info *pi, + * Flow control limit is violated. + * NGTCP2_ERR_FINAL_SIZE + * Frame has strictly larger end offset than it is permitted. ++ * NGTCP2_ERR_INTERNAL ++ * Suspicious remote endpoint activity exceeded threshold. + */ + static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + const ngtcp2_pkt_info *pi, const uint8_t *pkt, +@@ -8641,8 +9172,8 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + const uint8_t *payload; + size_t payloadlen; + ngtcp2_ssize nread, nwrite; +- ngtcp2_max_frame mfr; +- ngtcp2_frame *fr = &mfr.fr; ++ ngtcp2_frame_decoder frd; ++ ngtcp2_frame fr; + int require_ack = 0; + ngtcp2_crypto_aead *aead; + ngtcp2_crypto_cipher *hp; +@@ -8736,8 +9267,8 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + break; + default: + ngtcp2_log_rx_pkt_hd(&conn->log, &hd); +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, +- "packet type 0x%02x was ignored", hd.type); ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_PKT, ++ "packet type 0x%02x was ignored", hd.type); + return (ngtcp2_ssize)pktlen; + } + } else { +@@ -8784,8 +9315,8 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + hd.pkt_num = ngtcp2_pkt_adjust_pkt_num(pktns->acktr.max_pkt_num, hd.pkt_num, + hd.pkt_numlen); + if (hd.pkt_num > NGTCP2_MAX_PKT_NUM) { +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, +- "pkn=%" PRId64 " is greater than maximum pkn", hd.pkt_num); ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_PKT, ++ "pkn=%" PRId64 " is greater than maximum pkn", hd.pkt_num); + return NGTCP2_ERR_DISCARD_PKT; + } + +@@ -8796,7 +9327,7 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + conn->rx.preferred_addr.pkt_num < hd.pkt_num && + ngtcp2_sockaddr_eq((const ngtcp2_sockaddr *)&conn->hs_local_addr, + path->local.addr)) { +- ngtcp2_log_info( ++ ngtcp2_log_infof( + &conn->log, NGTCP2_LOG_EVENT_PKT, + "pkt=%" PRId64 + " is discarded because it was received on handshake local " +@@ -8948,7 +9479,7 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + ngtcp2_qlog_pkt_received_start(&conn->qlog); + + for (; payloadlen;) { +- nread = ngtcp2_pkt_decode_frame(fr, payload, payloadlen); ++ nread = ngtcp2_frame_decoder_decode(&frd, &fr, payload, payloadlen); + if (nread < 0) { + return nread; + } +@@ -8956,7 +9487,7 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + payload += nread; + payloadlen -= (size_t)nread; + +- switch (fr->type) { ++ switch (fr.hd.type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + if ((hd.flags & NGTCP2_PKT_FLAG_LONG_FORM) && +@@ -8965,10 +9496,10 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + } + assert(conn->remote.transport_params); + assign_recved_ack_delay_unscaled( +- &fr->ack, conn->remote.transport_params->ack_delay_exponent); ++ &fr.ack, conn->remote.transport_params->ack_delay_exponent); + + rv = +- ngtcp2_pkt_validate_ack(&fr->ack, conn->local.settings.initial_pkt_num); ++ ngtcp2_pkt_validate_ack(&fr.ack, conn->local.settings.initial_pkt_num); + if (rv != 0) { + return rv; + } +@@ -8976,10 +9507,10 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + break; + } + +- ngtcp2_log_rx_fr(&conn->log, &hd, fr); ++ ngtcp2_log_rx_fr(&conn->log, &hd, &fr); + + if (hd.type == NGTCP2_PKT_0RTT) { +- switch (fr->type) { ++ switch (fr.hd.type) { + case NGTCP2_FRAME_PADDING: + case NGTCP2_FRAME_PING: + case NGTCP2_FRAME_RESET_STREAM: +@@ -9005,7 +9536,7 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + } + } + +- switch (fr->type) { ++ switch (fr.hd.type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + case NGTCP2_FRAME_PADDING: +@@ -9016,7 +9547,7 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + require_ack = 1; + } + +- switch (fr->type) { ++ switch (fr.hd.type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + if (num_ack_processed >= NGTCP2_MAX_ACK_PER_PKT) { +@@ -9025,7 +9556,7 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + if (!conn->server) { + conn->flags |= NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED; + } +- rv = conn_recv_ack(conn, pktns, &fr->ack, pkt_ts, ts); ++ rv = conn_recv_ack(conn, pktns, &fr.ack, pkt_ts, ts); + if (rv != 0) { + return rv; + } +@@ -9033,7 +9564,7 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + ++num_ack_processed; + break; + case NGTCP2_FRAME_STREAM: +- rv = conn_recv_stream(conn, &fr->stream); ++ rv = conn_recv_stream(conn, &fr.stream, ts); + if (rv != 0) { + return rv; + } +@@ -9041,40 +9572,40 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + break; + case NGTCP2_FRAME_CRYPTO: + rv = conn_recv_crypto(conn, NGTCP2_ENCRYPTION_LEVEL_1RTT, +- &pktns->crypto.strm, &fr->stream); ++ &pktns->crypto.strm, &fr.stream, ts); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_RESET_STREAM: +- rv = conn_recv_reset_stream(conn, &fr->reset_stream); ++ rv = conn_recv_reset_stream(conn, &fr.reset_stream, ts); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_STOP_SENDING: +- rv = conn_recv_stop_sending(conn, &fr->stop_sending); ++ rv = conn_recv_stop_sending(conn, &fr.stop_sending, ts); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_MAX_STREAM_DATA: +- rv = conn_recv_max_stream_data(conn, &fr->max_stream_data); ++ rv = conn_recv_max_stream_data(conn, &fr.max_stream_data, ts); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_MAX_DATA: +- conn_recv_max_data(conn, &fr->max_data); ++ conn_recv_max_data(conn, &fr.max_data); + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_MAX_STREAMS_BIDI: + case NGTCP2_FRAME_MAX_STREAMS_UNI: +- rv = conn_recv_max_streams(conn, &fr->max_streams); ++ rv = conn_recv_max_streams(conn, &fr.max_streams); + if (rv != 0) { + return rv; + } +@@ -9082,41 +9613,42 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + break; + case NGTCP2_FRAME_CONNECTION_CLOSE: + case NGTCP2_FRAME_CONNECTION_CLOSE_APP: +- rv = conn_recv_connection_close(conn, &fr->connection_close); ++ rv = conn_recv_connection_close(conn, &fr.connection_close); + if (rv != 0) { + return rv; + } + break; + case NGTCP2_FRAME_PING: ++ ++conn->cstat.ping_recv; + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_PATH_CHALLENGE: +- conn_recv_path_challenge(conn, path, &fr->path_challenge); ++ conn_recv_path_challenge(conn, path, &fr.path_challenge); + path_challenge_recved = 1; + break; + case NGTCP2_FRAME_PATH_RESPONSE: +- rv = conn_recv_path_response(conn, &hd, &fr->path_response, ts); ++ rv = conn_recv_path_response(conn, &hd, &fr.path_response, ts); + if (rv != 0) { + return rv; + } + break; + case NGTCP2_FRAME_NEW_CONNECTION_ID: +- rv = conn_recv_new_connection_id(conn, &fr->new_connection_id); ++ rv = conn_recv_new_connection_id(conn, &fr.new_connection_id); + if (rv != 0) { + return rv; + } + recv_ncid = 1; + break; + case NGTCP2_FRAME_RETIRE_CONNECTION_ID: +- rv = conn_recv_retire_connection_id(conn, &hd, &fr->retire_connection_id, +- ts); ++ rv = ++ conn_recv_retire_connection_id(conn, &hd, &fr.retire_connection_id, ts); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_NEW_TOKEN: +- rv = conn_recv_new_token(conn, &fr->new_token); ++ rv = conn_recv_new_token(conn, &fr.new_token); + if (rv != 0) { + return rv; + } +@@ -9130,28 +9662,28 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_STREAMS_BLOCKED_BIDI: +- rv = conn_recv_streams_blocked_bidi(conn, &fr->streams_blocked); ++ rv = conn_recv_streams_blocked_bidi(conn, &fr.streams_blocked); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_STREAMS_BLOCKED_UNI: +- rv = conn_recv_streams_blocked_uni(conn, &fr->streams_blocked); ++ rv = conn_recv_streams_blocked_uni(conn, &fr.streams_blocked); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_STREAM_DATA_BLOCKED: +- rv = conn_recv_stream_data_blocked(conn, &fr->stream_data_blocked); ++ rv = conn_recv_stream_data_blocked(conn, &fr.stream_data_blocked); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_DATA_BLOCKED: +- rv = conn_recv_data_blocked(conn, &fr->data_blocked); ++ rv = conn_recv_data_blocked(conn, &fr.data_blocked); + if (rv != 0) { + return rv; + } +@@ -9163,7 +9695,7 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + conn->local.transport_params.max_datagram_frame_size) { + return NGTCP2_ERR_PROTO; + } +- rv = conn_recv_datagram(conn, &fr->datagram); ++ rv = conn_recv_datagram(conn, &fr.datagram); + if (rv != 0) { + return rv; + } +@@ -9171,7 +9703,7 @@ static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + break; + } + +- ngtcp2_qlog_write_frame(&conn->qlog, fr); ++ ngtcp2_qlog_write_frame(&conn->qlog, &fr); + } + + ngtcp2_qlog_pkt_received_end(&conn->qlog, &hd, pktlen); +@@ -9286,6 +9818,7 @@ static int conn_process_buffered_protected_pkt(ngtcp2_conn *conn, + *ppc = next; + if (nread < 0) { + if (nread == NGTCP2_ERR_DISCARD_PKT) { ++ ++conn->cstat.pkt_discarded; + continue; + } + return (int)nread; +@@ -9320,6 +9853,7 @@ static int conn_process_buffered_handshake_pkt(ngtcp2_conn *conn, + *ppc = next; + if (nread < 0) { + if (nread == NGTCP2_ERR_DISCARD_PKT) { ++ ++conn->cstat.pkt_discarded; + continue; + } + return (int)nread; +@@ -9465,6 +9999,7 @@ static int conn_recv_cpkt(ngtcp2_conn *conn, const ngtcp2_path *path, + } + } + if (nread == NGTCP2_ERR_DISCARD_PKT) { ++ ++conn->cstat.pkt_discarded; + return 0; + } + return (int)nread; +@@ -9474,8 +10009,11 @@ static int conn_recv_cpkt(ngtcp2_conn *conn, const ngtcp2_path *path, + pkt += nread; + pktlen -= (size_t)nread; + +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, +- "read packet %td left %zu", nread, pktlen); ++ ++conn->cstat.pkt_recv; ++ conn->cstat.bytes_recv += (uint64_t)nread; ++ ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_PKT, ++ "read packet %td left %zu", nread, pktlen); + } + + return 0; +@@ -9497,7 +10035,7 @@ static int conn_enqueue_handshake_done(ngtcp2_conn *conn) { + return rv; + } + +- nfrc->fr.type = NGTCP2_FRAME_HANDSHAKE_DONE; ++ nfrc->fr.handshake_done.type = NGTCP2_FRAME_HANDSHAKE_DONE; + nfrc->next = pktns->tx.frq; + pktns->tx.frq = nfrc; + +@@ -9647,9 +10185,9 @@ static ngtcp2_ssize conn_read_handshake(ngtcp2_conn *conn, + if ((size_t)nread < pktlen) { + /* We have 1RTT packet and application rx key, but the + handshake has not completed yet. */ +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, +- "buffering 1RTT packet len=%zu", +- pktlen - (size_t)nread); ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_CON, ++ "buffering 1RTT packet len=%zu", ++ pktlen - (size_t)nread); + + rv = conn_buffer_pkt(conn, &conn->pktns, path, pi, pkt + nread, + pktlen - (size_t)nread, pktlen, ts); +@@ -9728,8 +10266,8 @@ int ngtcp2_conn_read_pkt_versioned(ngtcp2_conn *conn, const ngtcp2_path *path, + + conn_update_timestamp(conn, ts); + +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "recv packet len=%zu", +- pktlen); ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_CON, "recv packet len=%zu", ++ pktlen); + + if (pktlen == 0) { + return 0; +@@ -9741,6 +10279,8 @@ int ngtcp2_conn_read_pkt_versioned(ngtcp2_conn *conn, const ngtcp2_path *path, + !ngtcp2_dcidtr_check_path_retired(&conn->dcid.dtr, path)) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "ignore packet from unknown path"); ++ ++conn->cstat.pkt_discarded; ++ + return 0; + } + +@@ -9814,6 +10354,49 @@ int ngtcp2_conn_read_pkt_versioned(ngtcp2_conn *conn, const ngtcp2_path *path, + return conn_recv_cpkt(conn, path, pi, pkt, pktlen, ts); + } + ++int ngtcp2_conn_continue_handshake(ngtcp2_conn *conn, ngtcp2_tstamp ts) { ++ int rv; ++ ngtcp2_encryption_level encryption_level; ++ uint64_t offset; ++ ++ conn_update_timestamp(conn, ts); ++ ++ switch (conn->state) { ++ case NGTCP2_CS_CLIENT_INITIAL: ++ case NGTCP2_CS_CLIENT_WAIT_HANDSHAKE: ++ case NGTCP2_CS_SERVER_INITIAL: ++ case NGTCP2_CS_SERVER_WAIT_HANDSHAKE: ++ /* Most of the handshake interruption happens in Initial ++ encryption level, but this might not be the case depending on ++ the TLS stack and its functionality and where interruption ++ occurs. After all, we do not need to support all kinds of ++ interruptions. */ ++ if (conn->in_pktns) { ++ encryption_level = NGTCP2_ENCRYPTION_LEVEL_INITIAL; ++ offset = ngtcp2_strm_rx_offset(&conn->in_pktns->crypto.strm); ++ } else if (conn->hs_pktns) { ++ encryption_level = NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE; ++ offset = ngtcp2_strm_rx_offset(&conn->hs_pktns->crypto.strm); ++ } else { ++ return 0; ++ } ++ ++ rv = conn_call_recv_crypto_data(conn, encryption_level, offset, NULL, 0); ++ if (rv != 0) { ++ return rv; ++ } ++ ++ return (int)conn_read_handshake(conn, /* path = */ NULL, /* pi = */ NULL, ++ /* pkt = */ NULL, 0, ts); ++ case NGTCP2_CS_CLOSING: ++ return NGTCP2_ERR_CLOSING; ++ case NGTCP2_CS_DRAINING: ++ return NGTCP2_ERR_DRAINING; ++ default: ++ return 0; ++ } ++} ++ + /* + * conn_check_pkt_num_exhausted returns nonzero if packet number is + * exhausted in at least one of packet number space. +@@ -9915,6 +10498,7 @@ static ngtcp2_ssize conn_write_handshake(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + size_t origlen = destlen; + uint64_t pending_early_datalen; + ngtcp2_preferred_addr *paddr; ++ ngtcp2_stateless_reset_token token; + + switch (conn->state) { + case NGTCP2_CS_CLIENT_INITIAL: +@@ -10042,8 +10626,8 @@ static ngtcp2_ssize conn_write_handshake(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + assert(!ngtcp2_dcidtr_unused_full(&conn->dcid.dtr)); + + paddr = &conn->remote.transport_params->preferred_addr; +- ngtcp2_dcidtr_push_unused(&conn->dcid.dtr, 1, &paddr->cid, +- paddr->stateless_reset_token); ++ memcpy(token.data, paddr->stateless_reset_token, sizeof(token.data)); ++ ngtcp2_dcidtr_push_unused(&conn->dcid.dtr, 1, &paddr->cid, &token); + + rv = ngtcp2_gaptr_push(&conn->dcid.seqgap, 1, 1); + if (rv != 0) { +@@ -10051,12 +10635,12 @@ static ngtcp2_ssize conn_write_handshake(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + } + } + +- if (conn->remote.transport_params->stateless_reset_token_present) { +- assert(conn->dcid.current.seq == 0); ++ if (conn->remote.transport_params->stateless_reset_token_present && ++ conn->dcid.current.seq == 0) { + assert(!(conn->dcid.current.flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT)); +- ngtcp2_dcid_set_token( +- &conn->dcid.current, +- conn->remote.transport_params->stateless_reset_token); ++ memcpy(token.data, conn->remote.transport_params->stateless_reset_token, ++ sizeof(token.data)); ++ ngtcp2_dcid_set_token(&conn->dcid.current, &token); + } + + rv = conn_call_activate_dcid(conn, &conn->dcid.current); +@@ -10095,8 +10679,6 @@ static ngtcp2_ssize conn_write_handshake(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + } + + res += nwrite; +- dest += nwrite; +- destlen -= (size_t)nwrite; + } + + if (res == 0) { +@@ -10106,8 +10688,6 @@ static ngtcp2_ssize conn_write_handshake(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + } + + res += nwrite; +- dest += nwrite; +- origlen -= (size_t)nwrite; + } + + return res; +@@ -10343,6 +10923,9 @@ int ngtcp2_conn_install_initial_key( + rv = ngtcp2_crypto_km_new(&pktns->crypto.tx.ckm, NULL, 0, NULL, tx_iv, ivlen, + conn->mem); + if (rv != 0) { ++ ngtcp2_crypto_km_del(pktns->crypto.rx.ckm, conn->mem); ++ pktns->crypto.rx.ckm = NULL; ++ + return rv; + } + +@@ -10393,6 +10976,9 @@ int ngtcp2_conn_install_vneg_initial_key( + rv = ngtcp2_crypto_km_new(&conn->vneg.tx.ckm, NULL, 0, NULL, tx_iv, ivlen, + conn->mem); + if (rv != 0) { ++ ngtcp2_crypto_km_del(conn->vneg.rx.ckm, conn->mem); ++ conn->vneg.rx.ckm = NULL; ++ + return rv; + } + +@@ -10430,8 +11016,7 @@ int ngtcp2_conn_install_rx_handshake_key( + if (rv != 0) { + ngtcp2_crypto_km_del(pktns->crypto.rx.ckm, conn->mem); + pktns->crypto.rx.ckm = NULL; +- +- memset(&pktns->crypto.rx.hp_ctx, 0, sizeof(pktns->crypto.rx.hp_ctx)); ++ pktns->crypto.rx.hp_ctx = (ngtcp2_crypto_cipher_ctx){0}; + + return rv; + } +@@ -10461,21 +11046,26 @@ int ngtcp2_conn_install_tx_handshake_key( + if (conn->server) { + rv = ngtcp2_conn_commit_local_transport_params(conn); + if (rv != 0) { +- return rv; ++ goto fail; + } + } + + rv = conn_call_recv_tx_key(conn, NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE); + if (rv != 0) { +- ngtcp2_crypto_km_del(pktns->crypto.tx.ckm, conn->mem); +- pktns->crypto.tx.ckm = NULL; +- +- memset(&pktns->crypto.tx.hp_ctx, 0, sizeof(pktns->crypto.tx.hp_ctx)); +- +- return rv; ++ goto fail; + } + + return 0; ++ ++fail: ++ /* If this function fails, aead_ctx and hp_ctx are still owned by ++ the caller. Delete the install key to remove the any reference ++ to them. */ ++ ngtcp2_crypto_km_del(pktns->crypto.tx.ckm, conn->mem); ++ pktns->crypto.tx.ckm = NULL; ++ pktns->crypto.tx.hp_ctx = (ngtcp2_crypto_cipher_ctx){0}; ++ ++ return rv; + } + + int ngtcp2_conn_install_0rtt_key(ngtcp2_conn *conn, +@@ -10506,8 +11096,7 @@ int ngtcp2_conn_install_0rtt_key(ngtcp2_conn *conn, + if (rv != 0) { + ngtcp2_crypto_km_del(conn->early.ckm, conn->mem); + conn->early.ckm = NULL; +- +- memset(&conn->early.hp_ctx, 0, sizeof(conn->early.hp_ctx)); ++ conn->early.hp_ctx = (ngtcp2_crypto_cipher_ctx){0}; + + return rv; + } +@@ -10554,8 +11143,7 @@ int ngtcp2_conn_install_rx_key(ngtcp2_conn *conn, const uint8_t *secret, + if (rv != 0) { + ngtcp2_crypto_km_del(pktns->crypto.rx.ckm, conn->mem); + pktns->crypto.rx.ckm = NULL; +- +- memset(&pktns->crypto.rx.hp_ctx, 0, sizeof(pktns->crypto.rx.hp_ctx)); ++ pktns->crypto.rx.hp_ctx = (ngtcp2_crypto_cipher_ctx){0}; + + return rv; + } +@@ -10600,8 +11188,7 @@ int ngtcp2_conn_install_tx_key(ngtcp2_conn *conn, const uint8_t *secret, + if (rv != 0) { + ngtcp2_crypto_km_del(pktns->crypto.tx.ckm, conn->mem); + pktns->crypto.tx.ckm = NULL; +- +- memset(&pktns->crypto.tx.hp_ctx, 0, sizeof(pktns->crypto.tx.hp_ctx)); ++ pktns->crypto.tx.hp_ctx = (ngtcp2_crypto_cipher_ctx){0}; + + return rv; + } +@@ -11059,9 +11646,9 @@ int ngtcp2_conn_set_remote_transport_params( + conn->local.transport_params.version_info.chosen_version = + conn->negotiated_version; + +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, +- "the negotiated version is 0x%08x", +- conn->negotiated_version); ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_CON, ++ "the negotiated version is 0x%08x", ++ conn->negotiated_version); + } else { + rv = conn_client_validate_transport_params(conn, params); + if (rv != 0) { +@@ -11213,21 +11800,18 @@ int ngtcp2_conn_set_0rtt_remote_transport_params( + /* These parameters are treated specially. If server accepts early + data, it must not set values for these parameters that are + smaller than these remembered values. */ +- conn->early.transport_params.initial_max_streams_bidi = +- params->initial_max_streams_bidi; +- conn->early.transport_params.initial_max_streams_uni = +- params->initial_max_streams_uni; +- conn->early.transport_params.initial_max_stream_data_bidi_local = +- params->initial_max_stream_data_bidi_local; +- conn->early.transport_params.initial_max_stream_data_bidi_remote = +- params->initial_max_stream_data_bidi_remote; +- conn->early.transport_params.initial_max_stream_data_uni = +- params->initial_max_stream_data_uni; +- conn->early.transport_params.initial_max_data = params->initial_max_data; +- conn->early.transport_params.active_connection_id_limit = +- params->active_connection_id_limit; +- conn->early.transport_params.max_datagram_frame_size = +- params->max_datagram_frame_size; ++ conn->early.transport_params = (ngtcp2_early_transport_params){ ++ .initial_max_streams_bidi = params->initial_max_streams_bidi, ++ .initial_max_streams_uni = params->initial_max_streams_uni, ++ .initial_max_stream_data_bidi_local = ++ params->initial_max_stream_data_bidi_local, ++ .initial_max_stream_data_bidi_remote = ++ params->initial_max_stream_data_bidi_remote, ++ .initial_max_stream_data_uni = params->initial_max_stream_data_uni, ++ .initial_max_data = params->initial_max_data, ++ .active_connection_id_limit = params->active_connection_id_limit, ++ .max_datagram_frame_size = params->max_datagram_frame_size, ++ }; + + conn_sync_stream_id_limit(conn); + +@@ -11417,17 +12001,20 @@ conn_write_vmsg_wrapper(ngtcp2_conn *conn, ngtcp2_path *path, + return nwrite; + } + ++ assert((size_t)nwrite <= destlen); ++ + if (cstat->bytes_in_flight >= cstat->cwnd) { + conn->rst.is_cwnd_limited = 1; + } else if ((cstat->cwnd >= cstat->ssthresh || + cstat->bytes_in_flight * 2 < cstat->cwnd) && + nwrite == 0 && conn_pacing_pkt_tx_allowed(conn, ts) && +- (conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED)) { +- conn->rst.app_limited = conn->rst.delivered + cstat->bytes_in_flight; +- +- if (conn->rst.app_limited == 0) { +- conn->rst.app_limited = cstat->max_tx_udp_payload_size; +- } ++ (conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED) && ++ /* Because NGTCP2_CONN_FLAG_AGGREGATE_PKTS is set after a ++ packet is produced, if it is set, we are sure that we ++ are not app-limited. */ ++ !(conn->flags & NGTCP2_CONN_FLAG_AGGREGATE_PKTS)) { ++ conn->rst.app_limited = ++ ngtcp2_max_uint64(conn->rst.delivered + cstat->bytes_in_flight, 1); + } + + return nwrite; +@@ -11471,12 +12058,17 @@ ngtcp2_ssize ngtcp2_conn_writev_stream_versioned( + return NGTCP2_ERR_INVALID_ARGUMENT; + } + +- vmsg.type = NGTCP2_VMSG_TYPE_STREAM; +- vmsg.stream.strm = strm; +- vmsg.stream.flags = flags; +- vmsg.stream.data = datav; +- vmsg.stream.datacnt = datavcnt; +- vmsg.stream.pdatalen = pdatalen; ++ vmsg = (ngtcp2_vmsg){ ++ .type = NGTCP2_VMSG_TYPE_STREAM, ++ .stream = ++ { ++ .strm = strm, ++ .data = datav, ++ .datacnt = datavcnt, ++ .pdatalen = pdatalen, ++ .flags = flags, ++ }, ++ }; + + pvmsg = &vmsg; + } +@@ -11549,12 +12141,17 @@ ngtcp2_ssize ngtcp2_conn_writev_datagram_versioned( + return NGTCP2_ERR_INVALID_ARGUMENT; + } + +- vmsg.type = NGTCP2_VMSG_TYPE_DATAGRAM; +- vmsg.datagram.dgram_id = dgram_id; +- vmsg.datagram.flags = flags; +- vmsg.datagram.data = datav; +- vmsg.datagram.datacnt = datavcnt; +- vmsg.datagram.paccepted = paccepted; ++ vmsg = (ngtcp2_vmsg){ ++ .type = NGTCP2_VMSG_TYPE_DATAGRAM, ++ .datagram = ++ { ++ .data = datav, ++ .datacnt = datavcnt, ++ .dgram_id = dgram_id, ++ .paccepted = paccepted, ++ .flags = flags, ++ }, ++ }; + + if (flags & NGTCP2_WRITE_DATAGRAM_FLAG_PADDING) { + wflags = NGTCP2_WRITE_PKT_FLAG_PADDING_IF_NOT_EMPTY; +@@ -11610,27 +12207,29 @@ ngtcp2_ssize ngtcp2_conn_write_vmsg(ngtcp2_conn *conn, ngtcp2_path *path, + conn_client_write_handshake(conn, pi, dest, destlen, wflags, vmsg, ts); + /* We might be unable to write a packet because of depletion of + congestion window budget, perhaps due to packet loss that +- shrinks the window drastically. */ +- if (nwrite <= 0) { ++ shrinks the window drastically. Then continue if we are in ++ post-handshake. There, we might be able to write packets ++ exceeding CWND to avoid deadlock. */ ++ if (nwrite < 0) { + return nwrite; + } + if (conn->state != NGTCP2_CS_POST_HANDSHAKE) { + return nwrite; + } + +- assert(nwrite); +- assert(dest[0] & NGTCP2_HEADER_FORM_BIT); +- assert(conn->negotiated_version); ++ if (nwrite) { ++ assert(dest[0] & NGTCP2_HEADER_FORM_BIT); ++ assert(conn->negotiated_version); + +- if (nwrite < NGTCP2_MAX_UDP_PAYLOAD_SIZE && +- ngtcp2_pkt_get_type_long(conn->negotiated_version, dest[0]) == ++ if (ngtcp2_pkt_get_type_long(conn->negotiated_version, dest[0]) == + NGTCP2_PKT_INITIAL) { +- wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; +- } ++ wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; ++ } + +- res = nwrite; +- dest += nwrite; +- destlen -= (size_t)nwrite; ++ res = nwrite; ++ dest += nwrite; ++ destlen -= (size_t)nwrite; ++ } + /* Break here so that we can coalesces 1RTT packet. */ + break; + case NGTCP2_CS_SERVER_INITIAL: +@@ -11685,7 +12284,7 @@ ngtcp2_ssize ngtcp2_conn_write_vmsg(ngtcp2_conn *conn, ngtcp2_path *path, + dest += nwrite; + destlen -= (size_t)nwrite; + +- if (res < NGTCP2_MAX_UDP_PAYLOAD_SIZE && conn->in_pktns && nwrite > 0) { ++ if (conn->in_pktns && nwrite > 0) { + it = ngtcp2_rtb_head(&conn->in_pktns->rtb); + if (!ngtcp2_ksl_it_end(&it)) { + rtbent = ngtcp2_ksl_it_get(&it); +@@ -11770,7 +12369,7 @@ ngtcp2_ssize ngtcp2_conn_write_vmsg(ngtcp2_conn *conn, ngtcp2_path *path, + if (!conn->pktns.rtb.probe_pkt_left && conn_cwnd_is_zero(conn)) { + destlen = 0; + } else { +- if (res == 0) { ++ if (res == 0 && !(conn->flags & NGTCP2_CONN_FLAG_AGGREGATE_PKTS)) { + nwrite = + conn_write_path_response(conn, path, pi, dest, origdestlen, ts); + if (nwrite) { +@@ -11832,9 +12431,22 @@ ngtcp2_ssize ngtcp2_conn_write_vmsg(ngtcp2_conn *conn, ngtcp2_path *path, + return nwrite; + } + if (nwrite > 0) { ++ /* This makes 1RTT packet padded. If 1RTT packet is not going ++ to be sent, packet is already padded. */ ++ if (ngtcp2_pkt_get_type_long(conn->negotiated_version, dest[0]) == ++ NGTCP2_PKT_INITIAL) { ++ wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; ++ } ++ + res = nwrite; + dest += nwrite; + destlen -= (size_t)nwrite; ++ ++ /* We only exceed CWND to avoid deadlock. Do no write 1RTT ++ packet if CWND is depleted. */ ++ if (conn_cwnd_is_zero(conn) && conn->pktns.rtb.probe_pkt_left == 0) { ++ return res; ++ } + } else if (destlen == 0) { + res = conn_write_handshake_ack_pkts(conn, pi, dest, origlen, ts); + if (res) { +@@ -11845,9 +12457,9 @@ ngtcp2_ssize ngtcp2_conn_write_vmsg(ngtcp2_conn *conn, ngtcp2_path *path, + } + + if (conn->pktns.rtb.probe_pkt_left) { +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, +- "transmit probe pkt left=%zu", +- conn->pktns.rtb.probe_pkt_left); ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_CON, ++ "transmit probe pkt left=%zu", ++ conn->pktns.rtb.probe_pkt_left); + + nwrite = conn_write_pkt(conn, pi, dest, destlen, (size_t)res, vmsg, + NGTCP2_PKT_1RTT, wflags, ts); +@@ -11901,11 +12513,12 @@ conn_write_connection_close(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + ngtcp2_frame fr; + uint8_t flags = NGTCP2_WRITE_PKT_FLAG_NONE; + +- fr.type = NGTCP2_FRAME_CONNECTION_CLOSE; +- fr.connection_close.error_code = error_code; +- fr.connection_close.frame_type = 0; +- fr.connection_close.reasonlen = reasonlen; +- fr.connection_close.reason = (uint8_t *)reason; ++ fr.connection_close = (ngtcp2_connection_close){ ++ .type = NGTCP2_FRAME_CONNECTION_CLOSE, ++ .error_code = error_code, ++ .reasonlen = reasonlen, ++ .reason = (uint8_t *)reason, ++ }; + + if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) && + pkt_type != NGTCP2_PKT_INITIAL) { +@@ -12080,11 +12693,12 @@ ngtcp2_ssize ngtcp2_conn_write_application_close_pkt( + + assert(conn->pktns.crypto.tx.ckm); + +- fr.type = NGTCP2_FRAME_CONNECTION_CLOSE_APP; +- fr.connection_close.error_code = app_error_code; +- fr.connection_close.frame_type = 0; +- fr.connection_close.reasonlen = reasonlen; +- fr.connection_close.reason = (uint8_t *)reason; ++ fr.connection_close = (ngtcp2_connection_close){ ++ .type = NGTCP2_FRAME_CONNECTION_CLOSE_APP, ++ .error_code = app_error_code, ++ .reasonlen = reasonlen, ++ .reason = (uint8_t *)reason, ++ }; + + nwrite = ngtcp2_conn_write_single_frame_pkt( + conn, pi, dest, destlen, NGTCP2_PKT_1RTT, NGTCP2_WRITE_PKT_FLAG_NONE, +@@ -12108,11 +12722,12 @@ ngtcp2_ssize ngtcp2_conn_write_application_close_pkt( + static void ccerr_init(ngtcp2_ccerr *ccerr, ngtcp2_ccerr_type type, + uint64_t error_code, const uint8_t *reason, + size_t reasonlen) { +- ccerr->type = type; +- ccerr->error_code = error_code; +- ccerr->frame_type = 0; +- ccerr->reason = (uint8_t *)reason; +- ccerr->reasonlen = reasonlen; ++ *ccerr = (ngtcp2_ccerr){ ++ .type = type, ++ .error_code = error_code, ++ .reason = (uint8_t *)reason, ++ .reasonlen = reasonlen, ++ }; + } + + void ngtcp2_ccerr_default(ngtcp2_ccerr *ccerr) { +@@ -12147,7 +12762,7 @@ void ngtcp2_ccerr_set_liberr(ngtcp2_ccerr *ccerr, int liberr, + reasonlen); + + return; +- }; ++ } + + ngtcp2_ccerr_set_transport_error( + ccerr, ngtcp2_err_infer_quic_transport_error_code(liberr), reason, +@@ -12528,8 +13143,8 @@ int ngtcp2_conn_get_tls_early_data_rejected(ngtcp2_conn *conn) { + return (conn->flags & NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED) != 0; + } + +-int ngtcp2_conn_update_rtt(ngtcp2_conn *conn, ngtcp2_duration rtt, +- ngtcp2_duration ack_delay, ngtcp2_tstamp ts) { ++void ngtcp2_conn_update_rtt(ngtcp2_conn *conn, ngtcp2_duration rtt, ++ ngtcp2_duration ack_delay, ngtcp2_tstamp ts) { + ngtcp2_conn_stat *cstat = &conn->cstat; + + assert(rtt > 0); +@@ -12550,13 +13165,13 @@ int ngtcp2_conn_update_rtt(ngtcp2_conn *conn, ngtcp2_duration rtt, + rtt < cstat->min_rtt + ack_delay) { + /* Ignore RTT sample if adjusting ack_delay causes the sample + less than min_rtt before handshake confirmation. */ +- ngtcp2_log_info( ++ ngtcp2_log_infof( + &conn->log, NGTCP2_LOG_EVENT_LDC, + "ignore rtt sample because ack_delay is too large latest_rtt=%" PRIu64 + " min_rtt=%" PRIu64 " ack_delay=%" PRIu64, + rtt / NGTCP2_MILLISECONDS, cstat->min_rtt / NGTCP2_MILLISECONDS, + ack_delay / NGTCP2_MILLISECONDS); +- return NGTCP2_ERR_INVALID_ARGUMENT; ++ return; + } + + cstat->latest_rtt = rtt; +@@ -12573,7 +13188,7 @@ int ngtcp2_conn_update_rtt(ngtcp2_conn *conn, ngtcp2_duration rtt, + cstat->smoothed_rtt = (cstat->smoothed_rtt * 7 + rtt) / 8; + } + +- ngtcp2_log_info( ++ ngtcp2_log_infof( + &conn->log, NGTCP2_LOG_EVENT_LDC, + "latest_rtt=%" PRIu64 " min_rtt=%" PRIu64 " smoothed_rtt=%" PRIu64 + " rttvar=%" PRIu64 " ack_delay=%" PRIu64, +@@ -12581,23 +13196,12 @@ int ngtcp2_conn_update_rtt(ngtcp2_conn *conn, ngtcp2_duration rtt, + cstat->min_rtt / NGTCP2_MILLISECONDS, + cstat->smoothed_rtt / NGTCP2_MILLISECONDS, + cstat->rttvar / NGTCP2_MILLISECONDS, ack_delay / NGTCP2_MILLISECONDS); +- +- return 0; + } + + void ngtcp2_conn_get_conn_info_versioned(ngtcp2_conn *conn, + int conn_info_version, + ngtcp2_conn_info *cinfo) { +- const ngtcp2_conn_stat *cstat = &conn->cstat; +- (void)conn_info_version; +- +- cinfo->latest_rtt = cstat->latest_rtt; +- cinfo->min_rtt = cstat->min_rtt; +- cinfo->smoothed_rtt = cstat->smoothed_rtt; +- cinfo->rttvar = cstat->rttvar; +- cinfo->cwnd = cstat->cwnd; +- cinfo->ssthresh = cstat->ssthresh; +- cinfo->bytes_in_flight = cstat->bytes_in_flight; ++ ngtcp2_conn_info_init_versioned(conn_info_version, cinfo, &conn->cstat); + } + + static void conn_get_loss_time_and_pktns(ngtcp2_conn *conn, +@@ -12679,9 +13283,10 @@ void ngtcp2_conn_set_loss_detection_timer(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + if (earliest_loss_time != UINT64_MAX) { + cstat->loss_detection_timer = earliest_loss_time; + +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_LDC, +- "loss_detection_timer=%" PRIu64 " nonzero crypto loss time", +- cstat->loss_detection_timer); ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_LDC, ++ "loss_detection_timer=%" PRIu64 ++ " nonzero crypto loss time", ++ cstat->loss_detection_timer); + return; + } + +@@ -12705,9 +13310,9 @@ void ngtcp2_conn_set_loss_detection_timer(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + timeout = + cstat->loss_detection_timer > ts ? cstat->loss_detection_timer - ts : 0; + +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_LDC, +- "loss_detection_timer=%" PRIu64 " timeout=%" PRIu64, +- cstat->loss_detection_timer, timeout / NGTCP2_MILLISECONDS); ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_LDC, ++ "loss_detection_timer=%" PRIu64 " timeout=%" PRIu64, ++ cstat->loss_detection_timer, timeout / NGTCP2_MILLISECONDS); + } + + void ngtcp2_conn_cancel_loss_detection_timer(ngtcp2_conn *conn) { +@@ -12779,8 +13384,8 @@ int ngtcp2_conn_on_loss_detection_timer(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + + ++cstat->pto_count; + +- ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_LDC, "pto_count=%zu", +- cstat->pto_count); ++ ngtcp2_log_infof(&conn->log, NGTCP2_LOG_EVENT_LDC, "pto_count=%zu", ++ cstat->pto_count); + + ngtcp2_conn_set_loss_detection_timer(conn, ts); + +@@ -12821,7 +13426,6 @@ int ngtcp2_conn_submit_crypto_data(ngtcp2_conn *conn, + const uint8_t *data, const size_t datalen) { + ngtcp2_pktns *pktns; + ngtcp2_frame_chain *frc; +- ngtcp2_stream *fr; + int rv; + + if (datalen == 0) { +@@ -12849,21 +13453,22 @@ int ngtcp2_conn_submit_crypto_data(ngtcp2_conn *conn, + return rv; + } + +- rv = ngtcp2_frame_chain_objalloc_new(&frc, &conn->frc_objalloc); ++ rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new( ++ &frc, 1, &conn->frc_objalloc, conn->mem); + if (rv != 0) { + return rv; + } + +- fr = &frc->fr.stream; +- +- fr->type = NGTCP2_FRAME_CRYPTO; +- fr->flags = 0; +- fr->fin = 0; +- fr->stream_id = 0; +- fr->offset = pktns->crypto.tx.offset; +- fr->datacnt = 1; +- fr->data[0].len = datalen; +- fr->data[0].base = (uint8_t *)data; ++ frc->fr.stream.type = NGTCP2_FRAME_CRYPTO; ++ frc->fr.stream.flags = 0; ++ frc->fr.stream.fin = 0; ++ frc->fr.stream.stream_id = 0; ++ frc->fr.stream.offset = pktns->crypto.tx.offset; ++ frc->fr.stream.datacnt = 1; ++ frc->fr.stream.data[0] = (ngtcp2_vec){ ++ .base = (uint8_t *)data, ++ .len = datalen, ++ }; + + rv = ngtcp2_strm_streamfrq_push(&pktns->crypto.strm, frc); + if (rv != 0) { +@@ -12968,20 +13573,46 @@ static size_t conn_get_num_active_dcid(ngtcp2_conn *conn) { + return n; + } + +-static void copy_dcid_to_cid_token(ngtcp2_cid_token *dest, ++size_t ngtcp2_conn_get_active_dcid(ngtcp2_conn *conn, ngtcp2_cid_token *dest) { ++ ngtcp2_cid_token2 cid_tokens[/* current */ 1 + /* pv */ 2 + ++ NGTCP2_DCIDTR_MAX_RETIRED_DCID_SIZE]; ++ size_t n, i; ++ ++ if (!dest) { ++ return ngtcp2_conn_get_active_dcid2(conn, NULL); ++ } ++ ++ n = ngtcp2_conn_get_active_dcid2(conn, cid_tokens); ++ ++ for (i = 0; i < n; ++i) { ++ dest[i].seq = cid_tokens[i].seq; ++ dest[i].cid = cid_tokens[i].cid; ++ ngtcp2_path_storage_init2(&dest[i].ps, &cid_tokens[i].ps.path); ++ dest[i].token_present = cid_tokens[i].token_present; ++ ++ if (dest[i].token_present) { ++ memcpy(dest[i].token, cid_tokens[i].token.data, sizeof(dest[i].token)); ++ } ++ } ++ ++ return n; ++} ++ ++static void copy_dcid_to_cid_token(ngtcp2_cid_token2 *dest, + const ngtcp2_dcid *src) { + dest->seq = src->seq; + dest->cid = src->cid; + ngtcp2_path_storage_init2(&dest->ps, &src->ps.path); + if ((dest->token_present = + (src->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) != 0)) { +- memcpy(dest->token, src->token, NGTCP2_STATELESS_RESET_TOKENLEN); ++ dest->token = src->token; + } + } + +-size_t ngtcp2_conn_get_active_dcid(ngtcp2_conn *conn, ngtcp2_cid_token *dest) { ++size_t ngtcp2_conn_get_active_dcid2(ngtcp2_conn *conn, ++ ngtcp2_cid_token2 *dest) { + ngtcp2_pv *pv = conn->pv; +- ngtcp2_cid_token *orig = dest; ++ ngtcp2_cid_token2 *orig = dest; + ngtcp2_dcid *dcid; + size_t len, i; + +@@ -13138,7 +13769,12 @@ int ngtcp2_conn_initiate_immediate_migration(ngtcp2_conn *conn, + conn->pv = pv; + } + +- return conn_call_activate_dcid(conn, &conn->dcid.current); ++ rv = conn_call_activate_dcid(conn, &conn->dcid.current); ++ if (rv != 0) { ++ return rv; ++ } ++ ++ return conn_call_begin_path_validation(conn, conn->pv); + } + + int ngtcp2_conn_initiate_migration(ngtcp2_conn *conn, const ngtcp2_path *path, +@@ -13178,7 +13814,12 @@ int ngtcp2_conn_initiate_migration(ngtcp2_conn *conn, const ngtcp2_path *path, + + conn->pv = pv; + +- return conn_call_activate_dcid(conn, &pv->dcid); ++ rv = conn_call_activate_dcid(conn, &pv->dcid); ++ if (rv != 0) { ++ return rv; ++ } ++ ++ return conn_call_begin_path_validation(conn, conn->pv); + } + + uint64_t ngtcp2_conn_get_max_data_left(ngtcp2_conn *conn) { +@@ -13354,8 +13995,17 @@ int ngtcp2_conn_set_stream_user_data(ngtcp2_conn *conn, int64_t stream_id, + return 0; + } + ++void *ngtcp2_conn_get_stream_user_data(ngtcp2_conn *conn, int64_t stream_id) { ++ ngtcp2_strm *strm = ngtcp2_conn_find_stream(conn, stream_id); ++ ++ if (strm == NULL) { ++ return NULL; ++ } ++ ++ return strm->stream_user_data; ++} ++ + void ngtcp2_conn_update_pkt_tx_time(ngtcp2_conn *conn, ngtcp2_tstamp ts) { +- uint64_t pacing_interval_m; + ngtcp2_duration wait, d; + + conn_update_timestamp(conn, ts); +@@ -13364,20 +14014,9 @@ void ngtcp2_conn_update_pkt_tx_time(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + return; + } + +- if (conn->cstat.pacing_interval_m) { +- pacing_interval_m = conn->cstat.pacing_interval_m; +- } else { +- /* 1.25 is the under-utilization avoidance factor described in +- https://datatracker.ietf.org/doc/html/rfc9002#section-7.7 */ +- pacing_interval_m = ((conn->cstat.first_rtt_sample_ts == UINT64_MAX +- ? NGTCP2_MILLISECONDS +- : conn->cstat.smoothed_rtt) +- << 10) * +- 100 / 125 / conn->cstat.cwnd; +- pacing_interval_m = ngtcp2_max_uint64(pacing_interval_m, 1); +- } +- +- wait = (ngtcp2_duration)((conn->tx.pacing.pktlen * pacing_interval_m) >> 10); ++ wait = (ngtcp2_duration)((conn->tx.pacing.pktlen * ++ conn->cstat.pacing_interval_m) >> ++ 10); + + d = ngtcp2_min_uint64(wait / 2, conn->tx.pacing.compensation); + wait -= d; +@@ -13411,6 +14050,115 @@ void ngtcp2_conn_add_path_history(ngtcp2_conn *conn, const ngtcp2_dcid *dcid, + ent->ts = ts; + } + ++ngtcp2_ssize ngtcp2_conn_write_aggregate_pkt_versioned( ++ ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, ++ ngtcp2_pkt_info *pi, uint8_t *buf, size_t buflen, size_t *pgsolen, ++ ngtcp2_write_pkt write_pkt, ngtcp2_tstamp ts) { ++ ngtcp2_ssize nwrite; ++ ++ buflen = ngtcp2_min_size(buflen, ngtcp2_conn_get_send_quantum(conn)); ++ ++ nwrite = ngtcp2_conn_write_aggregate_pkt2_versioned( ++ conn, path, pkt_info_version, pi, buf, buflen, pgsolen, write_pkt, 0, ts); ++ if (nwrite < 0) { ++ return nwrite; ++ } ++ ++ ngtcp2_conn_update_pkt_tx_time(conn, ts); ++ ++ return nwrite; ++} ++ ++ngtcp2_ssize ngtcp2_conn_write_aggregate_pkt2_versioned( ++ ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, ++ ngtcp2_pkt_info *pi, uint8_t *buf, size_t buflen, size_t *pgsolen, ++ ngtcp2_write_pkt write_pkt, size_t num_pkts, ngtcp2_tstamp ts) { ++ size_t max_udp_payloadlen = ngtcp2_conn_get_max_tx_udp_payload_size(conn); ++ size_t path_max_udp_payloadlen = ++ ngtcp2_conn_get_path_max_tx_udp_payload_size(conn); ++ ngtcp2_ssize nwrite; ++ uint8_t *wbuf = buf; ++ size_t wbuflen; ++ ngtcp2_ecn_state ecn_state; ++ int first_pkt; ++ ngtcp2_pkt_info pi_discard; ++ ngtcp2_path_storage path_discard; ++ (void)pkt_info_version; ++ ++ assert(buflen >= path_max_udp_payloadlen); ++ ++ if (num_pkts == 0) { ++ num_pkts = SIZE_MAX; ++ } ++ ++ for (;;) { ++ ecn_state = conn->tx.ecn.state; ++ ++ wbuflen = buflen >= max_udp_payloadlen ? max_udp_payloadlen ++ : path_max_udp_payloadlen; ++ ++ nwrite = write_pkt(conn, path, pi, wbuf, wbuflen, ts, conn->user_data); ++ if (nwrite < 0) { ++ break; ++ } ++ ++ if (nwrite == 0) { ++ nwrite = wbuf - buf; ++ break; ++ } ++ ++ first_pkt = buf == wbuf; ++ wbuf += nwrite; ++ buflen -= (size_t)nwrite; ++ ++ --num_pkts; ++ ++ if (first_pkt) { ++ assert(!(conn->flags & NGTCP2_CONN_FLAG_AGGREGATE_PKTS)); ++ ++ *pgsolen = (size_t)nwrite; ++ ++ if ((size_t)nwrite != path_max_udp_payloadlen || ++ buflen < path_max_udp_payloadlen || ecn_state != conn->tx.ecn.state || ++ num_pkts == 0) { ++ nwrite = wbuf - buf; ++ break; ++ } ++ ++ /* All aggregated packets should share the same path and pi. ++ Pass the placeholder values to the callback because they ++ might be overwritten by later calls, especially pi is set to ++ empty when no packet is produced. */ ++ if (path) { ++ ngtcp2_path_storage_zero(&path_discard); ++ path = &path_discard.path; ++ } ++ ++ if (pi) { ++ pi = &pi_discard; ++ } ++ ++ conn->flags |= NGTCP2_CONN_FLAG_AGGREGATE_PKTS; ++ ++ continue; ++ } ++ ++ if (buflen < path_max_udp_payloadlen || (size_t)nwrite < *pgsolen || ++ ecn_state != conn->tx.ecn.state || num_pkts == 0) { ++ nwrite = wbuf - buf; ++ break; ++ } ++ } ++ ++ conn->flags &= ~NGTCP2_CONN_FLAG_AGGREGATE_PKTS; ++ ++ return nwrite; ++} ++ ++ngtcp2_tstamp ngtcp2_conn_get_timestamp(const ngtcp2_conn *conn) { ++ return conn->log.last_ts; ++} ++ + const ngtcp2_path_history_entry * + ngtcp2_conn_find_path_history(ngtcp2_conn *conn, const ngtcp2_path *path, + ngtcp2_tstamp ts) { +@@ -13434,9 +14182,9 @@ ngtcp2_conn_find_path_history(ngtcp2_conn *conn, const ngtcp2_path *path, + + void ngtcp2_path_challenge_entry_init(ngtcp2_path_challenge_entry *pcent, + const ngtcp2_path *path, +- const uint8_t *data) { ++ const ngtcp2_path_challenge_data *data) { + ngtcp2_path_storage_init2(&pcent->ps, path); +- memcpy(pcent->data, data, sizeof(pcent->data)); ++ pcent->data = *data; + } + + /* The functions prefixed with ngtcp2_pkt_ are usually put inside +@@ -13454,7 +14202,7 @@ ngtcp2_ssize ngtcp2_pkt_write_connection_close( + ngtcp2_crypto_km ckm; + ngtcp2_crypto_cc cc; + ngtcp2_ppe ppe; +- ngtcp2_frame fr = {0}; ++ ngtcp2_frame fr; + int rv; + + ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, NGTCP2_PKT_INITIAL, dcid, +@@ -13485,11 +14233,12 @@ ngtcp2_ssize ngtcp2_pkt_write_connection_close( + return NGTCP2_ERR_NOBUF; + } + +- fr.type = NGTCP2_FRAME_CONNECTION_CLOSE; +- fr.connection_close.error_code = error_code; +- fr.connection_close.frame_type = 0; +- fr.connection_close.reasonlen = reasonlen; +- fr.connection_close.reason = (uint8_t *)reason; ++ fr.connection_close = (ngtcp2_connection_close){ ++ .type = NGTCP2_FRAME_CONNECTION_CLOSE, ++ .error_code = error_code, ++ .reasonlen = reasonlen, ++ .reason = (uint8_t *)reason, ++ }; + + rv = ngtcp2_ppe_encode_frame(&ppe, &fr); + if (rv != 0) { +diff --git a/third_party/ngtcp2/lib/ngtcp2_conn.h b/third_party/ngtcp2/lib/ngtcp2_conn.h +index 5979d39654b..47175736d42 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_conn.h ++++ b/third_party/ngtcp2/lib/ngtcp2_conn.h +@@ -52,6 +52,8 @@ + #include "ngtcp2_rst.h" + #include "ngtcp2_conn_stat.h" + #include "ngtcp2_dcidtr.h" ++#include "ngtcp2_pcg.h" ++#include "ngtcp2_ratelim.h" + + typedef enum { + /* Client specific handshake states */ +@@ -75,10 +77,6 @@ typedef enum { + unreceived data. */ + #define NGTCP2_MAX_REORDERED_CRYPTO_DATA 65536 + +-/* NGTCP2_MAX_RETRIES is the number of Retry packet which client can +- accept. */ +-#define NGTCP2_MAX_RETRIES 3 +- + /* NGTCP2_MAX_SCID_POOL_SIZE is the maximum number of source + connection ID the local endpoint provides to the remote endpoint. + The chosen value was described in old draft. Now a remote endpoint +@@ -96,48 +94,35 @@ typedef enum { + #define NGTCP2_CCERR_MAX_REASONLEN 1024 + + /* NGTCP2_WRITE_PKT_FLAG_NONE indicates that no flag is set. */ +-#define NGTCP2_WRITE_PKT_FLAG_NONE 0x00u ++#define NGTCP2_WRITE_PKT_FLAG_NONE 0x00U + /* NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING indicates that packet other + than Initial packet should be padded so that UDP datagram payload + is at least NGTCP2_MAX_UDP_PAYLOAD_SIZE bytes. Initial packet + might be padded based on QUIC requirement regardless of this + flag. */ +-#define NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING 0x01u ++#define NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING 0x01U + /* NGTCP2_WRITE_PKT_FLAG_MORE indicates that more frames might come + and it should be encoded into the current packet. */ +-#define NGTCP2_WRITE_PKT_FLAG_MORE 0x02u ++#define NGTCP2_WRITE_PKT_FLAG_MORE 0x02U + /* NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING_FULL is just like + NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING, but it requests to add + padding to the full UDP datagram payload size. */ +-#define NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING_FULL 0x04u ++#define NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING_FULL 0x04U + /* NGTCP2_WRITE_PKT_FLAG_PADDING_IF_NOT_EMPTY adds padding to the QUIC + packet as much as possible if the packet is not empty. */ +-#define NGTCP2_WRITE_PKT_FLAG_PADDING_IF_NOT_EMPTY 0x08u +- +-/* +- * ngtcp2_max_frame is defined so that it covers the largest ACK +- * frame. +- */ +-typedef union ngtcp2_max_frame { +- ngtcp2_frame fr; +- struct { +- ngtcp2_ack ack; +- /* ack includes 1 ngtcp2_ack_range. */ +- ngtcp2_ack_range ranges[NGTCP2_MAX_ACK_RANGES - 1]; +- } ackfr; +-} ngtcp2_max_frame; ++#define NGTCP2_WRITE_PKT_FLAG_PADDING_IF_NOT_EMPTY 0x08U + + typedef struct ngtcp2_path_challenge_entry { + ngtcp2_path_storage ps; +- uint8_t data[8]; ++ ngtcp2_path_challenge_data data; + } ngtcp2_path_challenge_entry; + + void ngtcp2_path_challenge_entry_init(ngtcp2_path_challenge_entry *pcent, + const ngtcp2_path *path, +- const uint8_t *data); ++ const ngtcp2_path_challenge_data *data); + + /* NGTCP2_CONN_FLAG_NONE indicates that no flag is set. */ +-#define NGTCP2_CONN_FLAG_NONE 0x00u ++#define NGTCP2_CONN_FLAG_NONE 0x00U + /* NGTCP2_CONN_FLAG_TLS_HANDSHAKE_COMPLETED is set when TLS stack + declares that TLS handshake has completed. The condition of this + declaration varies between TLS implementations and this flag does +@@ -145,61 +130,69 @@ void ngtcp2_path_challenge_entry_init(ngtcp2_path_challenge_entry *pcent, + implementations declare TLS handshake completion as server when + they write off Server Finished and before deriving application rx + secret. */ +-#define NGTCP2_CONN_FLAG_TLS_HANDSHAKE_COMPLETED 0x01u ++#define NGTCP2_CONN_FLAG_TLS_HANDSHAKE_COMPLETED 0x01U + /* NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED is set when the first + Initial packet has successfully been processed. */ +-#define NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED 0x02u ++#define NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED 0x02U + /* NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED is set if transport + parameters are received. */ +-#define NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED 0x04u ++#define NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED 0x04U + /* NGTCP2_CONN_FLAG_LOCAL_TRANSPORT_PARAMS_COMMITTED is set when a + local transport parameters are applied. */ +-#define NGTCP2_CONN_FLAG_LOCAL_TRANSPORT_PARAMS_COMMITTED 0x08u ++#define NGTCP2_CONN_FLAG_LOCAL_TRANSPORT_PARAMS_COMMITTED 0x08U + /* NGTCP2_CONN_FLAG_RECV_RETRY is set when a client receives Retry + packet. */ +-#define NGTCP2_CONN_FLAG_RECV_RETRY 0x10u ++#define NGTCP2_CONN_FLAG_RECV_RETRY 0x10U + /* NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED is set when 0-RTT packet is + rejected by a peer. */ +-#define NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED 0x20u ++#define NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED 0x20U + /* NGTCP2_CONN_FLAG_KEEP_ALIVE_CANCELLED is set when the expired + keep-alive timer has been cancelled. */ +-#define NGTCP2_CONN_FLAG_KEEP_ALIVE_CANCELLED 0x40u ++#define NGTCP2_CONN_FLAG_KEEP_ALIVE_CANCELLED 0x40U + /* NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED is set when an endpoint + confirmed completion of handshake. */ +-#define NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED 0x80u ++#define NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED 0x80U + /* NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED is set when the library + transitions its state to "post handshake". */ +-#define NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED 0x0100u ++#define NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED 0x0100U + /* NGTCP2_CONN_FLAG_HANDSHAKE_EARLY_RETRANSMIT is set when the early + handshake retransmission has done when server receives overlapping + Initial crypto data. */ +-#define NGTCP2_CONN_FLAG_HANDSHAKE_EARLY_RETRANSMIT 0x0200u ++#define NGTCP2_CONN_FLAG_HANDSHAKE_EARLY_RETRANSMIT 0x0200U + /* NGTCP2_CONN_FLAG_CLEAR_FIXED_BIT indicates that the local endpoint + sends a QUIC packet without Fixed Bit set if a remote endpoint + supports Greasing QUIC Bit extension. */ +-#define NGTCP2_CONN_FLAG_CLEAR_FIXED_BIT 0x0400u ++#define NGTCP2_CONN_FLAG_CLEAR_FIXED_BIT 0x0400U + /* NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED is set when key update is + not confirmed by the local endpoint. That is, it has not received + ACK frame which acknowledges packet which is encrypted with new + key. */ +-#define NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED 0x0800u ++#define NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED 0x0800U + /* NGTCP2_CONN_FLAG_PPE_PENDING is set when + NGTCP2_WRITE_STREAM_FLAG_MORE is used and the intermediate state of + ngtcp2_ppe is stored in pkt struct of ngtcp2_conn. */ +-#define NGTCP2_CONN_FLAG_PPE_PENDING 0x1000u ++#define NGTCP2_CONN_FLAG_PPE_PENDING 0x1000U + /* NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE is set when idle timer + should be restarted on next write. */ +-#define NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE 0x2000u ++#define NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE 0x2000U + /* NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED indicates that server as peer + verified client address. This flag is only used by client. */ +-#define NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED 0x4000u ++#define NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED 0x4000U + /* NGTCP2_CONN_FLAG_EARLY_KEY_INSTALLED indicates that an early key is + installed. conn->early.ckm cannot be used for this purpose because + it might be discarded when a certain condition is met. */ +-#define NGTCP2_CONN_FLAG_EARLY_KEY_INSTALLED 0x8000u ++#define NGTCP2_CONN_FLAG_EARLY_KEY_INSTALLED 0x8000U + /* NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR is set when the local + endpoint has initiated key update. */ +-#define NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR 0x10000u ++#define NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR 0x10000U ++/* NGTCP2_CONN_FLAG_AGGREGATE_PKTS is set when ++ ngtcp2_conn_writev_stream is called inside the callback invoked by ++ ngtcp2_conn_write_aggregate_pkt. */ ++#define NGTCP2_CONN_FLAG_AGGREGATE_PKTS 0x20000U ++/* NGTCP2_CONN_FLAG_CRUMBLE_INITIAL_CRYPTO, if set, crumbles an ++ Initial CRYPTO frame into pieces as a countermeasure against Deep ++ Packet Inspection. */ ++#define NGTCP2_CONN_FLAG_CRUMBLE_INITIAL_CRYPTO 0x40000U + + typedef struct ngtcp2_pktns { + struct { +@@ -394,14 +387,14 @@ struct ngtcp2_conn { + ngtcp2_tstamp last_max_data_ts; + + struct { +- /* state is the state of ECN validation */ +- ngtcp2_ecn_state state; + /* validation_start_ts is the timestamp when ECN validation is + started. It is UINT64_MAX if it has not started yet. */ + ngtcp2_tstamp validation_start_ts; + /* dgram_sent is the number of UDP datagram sent during ECN + validation period. */ + size_t dgram_sent; ++ /* state is the state of ECN validation */ ++ ngtcp2_ecn_state state; + } ecn; + + struct { +@@ -552,13 +545,13 @@ struct ngtcp2_conn { + /* retry_aead_ctx is AEAD cipher context to verify Retry packet + integrity. It is used by client only. */ + ngtcp2_crypto_aead_ctx retry_aead_ctx; ++ /* decryption_failure_count is the number of received packets that ++ fail authentication. */ ++ uint64_t decryption_failure_count; + /* tls_error is TLS related error. */ + int tls_error; + /* tls_alert is TLS alert generated by the local endpoint. */ + uint8_t tls_alert; +- /* decryption_failure_count is the number of received packets that +- fail authentication. */ +- uint64_t decryption_failure_count; + } crypto; + + /* pkt contains the packet intermediate construction data to support +@@ -568,13 +561,13 @@ struct ngtcp2_conn { + ngtcp2_pkt_hd hd; + ngtcp2_ppe ppe; + ngtcp2_frame_chain **pfrc; ++ ngtcp2_ssize hs_spktlen; + int pkt_empty; + int hd_logged; ++ int require_padding; + /* flags is bitwise OR of zero or more of + NGTCP2_RTB_ENTRY_FLAG_*. */ + uint16_t rtb_entry_flags; +- ngtcp2_ssize hs_spktlen; +- int require_padding; + } pkt; + + struct { +@@ -630,7 +623,6 @@ struct ngtcp2_conn { + ngtcp2_log log; + ngtcp2_qlog qlog; + ngtcp2_rst rst; +- ngtcp2_cc_algo cc_algo; + union { + ngtcp2_cc cc; + ngtcp2_cc_reno reno; +@@ -641,17 +633,23 @@ struct ngtcp2_conn { + successfully. The path is added to this history when a local + endpoint migrates to the another path. */ + ngtcp2_static_ringbuf_path_history path_history; ++ /* glitch_rlim is the rate limit of glitches that can be tolerated. ++ If more than those glitches are detected, a connection is ++ closed. */ ++ ngtcp2_ratelim glitch_rlim; + const ngtcp2_mem *mem; + /* idle_ts is the time instant when idle timer started. */ + ngtcp2_tstamp idle_ts; + /* handshake_confirmed_ts is the time instant when handshake is + confirmed. For server, it is confirmed when completed. */ + ngtcp2_tstamp handshake_confirmed_ts; ++ ngtcp2_pcg32 pcg; + void *user_data; + uint32_t client_chosen_version; + uint32_t negotiated_version; + /* flags is bitwise OR of zero or more of NGTCP2_CONN_FLAG_*. */ + uint32_t flags; ++ ngtcp2_cc_algo cc_algo; + int server; + }; + +@@ -663,9 +661,6 @@ typedef enum ngtcp2_vmsg_type { + typedef struct ngtcp2_vmsg_stream { + /* strm is a stream that data is sent to. */ + ngtcp2_strm *strm; +- /* flags is bitwise OR of zero or more of +- NGTCP2_WRITE_STREAM_FLAG_*. */ +- uint32_t flags; + /* data is the pointer to ngtcp2_vec array which contains the stream + data to send. */ + const ngtcp2_vec *data; +@@ -674,6 +669,9 @@ typedef struct ngtcp2_vmsg_stream { + /* pdatalen is the pointer to the variable which the number of bytes + written is assigned to if pdatalen is not NULL. */ + ngtcp2_ssize *pdatalen; ++ /* flags is bitwise OR of zero or more of ++ NGTCP2_WRITE_STREAM_FLAG_*. */ ++ uint32_t flags; + } ngtcp2_vmsg_stream; + + typedef struct ngtcp2_vmsg_datagram { +@@ -684,12 +682,12 @@ typedef struct ngtcp2_vmsg_datagram { + size_t datacnt; + /* dgram_id is an opaque identifier chosen by an application. */ + uint64_t dgram_id; +- /* flags is bitwise OR of zero or more of +- NGTCP2_WRITE_DATAGRAM_FLAG_*. */ +- uint32_t flags; + /* paccepted is the pointer to the variable which, if it is not + NULL, is assigned nonzero if data is written to a packet. */ + int *paccepted; ++ /* flags is bitwise OR of zero or more of ++ NGTCP2_WRITE_DATAGRAM_FLAG_*. */ ++ uint32_t flags; + } ngtcp2_vmsg_datagram; + + typedef struct ngtcp2_vmsg { +@@ -757,15 +755,9 @@ int ngtcp2_conn_close_stream_if_shut_rdwr(ngtcp2_conn *conn, ngtcp2_strm *strm); + * ack_delay included in ACK frame. |ack_delay| is actually tainted + * (sent by peer), so don't assume that |ack_delay| is always smaller + * than, or equals to |rtt|. +- * +- * This function returns 0 if it succeeds, or one of the following +- * negative error codes: +- * +- * NGTCP2_ERR_INVALID_ARGUMENT +- * RTT sample is ignored. + */ +-int ngtcp2_conn_update_rtt(ngtcp2_conn *conn, ngtcp2_duration rtt, +- ngtcp2_duration ack_delay, ngtcp2_tstamp ts); ++void ngtcp2_conn_update_rtt(ngtcp2_conn *conn, ngtcp2_duration rtt, ++ ngtcp2_duration ack_delay, ngtcp2_tstamp ts); + + void ngtcp2_conn_set_loss_detection_timer(ngtcp2_conn *conn, ngtcp2_tstamp ts); + +@@ -951,7 +943,7 @@ ngtcp2_conn_server_negotiate_version(ngtcp2_conn *conn, + * @function + * + * `ngtcp2_conn_write_connection_close_pkt` writes a packet which +- * contains a CONNECTION_CLOSE frame (type 0x1c) in the buffer pointed ++ * contains a CONNECTION_CLOSE frame (type 0x1C) in the buffer pointed + * by |dest| whose capacity is |datalen|. + * + * If |path| is not ``NULL``, this function stores the network path +@@ -993,7 +985,7 @@ ngtcp2_ssize ngtcp2_conn_write_connection_close_pkt( + * @function + * + * `ngtcp2_conn_write_application_close_pkt` writes a packet which +- * contains a CONNECTION_CLOSE frame (type 0x1d) in the buffer pointed ++ * contains a CONNECTION_CLOSE frame (type 0x1D) in the buffer pointed + * by |dest| whose capacity is |datalen|. + * + * If |path| is not ``NULL``, this function stores the network path +@@ -1006,7 +998,7 @@ ngtcp2_ssize ngtcp2_conn_write_connection_close_pkt( + * if it succeeds. The metadata includes ECN markings. + * + * If handshake has not been confirmed yet, CONNECTION_CLOSE (type +- * 0x1c) with error code :macro:`NGTCP2_APPLICATION_ERROR` is written ++ * 0x1C) with error code :macro:`NGTCP2_APPLICATION_ERROR` is written + * instead. + * + * This function must not be called from inside the callback +diff --git a/third_party/ngtcp2/lib/ngtcp2_conn_info.c b/third_party/ngtcp2/lib/ngtcp2_conn_info.c +new file mode 100644 +index 00000000000..84bb8820f50 +--- /dev/null ++++ b/third_party/ngtcp2/lib/ngtcp2_conn_info.c +@@ -0,0 +1,50 @@ ++/* ++ * ngtcp2 ++ * ++ * Copyright (c) 2025 ngtcp2 contributors ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ */ ++#include "ngtcp2_conn_info.h" ++#include "ngtcp2_conn_stat.h" ++ ++void ngtcp2_conn_info_init_versioned(int conn_info_version, ++ ngtcp2_conn_info *cinfo, ++ const ngtcp2_conn_stat *cstat) { ++ cinfo->latest_rtt = cstat->latest_rtt; ++ cinfo->min_rtt = cstat->min_rtt; ++ cinfo->smoothed_rtt = cstat->smoothed_rtt; ++ cinfo->rttvar = cstat->rttvar; ++ cinfo->cwnd = cstat->cwnd; ++ cinfo->ssthresh = cstat->ssthresh; ++ cinfo->bytes_in_flight = cstat->bytes_in_flight; ++ ++ switch (conn_info_version) { ++ case NGTCP2_CONN_INFO_V2: ++ cinfo->pkt_sent = cstat->pkt_sent; ++ cinfo->bytes_sent = cstat->bytes_sent; ++ cinfo->pkt_recv = cstat->pkt_recv; ++ cinfo->bytes_recv = cstat->bytes_recv; ++ cinfo->pkt_lost = cstat->pkt_lost; ++ cinfo->bytes_lost = cstat->bytes_lost; ++ cinfo->ping_recv = cstat->ping_recv; ++ cinfo->pkt_discarded = cstat->pkt_discarded; ++ } ++} +diff --git a/third_party/ngtcp2/lib/ngtcp2_conn_info.h b/third_party/ngtcp2/lib/ngtcp2_conn_info.h +new file mode 100644 +index 00000000000..161309df304 +--- /dev/null ++++ b/third_party/ngtcp2/lib/ngtcp2_conn_info.h +@@ -0,0 +1,45 @@ ++/* ++ * ngtcp2 ++ * ++ * Copyright (c) 2025 ngtcp2 contributors ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ */ ++#ifndef NGTCP2_CONN_INFO_H ++#define NGTCP2_CONN_INFO_H ++ ++#ifdef HAVE_CONFIG_H ++# include ++#endif /* defined(HAVE_CONFIG_H) */ ++ ++#include ++ ++typedef struct ngtcp2_conn_stat ngtcp2_conn_stat; ++ ++/* ++ * ngtcp2_conn_info_init_versioned initializes |cinfo| of version ++ * |conn_info_version| from |cstat|. This function only fills the ++ * fields of |cinfo| that are available in the specified version. ++ */ ++void ngtcp2_conn_info_init_versioned(int conn_info_version, ++ ngtcp2_conn_info *cinfo, ++ const ngtcp2_conn_stat *cstat); ++ ++#endif /* !defined(NGTCP2_CONN_INFO_H) */ +diff --git a/third_party/ngtcp2/lib/ngtcp2_conn_stat.h b/third_party/ngtcp2/lib/ngtcp2_conn_stat.h +index be041b90860..b5fea473910 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_conn_stat.h ++++ b/third_party/ngtcp2/lib/ngtcp2_conn_stat.h +@@ -31,6 +31,8 @@ + + #include + ++#include "ngtcp2_pktns_id.h" ++ + /** + * @struct + * +@@ -128,6 +130,43 @@ typedef struct ngtcp2_conn_stat { + * scheduled and transmitted together. + */ + size_t send_quantum; ++ /* ++ * pkt_sent is the number of QUIC packets sent. ++ */ ++ uint64_t pkt_sent; ++ /* ++ * bytes_sent is the number of bytes (the sum of QUIC packet length) ++ * sent. ++ */ ++ uint64_t bytes_sent; ++ /* ++ * pkt_recv is the number of QUIC packets received, excluding ++ * discarded ones. ++ */ ++ uint64_t pkt_recv; ++ /* ++ * bytes_recv is the number of bytes (the sum of QUIC packet length) ++ * received, excluding discarded ones. ++ */ ++ uint64_t bytes_recv; ++ /* ++ * pkt_lost is the number of QUIC packets that are considered lost, ++ * excluding PMTUD packets. ++ */ ++ uint64_t pkt_lost; ++ /* ++ * bytes_lost is the number of bytes (the sum of QUIC packet length) ++ * lost, excluding PMTUD packets. ++ */ ++ uint64_t bytes_lost; ++ /* ++ * ping_recv is the number of PING frames received. ++ */ ++ uint64_t ping_recv; ++ /* ++ * pkt_discarded is the number of QUIC packets discarded. ++ */ ++ uint64_t pkt_discarded; + } ngtcp2_conn_stat; + + #endif /* !defined(NGTCP2_CONN_STAT_H) */ +diff --git a/third_party/ngtcp2/lib/ngtcp2_conv.c b/third_party/ngtcp2/lib/ngtcp2_conv.c +index 6528011cc0e..a0cef0c8643 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_conv.c ++++ b/third_party/ngtcp2/lib/ngtcp2_conv.c +@@ -63,33 +63,33 @@ const uint8_t *ngtcp2_get_uint16(uint16_t *dest, const uint8_t *p) { + } + + static const uint8_t *get_uvarint(uint64_t *dest, const uint8_t *p) { +- union { +- uint8_t n8; +- uint16_t n16; +- uint32_t n32; +- uint64_t n64; +- } n; ++ uint16_t n16; ++ uint32_t n32; ++ uint64_t n64; + + switch (*p >> 6) { + case 0: + *dest = *p++; + return p; + case 1: +- memcpy(&n, p, 2); +- n.n8 &= 0x3f; +- *dest = ngtcp2_ntohs(n.n16); ++ memcpy(&n16, p, 2); ++ n16 = ngtcp2_ntohs(n16); ++ n16 &= 0x3FFF; ++ *dest = n16; + + return p + 2; + case 2: +- memcpy(&n, p, 4); +- n.n8 &= 0x3f; +- *dest = ngtcp2_ntohl(n.n32); ++ memcpy(&n32, p, 4); ++ n32 = ngtcp2_ntohl(n32); ++ n32 &= 0x3FFFFFFF; ++ *dest = n32; + + return p + 4; + case 3: +- memcpy(&n, p, 8); +- n.n8 &= 0x3f; +- *dest = ngtcp2_ntohl64(n.n64); ++ memcpy(&n64, p, 8); ++ n64 = ngtcp2_ntohl64(n64); ++ n64 &= 0x3FFFFFFFFFFFFFFF; ++ *dest = n64; + + return p + 8; + default: +@@ -168,7 +168,7 @@ uint8_t *ngtcp2_put_uvarint(uint8_t *p, uint64_t n) { + } + assert(n < 4611686018427387904ULL); + rv = ngtcp2_put_uint64be(p, n); +- *p |= 0xc0; ++ *p |= 0xC0; + return rv; + } + +@@ -200,7 +200,7 @@ uint8_t *ngtcp2_put_pkt_num(uint8_t *p, int64_t pkt_num, size_t len) { + } + + size_t ngtcp2_get_uvarintlen(const uint8_t *p) { +- return (size_t)(1u << (*p >> 6)); ++ return (size_t)(1U << (*p >> 6)); + } + + size_t ngtcp2_put_uvarintlen(uint64_t n) { +diff --git a/third_party/ngtcp2/lib/ngtcp2_crypto.c b/third_party/ngtcp2/lib/ngtcp2_crypto.c +index 1f74e8c5839..078568fde3b 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_crypto.c ++++ b/third_party/ngtcp2/lib/ngtcp2_crypto.c +@@ -65,15 +65,21 @@ int ngtcp2_crypto_km_nocopy_new(ngtcp2_crypto_km **pckm, size_t secretlen, + } + + p = (uint8_t *)(*pckm) + sizeof(ngtcp2_crypto_km); +- (*pckm)->secret.base = p; +- (*pckm)->secret.len = secretlen; +- p += secretlen; +- (*pckm)->iv.base = p; +- (*pckm)->iv.len = ivlen; +- (*pckm)->aead_ctx.native_handle = NULL; +- (*pckm)->pkt_num = -1; +- (*pckm)->use_count = 0; +- (*pckm)->flags = NGTCP2_CRYPTO_KM_FLAG_NONE; ++ ++ **pckm = (ngtcp2_crypto_km){ ++ .secret = ++ { ++ .base = p, ++ .len = secretlen, ++ }, ++ .iv = ++ { ++ .base = p + secretlen, ++ .len = ivlen, ++ }, ++ .pkt_num = -1, ++ .flags = NGTCP2_CRYPTO_KM_FLAG_NONE, ++ }; + + return 0; + } +diff --git a/third_party/ngtcp2/lib/ngtcp2_crypto.h b/third_party/ngtcp2/lib/ngtcp2_crypto.h +index ca6d494846f..c1e786b5fc7 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_crypto.h ++++ b/third_party/ngtcp2/lib/ngtcp2_crypto.h +@@ -42,10 +42,10 @@ + #define NGTCP2_MAX_AEAD_OVERHEAD 16 + + /* NGTCP2_CRYPTO_KM_FLAG_NONE indicates that no flag is set. */ +-#define NGTCP2_CRYPTO_KM_FLAG_NONE 0x00u ++#define NGTCP2_CRYPTO_KM_FLAG_NONE 0x00U + /* NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE is set if key phase bit is + set. */ +-#define NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE 0x01u ++#define NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE 0x01U + + typedef struct ngtcp2_crypto_km { + ngtcp2_vec secret; +diff --git a/third_party/ngtcp2/lib/ngtcp2_dcidtr.c b/third_party/ngtcp2/lib/ngtcp2_dcidtr.c +index 170b9f803cc..a312398bbfa 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_dcidtr.c ++++ b/third_party/ngtcp2/lib/ngtcp2_dcidtr.c +@@ -157,9 +157,9 @@ int ngtcp2_dcidtr_bind_dcid(ngtcp2_dcidtr *dtr, ngtcp2_dcid **pdest, + return 0; + } + +-int ngtcp2_dcidtr_verify_stateless_reset(const ngtcp2_dcidtr *dtr, +- const ngtcp2_path *path, +- const uint8_t *token) { ++int ngtcp2_dcidtr_verify_stateless_reset( ++ const ngtcp2_dcidtr *dtr, const ngtcp2_path *path, ++ const ngtcp2_stateless_reset_token *token) { + const ngtcp2_dcid *dcid; + const ngtcp2_ringbuf *rb = &dtr->bound.rb; + size_t i, len = ngtcp2_ringbuf_len(rb); +@@ -176,7 +176,7 @@ int ngtcp2_dcidtr_verify_stateless_reset(const ngtcp2_dcidtr *dtr, + + static int verify_token_uniqueness(const ngtcp2_ringbuf *rb, int *pfound, + uint64_t seq, const ngtcp2_cid *cid, +- const uint8_t *token) { ++ const ngtcp2_stateless_reset_token *token) { + const ngtcp2_dcid *dcid; + size_t i, len = ngtcp2_ringbuf_len(rb); + int rv; +@@ -196,9 +196,9 @@ static int verify_token_uniqueness(const ngtcp2_ringbuf *rb, int *pfound, + return 0; + } + +-int ngtcp2_dcidtr_verify_token_uniqueness(const ngtcp2_dcidtr *dtr, int *pfound, +- uint64_t seq, const ngtcp2_cid *cid, +- const uint8_t *token) { ++int ngtcp2_dcidtr_verify_token_uniqueness( ++ const ngtcp2_dcidtr *dtr, int *pfound, uint64_t seq, const ngtcp2_cid *cid, ++ const ngtcp2_stateless_reset_token *token) { + int rv; + + rv = verify_token_uniqueness(&dtr->bound.rb, pfound, seq, cid, token); +@@ -406,7 +406,8 @@ ngtcp2_tstamp ngtcp2_dcidtr_earliest_retired_ts(const ngtcp2_dcidtr *dtr) { + } + + void ngtcp2_dcidtr_push_unused(ngtcp2_dcidtr *dtr, uint64_t seq, +- const ngtcp2_cid *cid, const uint8_t *token) { ++ const ngtcp2_cid *cid, ++ const ngtcp2_stateless_reset_token *token) { + ngtcp2_dcid *dcid = ngtcp2_ringbuf_push_back(&dtr->unused.rb); + + ngtcp2_dcid_init(dcid, seq, cid, token); +diff --git a/third_party/ngtcp2/lib/ngtcp2_dcidtr.h b/third_party/ngtcp2/lib/ngtcp2_dcidtr.h +index 63043427bc0..945ed12bae1 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_dcidtr.h ++++ b/third_party/ngtcp2/lib/ngtcp2_dcidtr.h +@@ -154,9 +154,9 @@ int ngtcp2_dcidtr_bind_dcid(ngtcp2_dcidtr *dtr, ngtcp2_dcid **pdest, + * There is no Destination Connection ID that matches the given + * |path| and |token|. + */ +-int ngtcp2_dcidtr_verify_stateless_reset(const ngtcp2_dcidtr *dtr, +- const ngtcp2_path *path, +- const uint8_t *token); ++int ngtcp2_dcidtr_verify_stateless_reset( ++ const ngtcp2_dcidtr *dtr, const ngtcp2_path *path, ++ const ngtcp2_stateless_reset_token *token); + + /* + * ngtcp2_dcidtr_verify_token_uniqueness verifies that the uniqueness +@@ -180,9 +180,9 @@ int ngtcp2_dcidtr_verify_stateless_reset(const ngtcp2_dcidtr *dtr, + * The given combination of values does not satisfy the above + * conditions. + */ +-int ngtcp2_dcidtr_verify_token_uniqueness(const ngtcp2_dcidtr *dtr, int *pfound, +- uint64_t seq, const ngtcp2_cid *cid, +- const uint8_t *token); ++int ngtcp2_dcidtr_verify_token_uniqueness( ++ const ngtcp2_dcidtr *dtr, int *pfound, uint64_t seq, const ngtcp2_cid *cid, ++ const ngtcp2_stateless_reset_token *token); + + /* + * ngtcp2_dcidtr_retire_inactive_dcid_prior_to retires inactive +@@ -273,7 +273,8 @@ ngtcp2_tstamp ngtcp2_dcidtr_earliest_retired_ts(const ngtcp2_dcidtr *dtr); + * buffer space is full, the earliest ngtcp2_dcid is removed. + */ + void ngtcp2_dcidtr_push_unused(ngtcp2_dcidtr *dtr, uint64_t seq, +- const ngtcp2_cid *cid, const uint8_t *token); ++ const ngtcp2_cid *cid, ++ const ngtcp2_stateless_reset_token *token); + + /* + * ngtcp2_dcidtr_pop_unused_cid_token removes an unused Destination +diff --git a/third_party/ngtcp2/lib/ngtcp2_frame_chain.c b/third_party/ngtcp2/lib/ngtcp2_frame_chain.c +index 0f6b06a788d..ec0839b7a4d 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_frame_chain.c ++++ b/third_party/ngtcp2/lib/ngtcp2_frame_chain.c +@@ -36,7 +36,7 @@ int ngtcp2_frame_chain_objalloc_new(ngtcp2_frame_chain **pfrc, + return NGTCP2_ERR_NOMEM; + } + +- ngtcp2_frame_chain_init(*pfrc); ++ ngtcp2_frame_chain_init(*pfrc, NGTCP2_FRAME_CHAIN_FLAG_NONE); + + return 0; + } +@@ -48,7 +48,7 @@ int ngtcp2_frame_chain_extralen_new(ngtcp2_frame_chain **pfrc, size_t extralen, + return NGTCP2_ERR_NOMEM; + } + +- ngtcp2_frame_chain_init(*pfrc); ++ ngtcp2_frame_chain_init(*pfrc, NGTCP2_FRAME_CHAIN_FLAG_MALLOC); + + return 0; + } +@@ -57,14 +57,26 @@ int ngtcp2_frame_chain_stream_datacnt_objalloc_new(ngtcp2_frame_chain **pfrc, + size_t datacnt, + ngtcp2_objalloc *objalloc, + const ngtcp2_mem *mem) { ++ int rv; ++ + if (datacnt > NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES) { +- return ngtcp2_frame_chain_extralen_new(pfrc, +- sizeof(ngtcp2_vec) * (datacnt - 1) - +- NGTCP2_FRAME_CHAIN_STREAM_AVAIL, +- mem); ++ rv = ngtcp2_frame_chain_extralen_new( ++ pfrc, ++ sizeof(ngtcp2_vec) * (datacnt - NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES), ++ mem); ++ } else { ++ rv = ngtcp2_frame_chain_objalloc_new(pfrc, objalloc); ++ } ++ ++ if (rv != 0) { ++ return rv; + } + +- return ngtcp2_frame_chain_objalloc_new(pfrc, objalloc); ++ (*pfrc)->fr.stream.data = ++ (ngtcp2_vec *)(void *)((uint8_t *)*pfrc + ++ offsetof(ngtcp2_frame_chain, buf)); ++ ++ return 0; + } + + int ngtcp2_frame_chain_new_token_objalloc_new(ngtcp2_frame_chain **pfrc, +@@ -87,9 +99,9 @@ int ngtcp2_frame_chain_new_token_objalloc_new(ngtcp2_frame_chain **pfrc, + } + + fr = &(*pfrc)->fr; +- fr->type = NGTCP2_FRAME_NEW_TOKEN; ++ fr->new_token.type = NGTCP2_FRAME_NEW_TOKEN; + +- p = (uint8_t *)fr + sizeof(ngtcp2_new_token); ++ p = (uint8_t *)*pfrc + offsetof(ngtcp2_frame_chain, buf); + memcpy(p, token, tokenlen); + + fr->new_token.token = p; +@@ -98,21 +110,6 @@ int ngtcp2_frame_chain_new_token_objalloc_new(ngtcp2_frame_chain **pfrc, + return 0; + } + +-void ngtcp2_frame_chain_del(ngtcp2_frame_chain *frc, const ngtcp2_mem *mem) { +- ngtcp2_frame_chain_binder *binder; +- +- if (frc == NULL) { +- return; +- } +- +- binder = frc->binder; +- if (binder && --binder->refcount == 0) { +- ngtcp2_mem_free(mem, binder); +- } +- +- ngtcp2_mem_free(mem, frc); +-} +- + void ngtcp2_frame_chain_objalloc_del(ngtcp2_frame_chain *frc, + ngtcp2_objalloc *objalloc, + const ngtcp2_mem *mem) { +@@ -122,39 +119,23 @@ void ngtcp2_frame_chain_objalloc_del(ngtcp2_frame_chain *frc, + return; + } + +- switch (frc->fr.type) { +- case NGTCP2_FRAME_CRYPTO: +- case NGTCP2_FRAME_STREAM: +- if (frc->fr.stream.datacnt > NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES) { +- ngtcp2_frame_chain_del(frc, mem); +- +- return; +- } +- +- break; +- case NGTCP2_FRAME_NEW_TOKEN: +- if (frc->fr.new_token.tokenlen > NGTCP2_FRAME_CHAIN_NEW_TOKEN_THRES) { +- ngtcp2_frame_chain_del(frc, mem); +- +- return; +- } +- +- break; +- } +- + binder = frc->binder; + if (binder && --binder->refcount == 0) { + ngtcp2_mem_free(mem, binder); + } + +- frc->binder = NULL; ++ if (frc->flags & NGTCP2_FRAME_CHAIN_FLAG_MALLOC) { ++ ngtcp2_mem_free(mem, frc); ++ return; ++ } + + ngtcp2_objalloc_frame_chain_release(objalloc, frc); + } + +-void ngtcp2_frame_chain_init(ngtcp2_frame_chain *frc) { ++void ngtcp2_frame_chain_init(ngtcp2_frame_chain *frc, uint32_t flags) { + frc->next = NULL; + frc->binder = NULL; ++ frc->flags = flags; + } + + void ngtcp2_frame_chain_list_objalloc_del(ngtcp2_frame_chain *frc, +diff --git a/third_party/ngtcp2/lib/ngtcp2_frame_chain.h b/third_party/ngtcp2/lib/ngtcp2_frame_chain.h +index e7b33632529..c73100dbcba 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_frame_chain.h ++++ b/third_party/ngtcp2/lib/ngtcp2_frame_chain.h +@@ -36,10 +36,10 @@ + + /* NGTCP2_FRAME_CHAIN_BINDER_FLAG_NONE indicates that no flag is + set. */ +-#define NGTCP2_FRAME_CHAIN_BINDER_FLAG_NONE 0x00u ++#define NGTCP2_FRAME_CHAIN_BINDER_FLAG_NONE 0x00U + /* NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK indicates that an information + which a frame carries has been acknowledged. */ +-#define NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK 0x01u ++#define NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK 0x01U + + /* + * ngtcp2_frame_chain_binder binds 2 or more of ngtcp2_frame_chain to +@@ -57,6 +57,25 @@ typedef struct ngtcp2_frame_chain_binder { + int ngtcp2_frame_chain_binder_new(ngtcp2_frame_chain_binder **pbinder, + const ngtcp2_mem *mem); + ++/* NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES is the number of datacnt ++ that changes allocation method. If datacnt is more than this ++ value, ngtcp2_frame_chain is allocated without ngtcp2_objalloc. ++ Otherwise, it is allocated using ngtcp2_objalloc. */ ++#define NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES 4 ++ ++/* NGTCP2_FRAME_CHAIN_NEW_TOKEN_THRES is the length of a token that ++ changes allocation method. If the length is more than this value, ++ ngtcp2_frame_chain is allocated without ngtcp2_objalloc. ++ Otherwise, it is allocated using ngtcp2_objalloc. */ ++#define NGTCP2_FRAME_CHAIN_NEW_TOKEN_THRES \ ++ (NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES * sizeof(ngtcp2_vec)) ++ ++/* NGTCP2_FRAME_CHAIN_FLAG_NONE indicates no flag is set. */ ++#define NGTCP2_FRAME_CHAIN_FLAG_NONE 0x0 ++/* NGTCP2_FRAME_CHAIN_FLAG_MALLOC indicates that ngtcp2_frame_chain is ++ allocated by ngtcp2_mem_malloc. */ ++#define NGTCP2_FRAME_CHAIN_FLAG_MALLOC 0x1 ++ + typedef struct ngtcp2_frame_chain ngtcp2_frame_chain; + + /* +@@ -67,7 +86,9 @@ struct ngtcp2_frame_chain { + struct { + ngtcp2_frame_chain *next; + ngtcp2_frame_chain_binder *binder; ++ uint32_t flags; + ngtcp2_frame fr; ++ uint8_t buf[sizeof(ngtcp2_vec) * NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES]; + }; + + ngtcp2_opl_entry oplent; +@@ -90,10 +111,6 @@ ngtcp2_objalloc_decl(frame_chain, ngtcp2_frame_chain, oplent) + int ngtcp2_bind_frame_chains(ngtcp2_frame_chain *a, ngtcp2_frame_chain *b, + const ngtcp2_mem *mem); + +-/* NGTCP2_MAX_STREAM_DATACNT is the maximum number of ngtcp2_vec that +- a ngtcp2_stream can include. */ +-#define NGTCP2_MAX_STREAM_DATACNT 256 +- + /* + * ngtcp2_frame_chain_objalloc_new allocates ngtcp2_frame_chain using + * |objalloc|. +@@ -108,26 +125,6 @@ int ngtcp2_frame_chain_objalloc_new(ngtcp2_frame_chain **pfrc, + int ngtcp2_frame_chain_extralen_new(ngtcp2_frame_chain **pfrc, size_t extralen, + const ngtcp2_mem *mem); + +-/* NGTCP2_FRAME_CHAIN_STREAM_AVAIL is the number of additional bytes +- available after ngtcp2_stream when it is embedded in +- ngtcp2_frame. */ +-#define NGTCP2_FRAME_CHAIN_STREAM_AVAIL \ +- (sizeof(ngtcp2_frame) - sizeof(ngtcp2_stream)) +- +-/* NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES is the number of datacnt +- that changes allocation method. If datacnt is more than this +- value, ngtcp2_frame_chain is allocated without ngtcp2_objalloc. +- Otherwise, it is allocated using ngtcp2_objalloc. */ +-#define NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES \ +- (NGTCP2_FRAME_CHAIN_STREAM_AVAIL / sizeof(ngtcp2_vec) + 1) +- +-/* NGTCP2_FRAME_CHAIN_NEW_TOKEN_THRES is the length of a token that +- changes allocation method. If the length is more than this value, +- ngtcp2_frame_chain is allocated without ngtcp2_objalloc. +- Otherwise, it is allocated using ngtcp2_objalloc. */ +-#define NGTCP2_FRAME_CHAIN_NEW_TOKEN_THRES \ +- (sizeof(ngtcp2_frame) - sizeof(ngtcp2_new_token)) +- + /* + * ngtcp2_frame_chain_stream_datacnt_objalloc_new allocates enough + * data to store additional |datacnt| - 1 ngtcp2_vec object after +@@ -135,8 +132,6 @@ int ngtcp2_frame_chain_extralen_new(ngtcp2_frame_chain **pfrc, size_t extralen, + * words, |datacnt| <= NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES, + * ngtcp2_frame_chain_objalloc_new is called internally. Otherwise, + * ngtcp2_frame_chain_extralen_new is used and objalloc is not used. +- * Therefore, it is important to call ngtcp2_frame_chain_objalloc_del +- * without changing datacnt field. + */ + int ngtcp2_frame_chain_stream_datacnt_objalloc_new(ngtcp2_frame_chain **pfrc, + size_t datacnt, +@@ -157,24 +152,19 @@ int ngtcp2_frame_chain_new_token_objalloc_new(ngtcp2_frame_chain **pfrc, + const ngtcp2_mem *mem); + + /* +- * ngtcp2_frame_chain_del deallocates |frc|. It also deallocates the +- * memory pointed by |frc|. +- */ +-void ngtcp2_frame_chain_del(ngtcp2_frame_chain *frc, const ngtcp2_mem *mem); +- +-/* +- * ngtcp2_frame_chain_objalloc_del adds |frc| to |objalloc| for reuse. +- * It might just delete |frc| depending on the frame type and the size +- * of |frc|. ++ * ngtcp2_frame_chain_objalloc_del adds |frc| to |objalloc| for reuse ++ * if NGTCP2_FRAME_CHAIN_FLAG_MALLOC is not set in |frc|->flags. ++ * Otherwise, it deletes |frc|. + */ + void ngtcp2_frame_chain_objalloc_del(ngtcp2_frame_chain *frc, + ngtcp2_objalloc *objalloc, + const ngtcp2_mem *mem); + + /* +- * ngtcp2_frame_chain_init initializes |frc|. ++ * ngtcp2_frame_chain_init initializes |frc|. |flags| is bitwise-OR ++ * of zero or more of NGTCP2_FRAME_CHAIN_FLAG_*. + */ +-void ngtcp2_frame_chain_init(ngtcp2_frame_chain *frc); ++void ngtcp2_frame_chain_init(ngtcp2_frame_chain *frc, uint32_t flags); + + /* + * ngtcp2_frame_chain_list_objalloc_del adds all ngtcp2_frame_chain +diff --git a/third_party/ngtcp2/lib/ngtcp2_gaptr.c b/third_party/ngtcp2/lib/ngtcp2_gaptr.c +index d04b9634c20..267bd07225d 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_gaptr.c ++++ b/third_party/ngtcp2/lib/ngtcp2_gaptr.c +@@ -35,11 +35,11 @@ void ngtcp2_gaptr_init(ngtcp2_gaptr *gaptr, const ngtcp2_mem *mem) { + } + + static int gaptr_gap_init(ngtcp2_gaptr *gaptr) { +- ngtcp2_range range = { +- .end = UINT64_MAX, +- }; +- +- return ngtcp2_ksl_insert(&gaptr->gap, NULL, &range, NULL); ++ return ngtcp2_ksl_insert(&gaptr->gap, NULL, ++ &(ngtcp2_range){ ++ .end = UINT64_MAX, ++ }, ++ NULL); + } + + void ngtcp2_gaptr_free(ngtcp2_gaptr *gaptr) { +@@ -116,10 +116,6 @@ uint64_t ngtcp2_gaptr_first_gap_offset(const ngtcp2_gaptr *gaptr) { + + ngtcp2_range ngtcp2_gaptr_get_first_gap_after(const ngtcp2_gaptr *gaptr, + uint64_t offset) { +- ngtcp2_range q = { +- .begin = offset, +- .end = offset + 1, +- }; + ngtcp2_ksl_it it; + + if (ngtcp2_ksl_len(&gaptr->gap) == 0) { +@@ -129,7 +125,11 @@ ngtcp2_range ngtcp2_gaptr_get_first_gap_after(const ngtcp2_gaptr *gaptr, + return r; + } + +- it = ngtcp2_ksl_lower_bound_search(&gaptr->gap, &q, ++ it = ngtcp2_ksl_lower_bound_search(&gaptr->gap, ++ &(ngtcp2_range){ ++ .begin = offset, ++ .end = offset + 1, ++ }, + ngtcp2_ksl_range_exclusive_search); + + assert(!ngtcp2_ksl_it_end(&it)); +diff --git a/third_party/ngtcp2/lib/ngtcp2_ksl.c b/third_party/ngtcp2/lib/ngtcp2_ksl.c +index 22c131a1ac6..80000f42f9b 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_ksl.c ++++ b/third_party/ngtcp2/lib/ngtcp2_ksl.c +@@ -31,71 +31,78 @@ + + #include "ngtcp2_macro.h" + #include "ngtcp2_mem.h" +-#include "ngtcp2_range.h" + + static ngtcp2_ksl_blk null_blk; + + ngtcp2_objalloc_def(ksl_blk, ngtcp2_ksl_blk, oplent) + +-static size_t ksl_nodelen(size_t keylen) { +- assert(keylen >= sizeof(uint64_t)); +- +- return (sizeof(ngtcp2_ksl_node) + keylen - sizeof(uint64_t) + 0x7u) & +- ~(uintptr_t)0x7u; +-} +- +-static size_t ksl_blklen(size_t nodelen) { +- return sizeof(ngtcp2_ksl_blk) + nodelen * NGTCP2_KSL_MAX_NBLK - +- sizeof(uint64_t); ++static size_t ksl_blklen(size_t aligned_keylen) { ++ return sizeof(ngtcp2_ksl_blk) + NGTCP2_KSL_MAX_NBLK * aligned_keylen; + } + + /* +- * ksl_node_set_key sets |key| to |node|. ++ * ksl_set_nth_key sets |key| to |n|th node under |blk|. + */ +-static void ksl_node_set_key(ngtcp2_ksl *ksl, ngtcp2_ksl_node *node, +- const void *key) { +- memcpy(node->key, key, ksl->keylen); ++static void ksl_set_nth_key(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, ++ size_t n, const ngtcp2_ksl_key *key) { ++ memcpy(blk->keys + n * ksl->aligned_keylen, key, ksl->keylen); + } + + void ngtcp2_ksl_init(ngtcp2_ksl *ksl, ngtcp2_ksl_compar compar, + ngtcp2_ksl_search search, size_t keylen, + const ngtcp2_mem *mem) { +- size_t nodelen = ksl_nodelen(keylen); ++ size_t aligned_keylen; ++ ++ assert(keylen >= sizeof(uint64_t)); ++ ++ aligned_keylen = (keylen + 0x7U) & ~0x7U; ++ ++ assert(aligned_keylen <= UINT16_MAX); + + ngtcp2_objalloc_init(&ksl->blkalloc, +- (ksl_blklen(nodelen) + 0xfu) & ~(uintptr_t)0xfu, mem); ++ (ksl_blklen(aligned_keylen) + 0xFU) & ~(uintptr_t)0xFU, ++ mem); + +- ksl->head = NULL; ++ ksl->root = NULL; + ksl->front = ksl->back = NULL; + ksl->compar = compar; + ksl->search = search; + ksl->n = 0; + ksl->keylen = keylen; +- ksl->nodelen = nodelen; ++ ksl->aligned_keylen = aligned_keylen; + } + + static ngtcp2_ksl_blk *ksl_blk_objalloc_new(ngtcp2_ksl *ksl) { +- return ngtcp2_objalloc_ksl_blk_len_get(&ksl->blkalloc, +- ksl_blklen(ksl->nodelen)); ++ ngtcp2_ksl_blk *blk = ngtcp2_objalloc_ksl_blk_len_get( ++ &ksl->blkalloc, ksl_blklen(ksl->aligned_keylen)); ++ ++ if (!blk) { ++ return NULL; ++ } ++ ++ blk->keys = (uint8_t *)blk + sizeof(*blk); ++ blk->aligned_keylen = (uint16_t)ksl->aligned_keylen; ++ ++ return blk; + } + + static void ksl_blk_objalloc_del(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk) { + ngtcp2_objalloc_ksl_blk_release(&ksl->blkalloc, blk); + } + +-static int ksl_head_init(ngtcp2_ksl *ksl) { +- ngtcp2_ksl_blk *head = ksl_blk_objalloc_new(ksl); ++static int ksl_root_init(ngtcp2_ksl *ksl) { ++ ngtcp2_ksl_blk *root = ksl_blk_objalloc_new(ksl); + +- if (!head) { ++ if (!root) { + return NGTCP2_ERR_NOMEM; + } + +- head->next = head->prev = NULL; +- head->n = 0; +- head->leaf = 1; ++ root->next = root->prev = NULL; ++ root->n = 0; ++ root->leaf = 1; + +- ksl->head = head; +- ksl->front = ksl->back = head; ++ ksl->root = root; ++ ksl->front = ksl->back = root; + + return 0; + } +@@ -109,7 +116,7 @@ static void ksl_free_blk(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk) { + + if (!blk->leaf) { + for (i = 0; i < blk->n; ++i) { +- ksl_free_blk(ksl, ngtcp2_ksl_nth_node(ksl, blk, i)->blk); ++ ksl_free_blk(ksl, blk->nodes[i].blk); + } + } + +@@ -118,12 +125,12 @@ static void ksl_free_blk(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk) { + #endif /* defined(NOMEMPOOL) */ + + void ngtcp2_ksl_free(ngtcp2_ksl *ksl) { +- if (!ksl || !ksl->head) { ++ if (!ksl || !ksl->root) { + return; + } + + #ifdef NOMEMPOOL +- ksl_free_blk(ksl, ksl->head); ++ ksl_free_blk(ksl, ksl->root); + #endif /* defined(NOMEMPOOL) */ + + ngtcp2_objalloc_free(&ksl->blkalloc); +@@ -160,8 +167,10 @@ static ngtcp2_ksl_blk *ksl_split_blk(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk) { + rblk->n = blk->n / 2; + blk->n -= rblk->n; + +- memcpy(rblk->nodes, blk->nodes + ksl->nodelen * blk->n, +- ksl->nodelen * rblk->n); ++ memcpy(rblk->nodes, blk->nodes + blk->n, rblk->n * sizeof(ngtcp2_ksl_node)); ++ ++ memcpy(rblk->keys, blk->keys + blk->n * ksl->aligned_keylen, ++ rblk->n * ksl->aligned_keylen); + + assert(blk->n >= NGTCP2_KSL_MIN_NBLK); + assert(rblk->n >= NGTCP2_KSL_MIN_NBLK); +@@ -181,32 +190,31 @@ static ngtcp2_ksl_blk *ksl_split_blk(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk) { + * Out of memory. + */ + static int ksl_split_node(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i) { +- ngtcp2_ksl_node *node; +- ngtcp2_ksl_blk *lblk = ngtcp2_ksl_nth_node(ksl, blk, i)->blk, *rblk; ++ ngtcp2_ksl_blk *lblk = blk->nodes[i].blk, *rblk; + + rblk = ksl_split_blk(ksl, lblk); + if (rblk == NULL) { + return NGTCP2_ERR_NOMEM; + } + +- memmove(blk->nodes + (i + 2) * ksl->nodelen, +- blk->nodes + (i + 1) * ksl->nodelen, +- ksl->nodelen * (blk->n - (i + 1))); ++ memmove(blk->nodes + (i + 2), blk->nodes + (i + 1), ++ (blk->n - (i + 1)) * sizeof(ngtcp2_ksl_node)); ++ ++ memmove(blk->keys + (i + 1) * ksl->aligned_keylen, ++ blk->keys + i * ksl->aligned_keylen, ++ (blk->n - i) * ksl->aligned_keylen); + +- node = ngtcp2_ksl_nth_node(ksl, blk, i + 1); +- node->blk = rblk; ++ blk->nodes[i + 1].blk = rblk; + ++blk->n; +- ksl_node_set_key(ksl, node, ngtcp2_ksl_nth_node(ksl, rblk, rblk->n - 1)->key); + +- node = ngtcp2_ksl_nth_node(ksl, blk, i); +- ksl_node_set_key(ksl, node, ngtcp2_ksl_nth_node(ksl, lblk, lblk->n - 1)->key); ++ ksl_set_nth_key(ksl, blk, i, ngtcp2_ksl_blk_nth_key(lblk, lblk->n - 1)); + + return 0; + } + + /* +- * ksl_split_head splits a head (root) block. It increases the height +- * of skip list by 1. ++ * ksl_split_root splits a root block. It increases the height of ++ * skip list by 1. + * + * It returns 0 if it succeeds, or one of the following negative error + * codes: +@@ -214,37 +222,34 @@ static int ksl_split_node(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i) { + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +-static int ksl_split_head(ngtcp2_ksl *ksl) { +- ngtcp2_ksl_blk *rblk = NULL, *lblk, *nhead = NULL; +- ngtcp2_ksl_node *node; ++static int ksl_split_root(ngtcp2_ksl *ksl) { ++ ngtcp2_ksl_blk *rblk = NULL, *lblk, *nroot = NULL; + +- rblk = ksl_split_blk(ksl, ksl->head); ++ rblk = ksl_split_blk(ksl, ksl->root); + if (rblk == NULL) { + return NGTCP2_ERR_NOMEM; + } + +- lblk = ksl->head; ++ lblk = ksl->root; + +- nhead = ksl_blk_objalloc_new(ksl); ++ nroot = ksl_blk_objalloc_new(ksl); + +- if (nhead == NULL) { ++ if (nroot == NULL) { + ksl_blk_objalloc_del(ksl, rblk); + return NGTCP2_ERR_NOMEM; + } + +- nhead->next = nhead->prev = NULL; +- nhead->n = 2; +- nhead->leaf = 0; ++ nroot->next = nroot->prev = NULL; ++ nroot->n = 2; ++ nroot->leaf = 0; + +- node = ngtcp2_ksl_nth_node(ksl, nhead, 0); +- ksl_node_set_key(ksl, node, ngtcp2_ksl_nth_node(ksl, lblk, lblk->n - 1)->key); +- node->blk = lblk; ++ ksl_set_nth_key(ksl, nroot, 0, ngtcp2_ksl_blk_nth_key(lblk, lblk->n - 1)); ++ nroot->nodes[0].blk = lblk; + +- node = ngtcp2_ksl_nth_node(ksl, nhead, 1); +- ksl_node_set_key(ksl, node, ngtcp2_ksl_nth_node(ksl, rblk, rblk->n - 1)->key); +- node->blk = rblk; ++ ksl_set_nth_key(ksl, nroot, 1, ngtcp2_ksl_blk_nth_key(rblk, rblk->n - 1)); ++ nroot->nodes[1].blk = rblk; + +- ksl->head = nhead; ++ ksl->root = nroot; + + return 0; + } +@@ -257,16 +262,17 @@ static int ksl_split_head(ngtcp2_ksl *ksl) { + */ + static void ksl_insert_node(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i, + const ngtcp2_ksl_key *key, void *data) { +- ngtcp2_ksl_node *node; +- + assert(blk->n < NGTCP2_KSL_MAX_NBLK); + +- memmove(blk->nodes + (i + 1) * ksl->nodelen, blk->nodes + i * ksl->nodelen, +- ksl->nodelen * (blk->n - i)); ++ memmove(blk->nodes + (i + 1), blk->nodes + i, ++ (blk->n - i) * sizeof(ngtcp2_ksl_node)); ++ ++ memmove(blk->keys + (i + 1) * ksl->aligned_keylen, ++ blk->keys + i * ksl->aligned_keylen, ++ (blk->n - i) * ksl->aligned_keylen); + +- node = ngtcp2_ksl_nth_node(ksl, blk, i); +- ksl_node_set_key(ksl, node, key); +- node->data = data; ++ ksl_set_nth_key(ksl, blk, i, key); ++ blk->nodes[i].data = data; + + ++blk->n; + } +@@ -278,28 +284,27 @@ int ngtcp2_ksl_insert(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + size_t i; + int rv; + +- if (!ksl->head) { +- rv = ksl_head_init(ksl); ++ if (!ksl->root) { ++ rv = ksl_root_init(ksl); + if (rv != 0) { + return rv; + } + } + +- if (ksl->head->n == NGTCP2_KSL_MAX_NBLK) { +- rv = ksl_split_head(ksl); ++ if (ksl->root->n == NGTCP2_KSL_MAX_NBLK) { ++ rv = ksl_split_root(ksl); + if (rv != 0) { + return rv; + } + } + +- blk = ksl->head; ++ blk = ksl->root; + + for (;;) { + i = ksl->search(ksl, blk, key); + + if (blk->leaf) { +- if (i < blk->n && +- !ksl->compar(key, ngtcp2_ksl_nth_node(ksl, blk, i)->key)) { ++ if (i < blk->n && !ksl->compar(key, ngtcp2_ksl_blk_nth_key(blk, i))) { + if (it) { + *it = ngtcp2_ksl_end(ksl); + } +@@ -311,7 +316,7 @@ int ngtcp2_ksl_insert(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + ++ksl->n; + + if (it) { +- ngtcp2_ksl_it_init(it, ksl, blk, i); ++ ngtcp2_ksl_it_init(it, blk, i); + } + + return 0; +@@ -320,17 +325,17 @@ int ngtcp2_ksl_insert(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + if (i == blk->n) { + /* This insertion extends the largest key in this subtree. */ + for (; !blk->leaf;) { +- node = ngtcp2_ksl_nth_node(ksl, blk, blk->n - 1); ++ node = &blk->nodes[blk->n - 1]; + if (node->blk->n == NGTCP2_KSL_MAX_NBLK) { + rv = ksl_split_node(ksl, blk, blk->n - 1); + if (rv != 0) { + return rv; + } + +- node = ngtcp2_ksl_nth_node(ksl, blk, blk->n - 1); ++ node = &blk->nodes[blk->n - 1]; + } + +- ksl_node_set_key(ksl, node, key); ++ ksl_set_nth_key(ksl, blk, blk->n - 1, key); + blk = node->blk; + } + +@@ -338,13 +343,13 @@ int ngtcp2_ksl_insert(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + ++ksl->n; + + if (it) { +- ngtcp2_ksl_it_init(it, ksl, blk, blk->n - 1); ++ ngtcp2_ksl_it_init(it, blk, blk->n - 1); + } + + return 0; + } + +- node = ngtcp2_ksl_nth_node(ksl, blk, i); ++ node = &blk->nodes[i]; + + if (node->blk->n == NGTCP2_KSL_MAX_NBLK) { + rv = ksl_split_node(ksl, blk, i); +@@ -352,12 +357,8 @@ int ngtcp2_ksl_insert(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + return rv; + } + +- if (ksl->compar((ngtcp2_ksl_key *)node->key, key)) { +- node = ngtcp2_ksl_nth_node(ksl, blk, i + 1); +- +- if (ksl->compar((ngtcp2_ksl_key *)node->key, key)) { +- ksl_node_set_key(ksl, node, key); +- } ++ if (ksl->compar(ngtcp2_ksl_blk_nth_key(blk, i), key)) { ++ node = &blk->nodes[i + 1]; + } + } + +@@ -370,8 +371,12 @@ int ngtcp2_ksl_insert(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + * |i|. + */ + static void ksl_remove_node(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i) { +- memmove(blk->nodes + i * ksl->nodelen, blk->nodes + (i + 1) * ksl->nodelen, +- ksl->nodelen * (blk->n - (i + 1))); ++ memmove(blk->nodes + i, blk->nodes + (i + 1), ++ (blk->n - (i + 1)) * sizeof(ngtcp2_ksl_node)); ++ ++ memmove(blk->keys + i * ksl->aligned_keylen, ++ blk->keys + (i + 1) * ksl->aligned_keylen, ++ (blk->n - (i + 1)) * ksl->aligned_keylen); + + --blk->n; + } +@@ -380,9 +385,9 @@ static void ksl_remove_node(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i) { + * ksl_merge_node merges 2 nodes which are the nodes at the index of + * |i| and |i + 1|. + * +- * If |blk| is the head (root) block and it contains just 2 nodes +- * before merging nodes, the merged block becomes head block, which +- * decreases the height of |ksl| by 1. ++ * If |blk| is the root block and it contains just 2 nodes before ++ * merging nodes, the merged block becomes root block, which decreases ++ * the height of |ksl| by 1. + * + * This function returns the pointer to the merged block. + */ +@@ -393,15 +398,17 @@ static ngtcp2_ksl_blk *ksl_merge_node(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, + + assert(i + 1 < blk->n); + +- lnode = ngtcp2_ksl_nth_node(ksl, blk, i); ++ lnode = &blk->nodes[i]; + + lblk = lnode->blk; +- rblk = ngtcp2_ksl_nth_node(ksl, blk, i + 1)->blk; ++ rblk = blk->nodes[i + 1].blk; + +- assert(lblk->n + rblk->n < NGTCP2_KSL_MAX_NBLK); ++ assert(lblk->n + rblk->n <= NGTCP2_KSL_MAX_NBLK); + +- memcpy(lblk->nodes + ksl->nodelen * lblk->n, rblk->nodes, +- ksl->nodelen * rblk->n); ++ memcpy(lblk->nodes + lblk->n, rblk->nodes, rblk->n * sizeof(ngtcp2_ksl_node)); ++ ++ memcpy(lblk->keys + lblk->n * ksl->aligned_keylen, rblk->keys, ++ rblk->n * ksl->aligned_keylen); + + lblk->n += rblk->n; + lblk->next = rblk->next; +@@ -414,13 +421,12 @@ static ngtcp2_ksl_blk *ksl_merge_node(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, + + ksl_blk_objalloc_del(ksl, rblk); + +- if (ksl->head == blk && blk->n == 2) { +- ksl_blk_objalloc_del(ksl, ksl->head); +- ksl->head = lblk; ++ if (ksl->root == blk && blk->n == 2) { ++ ksl_blk_objalloc_del(ksl, ksl->root); ++ ksl->root = lblk; + } else { + ksl_remove_node(ksl, blk, i + 1); +- ksl_node_set_key(ksl, lnode, +- ngtcp2_ksl_nth_node(ksl, lblk, lblk->n - 1)->key); ++ ksl_set_nth_key(ksl, blk, i, ngtcp2_ksl_blk_nth_key(lblk, lblk->n - 1)); + } + + return lblk; +@@ -438,8 +444,8 @@ static void ksl_shift_left(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i) { + + assert(i > 0); + +- lnode = ngtcp2_ksl_nth_node(ksl, blk, i - 1); +- rnode = ngtcp2_ksl_nth_node(ksl, blk, i); ++ lnode = &blk->nodes[i - 1]; ++ rnode = &blk->nodes[i]; + + lblk = lnode->blk; + rblk = rnode->blk; +@@ -453,15 +459,20 @@ static void ksl_shift_left(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i) { + assert(lblk->n <= NGTCP2_KSL_MAX_NBLK - n); + assert(rblk->n >= NGTCP2_KSL_MIN_NBLK + n); + +- memcpy(lblk->nodes + ksl->nodelen * lblk->n, rblk->nodes, ksl->nodelen * n); ++ memcpy(lblk->nodes + lblk->n, rblk->nodes, n * sizeof(ngtcp2_ksl_node)); ++ ++ memcpy(lblk->keys + lblk->n * ksl->aligned_keylen, rblk->keys, ++ n * ksl->aligned_keylen); + + lblk->n += (uint32_t)n; + rblk->n -= (uint32_t)n; + +- ksl_node_set_key(ksl, lnode, +- ngtcp2_ksl_nth_node(ksl, lblk, lblk->n - 1)->key); ++ ksl_set_nth_key(ksl, blk, i - 1, ngtcp2_ksl_blk_nth_key(lblk, lblk->n - 1)); ++ ++ memmove(rblk->nodes, rblk->nodes + n, rblk->n * sizeof(ngtcp2_ksl_node)); + +- memmove(rblk->nodes, rblk->nodes + ksl->nodelen * n, ksl->nodelen * rblk->n); ++ memmove(rblk->keys, rblk->keys + n * ksl->aligned_keylen, ++ rblk->n * ksl->aligned_keylen); + } + + /* +@@ -476,8 +487,8 @@ static void ksl_shift_right(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i) { + + assert(i < blk->n - 1); + +- lnode = ngtcp2_ksl_nth_node(ksl, blk, i); +- rnode = ngtcp2_ksl_nth_node(ksl, blk, i + 1); ++ lnode = &blk->nodes[i]; ++ rnode = &blk->nodes[i + 1]; + + lblk = lnode->blk; + rblk = rnode->blk; +@@ -491,15 +502,20 @@ static void ksl_shift_right(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i) { + assert(lblk->n >= NGTCP2_KSL_MIN_NBLK + n); + assert(rblk->n <= NGTCP2_KSL_MAX_NBLK - n); + +- memmove(rblk->nodes + ksl->nodelen * n, rblk->nodes, ksl->nodelen * rblk->n); ++ memmove(rblk->nodes + n, rblk->nodes, rblk->n * sizeof(ngtcp2_ksl_node)); ++ ++ memmove(rblk->keys + n * ksl->aligned_keylen, rblk->keys, ++ rblk->n * ksl->aligned_keylen); + + rblk->n += (uint32_t)n; + lblk->n -= (uint32_t)n; + +- memcpy(rblk->nodes, lblk->nodes + ksl->nodelen * lblk->n, ksl->nodelen * n); ++ memcpy(rblk->nodes, lblk->nodes + lblk->n, n * sizeof(ngtcp2_ksl_node)); ++ ++ memcpy(rblk->keys, lblk->keys + lblk->n * ksl->aligned_keylen, ++ n * ksl->aligned_keylen); + +- ksl_node_set_key(ksl, lnode, +- ngtcp2_ksl_nth_node(ksl, lblk, lblk->n - 1)->key); ++ ksl_set_nth_key(ksl, blk, i, ngtcp2_ksl_blk_nth_key(lblk, lblk->n - 1)); + } + + /* +@@ -516,9 +532,9 @@ int ngtcp2_ksl_remove_hint(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + const ngtcp2_ksl_key *key) { + ngtcp2_ksl_blk *blk = hint->blk; + +- assert(ksl->head); ++ assert(ksl->root); + +- if (blk->n <= NGTCP2_KSL_MIN_NBLK) { ++ if (blk != ksl->root && blk->n == NGTCP2_KSL_MIN_NBLK) { + return ngtcp2_ksl_remove(ksl, it, key); + } + +@@ -528,9 +544,9 @@ int ngtcp2_ksl_remove_hint(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + + if (it) { + if (hint->i == blk->n && blk->next) { +- ngtcp2_ksl_it_init(it, ksl, blk->next, 0); ++ ngtcp2_ksl_it_init(it, blk->next, 0); + } else { +- ngtcp2_ksl_it_init(it, ksl, blk, hint->i); ++ ngtcp2_ksl_it_init(it, blk, hint->i); + } + } + +@@ -539,7 +555,7 @@ int ngtcp2_ksl_remove_hint(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + + int ngtcp2_ksl_remove(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + const ngtcp2_ksl_key *key) { +- ngtcp2_ksl_blk *blk = ksl->head; ++ ngtcp2_ksl_blk *blk = ksl->root; + ngtcp2_ksl_node *node; + size_t i; + +@@ -548,8 +564,8 @@ int ngtcp2_ksl_remove(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + } + + if (!blk->leaf && blk->n == 2 && +- ngtcp2_ksl_nth_node(ksl, blk, 0)->blk->n == NGTCP2_KSL_MIN_NBLK && +- ngtcp2_ksl_nth_node(ksl, blk, 1)->blk->n == NGTCP2_KSL_MIN_NBLK) { ++ blk->nodes[0].blk->n == NGTCP2_KSL_MIN_NBLK && ++ blk->nodes[1].blk->n == NGTCP2_KSL_MIN_NBLK) { + blk = ksl_merge_node(ksl, blk, 0); + } + +@@ -565,7 +581,7 @@ int ngtcp2_ksl_remove(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + } + + if (blk->leaf) { +- if (ksl->compar(key, ngtcp2_ksl_nth_node(ksl, blk, i)->key)) { ++ if (ksl->compar(key, ngtcp2_ksl_blk_nth_key(blk, i))) { + if (it) { + *it = ngtcp2_ksl_end(ksl); + } +@@ -578,16 +594,16 @@ int ngtcp2_ksl_remove(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + + if (it) { + if (blk->n == i && blk->next) { +- ngtcp2_ksl_it_init(it, ksl, blk->next, 0); ++ ngtcp2_ksl_it_init(it, blk->next, 0); + } else { +- ngtcp2_ksl_it_init(it, ksl, blk, i); ++ ngtcp2_ksl_it_init(it, blk, i); + } + } + + return 0; + } + +- node = ngtcp2_ksl_nth_node(ksl, blk, i); ++ node = &blk->nodes[i]; + + if (node->blk->n > NGTCP2_KSL_MIN_NBLK) { + blk = node->blk; +@@ -596,16 +612,14 @@ int ngtcp2_ksl_remove(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + + assert(node->blk->n == NGTCP2_KSL_MIN_NBLK); + +- if (i + 1 < blk->n && +- ngtcp2_ksl_nth_node(ksl, blk, i + 1)->blk->n > NGTCP2_KSL_MIN_NBLK) { ++ if (i + 1 < blk->n && blk->nodes[i + 1].blk->n > NGTCP2_KSL_MIN_NBLK) { + ksl_shift_left(ksl, blk, i + 1); + blk = node->blk; + + continue; + } + +- if (i > 0 && +- ngtcp2_ksl_nth_node(ksl, blk, i - 1)->blk->n > NGTCP2_KSL_MIN_NBLK) { ++ if (i > 0 && blk->nodes[i - 1].blk->n > NGTCP2_KSL_MIN_NBLK) { + ksl_shift_right(ksl, blk, i - 1); + blk = node->blk; + +@@ -631,12 +645,12 @@ ngtcp2_ksl_it ngtcp2_ksl_lower_bound(const ngtcp2_ksl *ksl, + ngtcp2_ksl_it ngtcp2_ksl_lower_bound_search(const ngtcp2_ksl *ksl, + const ngtcp2_ksl_key *key, + ngtcp2_ksl_search search) { +- ngtcp2_ksl_blk *blk = ksl->head; ++ ngtcp2_ksl_blk *blk = ksl->root; + ngtcp2_ksl_it it; + size_t i; + + if (!blk) { +- ngtcp2_ksl_it_init(&it, ksl, &null_blk, 0); ++ ngtcp2_ksl_it_init(&it, &null_blk, 0); + return it; + } + +@@ -649,7 +663,7 @@ ngtcp2_ksl_it ngtcp2_ksl_lower_bound_search(const ngtcp2_ksl *ksl, + i = 0; + } + +- ngtcp2_ksl_it_init(&it, ksl, blk, i); ++ ngtcp2_ksl_it_init(&it, blk, i); + + return it; + } +@@ -657,7 +671,7 @@ ngtcp2_ksl_it ngtcp2_ksl_lower_bound_search(const ngtcp2_ksl *ksl, + if (i == blk->n) { + /* This happens if descendant has smaller key. Fast forward to + find last node in this subtree. */ +- for (; !blk->leaf; blk = ngtcp2_ksl_nth_node(ksl, blk, blk->n - 1)->blk) ++ for (; !blk->leaf; blk = blk->nodes[blk->n - 1].blk) + ; + + if (blk->next) { +@@ -667,39 +681,41 @@ ngtcp2_ksl_it ngtcp2_ksl_lower_bound_search(const ngtcp2_ksl *ksl, + i = blk->n; + } + +- ngtcp2_ksl_it_init(&it, ksl, blk, i); ++ ngtcp2_ksl_it_init(&it, blk, i); + + return it; + } + +- blk = ngtcp2_ksl_nth_node(ksl, blk, i)->blk; ++ blk = blk->nodes[i].blk; + } + } + + void ngtcp2_ksl_update_key(ngtcp2_ksl *ksl, const ngtcp2_ksl_key *old_key, + const ngtcp2_ksl_key *new_key) { +- ngtcp2_ksl_blk *blk = ksl->head; ++ ngtcp2_ksl_blk *blk = ksl->root; + ngtcp2_ksl_node *node; ++ const ngtcp2_ksl_key *node_key; + size_t i; + +- assert(ksl->head); ++ assert(ksl->root); + + for (;;) { + i = ksl->search(ksl, blk, old_key); + + assert(i < blk->n); +- node = ngtcp2_ksl_nth_node(ksl, blk, i); ++ node = &blk->nodes[i]; ++ node_key = ngtcp2_ksl_blk_nth_key(blk, i); + + if (blk->leaf) { +- assert(key_equal(ksl->compar, (ngtcp2_ksl_key *)node->key, old_key)); +- ksl_node_set_key(ksl, node, new_key); ++ assert(key_equal(ksl->compar, node_key, old_key)); ++ ksl_set_nth_key(ksl, blk, i, new_key); + + return; + } + +- if (key_equal(ksl->compar, (ngtcp2_ksl_key *)node->key, old_key) || +- ksl->compar((ngtcp2_ksl_key *)node->key, new_key)) { +- ksl_node_set_key(ksl, node, new_key); ++ if (key_equal(ksl->compar, node_key, old_key) || ++ ksl->compar(node_key, new_key)) { ++ ksl_set_nth_key(ksl, blk, i, new_key); + } + + blk = node->blk; +@@ -709,15 +725,15 @@ void ngtcp2_ksl_update_key(ngtcp2_ksl *ksl, const ngtcp2_ksl_key *old_key, + size_t ngtcp2_ksl_len(const ngtcp2_ksl *ksl) { return ksl->n; } + + void ngtcp2_ksl_clear(ngtcp2_ksl *ksl) { +- if (!ksl->head) { ++ if (!ksl->root) { + return; + } + + #ifdef NOMEMPOOL +- ksl_free_blk(ksl, ksl->head); ++ ksl_free_blk(ksl, ksl->root); + #endif /* defined(NOMEMPOOL) */ + +- ksl->front = ksl->back = ksl->head = NULL; ++ ksl->front = ksl->back = ksl->root = NULL; + ksl->n = 0; + + ngtcp2_objalloc_clear(&ksl->blkalloc); +@@ -727,14 +743,12 @@ void ngtcp2_ksl_clear(ngtcp2_ksl *ksl) { + static void ksl_print(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, + size_t level) { + size_t i; +- ngtcp2_ksl_node *node; + + fprintf(stderr, "LV=%zu n=%u\n", level, blk->n); + + if (blk->leaf) { + for (i = 0; i < blk->n; ++i) { +- node = ngtcp2_ksl_nth_node(ksl, blk, i); +- fprintf(stderr, " %" PRId64, *(int64_t *)(void *)node->key); ++ fprintf(stderr, " %" PRId64, *(int64_t *)ngtcp2_ksl_blk_nth_key(blk, i)); + } + + fprintf(stderr, "\n"); +@@ -743,26 +757,26 @@ static void ksl_print(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, + } + + for (i = 0; i < blk->n; ++i) { +- ksl_print(ksl, ngtcp2_ksl_nth_node(ksl, blk, i)->blk, level + 1); ++ ksl_print(ksl, blk->nodes[i].blk, level + 1); + } + } + + void ngtcp2_ksl_print(const ngtcp2_ksl *ksl) { +- if (!ksl->head) { ++ if (!ksl->root) { + return; + } + +- ksl_print(ksl, ksl->head, 0); ++ ksl_print(ksl, ksl->root, 0); + } + #endif /* !defined(WIN32) */ + + ngtcp2_ksl_it ngtcp2_ksl_begin(const ngtcp2_ksl *ksl) { + ngtcp2_ksl_it it; + +- if (ksl->head) { +- ngtcp2_ksl_it_init(&it, ksl, ksl->front, 0); ++ if (ksl->root) { ++ ngtcp2_ksl_it_init(&it, ksl->front, 0); + } else { +- ngtcp2_ksl_it_init(&it, ksl, &null_blk, 0); ++ ngtcp2_ksl_it_init(&it, &null_blk, 0); + } + + return it; +@@ -771,18 +785,16 @@ ngtcp2_ksl_it ngtcp2_ksl_begin(const ngtcp2_ksl *ksl) { + ngtcp2_ksl_it ngtcp2_ksl_end(const ngtcp2_ksl *ksl) { + ngtcp2_ksl_it it; + +- if (ksl->head) { +- ngtcp2_ksl_it_init(&it, ksl, ksl->back, ksl->back->n); ++ if (ksl->root) { ++ ngtcp2_ksl_it_init(&it, ksl->back, ksl->back->n); + } else { +- ngtcp2_ksl_it_init(&it, ksl, &null_blk, 0); ++ ngtcp2_ksl_it_init(&it, &null_blk, 0); + } + + return it; + } + +-void ngtcp2_ksl_it_init(ngtcp2_ksl_it *it, const ngtcp2_ksl *ksl, +- ngtcp2_ksl_blk *blk, size_t i) { +- it->ksl = ksl; ++void ngtcp2_ksl_it_init(ngtcp2_ksl_it *it, ngtcp2_ksl_blk *blk, size_t i) { + it->blk = blk; + it->i = i; + } +@@ -802,12 +814,6 @@ int ngtcp2_ksl_it_begin(const ngtcp2_ksl_it *it) { + return it->i == 0 && it->blk->prev == NULL; + } + +-int ngtcp2_ksl_range_compar(const ngtcp2_ksl_key *lhs, +- const ngtcp2_ksl_key *rhs) { +- const ngtcp2_range *a = lhs, *b = rhs; +- return a->begin < b->begin; +-} +- + ngtcp2_ksl_search_def(range, ngtcp2_ksl_range_compar) + + size_t ngtcp2_ksl_range_search(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, +@@ -815,13 +821,6 @@ size_t ngtcp2_ksl_range_search(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, + return ksl_range_search(ksl, blk, key); + } + +-int ngtcp2_ksl_range_exclusive_compar(const ngtcp2_ksl_key *lhs, +- const ngtcp2_ksl_key *rhs) { +- const ngtcp2_range *a = lhs, *b = rhs; +- return a->begin < b->begin && !(ngtcp2_max_uint64(a->begin, b->begin) < +- ngtcp2_min_uint64(a->end, b->end)); +-} +- + ngtcp2_ksl_search_def(range_exclusive, ngtcp2_ksl_range_exclusive_compar) + + size_t ngtcp2_ksl_range_exclusive_search(const ngtcp2_ksl *ksl, +@@ -830,11 +829,6 @@ size_t ngtcp2_ksl_range_exclusive_search(const ngtcp2_ksl *ksl, + return ksl_range_exclusive_search(ksl, blk, key); + } + +-int ngtcp2_ksl_uint64_less(const ngtcp2_ksl_key *lhs, +- const ngtcp2_ksl_key *rhs) { +- return *(uint64_t *)lhs < *(uint64_t *)rhs; +-} +- + ngtcp2_ksl_search_def(uint64_less, ngtcp2_ksl_uint64_less) + + size_t ngtcp2_ksl_uint64_less_search(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, +@@ -842,11 +836,6 @@ size_t ngtcp2_ksl_uint64_less_search(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, + return ksl_uint64_less_search(ksl, blk, key); + } + +-int ngtcp2_ksl_int64_greater(const ngtcp2_ksl_key *lhs, +- const ngtcp2_ksl_key *rhs) { +- return *(int64_t *)lhs > *(int64_t *)rhs; +-} +- + ngtcp2_ksl_search_def(int64_greater, ngtcp2_ksl_int64_greater) + + size_t ngtcp2_ksl_int64_greater_search(const ngtcp2_ksl *ksl, +diff --git a/third_party/ngtcp2/lib/ngtcp2_ksl.h b/third_party/ngtcp2/lib/ngtcp2_ksl.h +index de78bcb8070..8024a360cdb 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_ksl.h ++++ b/third_party/ngtcp2/lib/ngtcp2_ksl.h +@@ -34,14 +34,15 @@ + #include + + #include "ngtcp2_objalloc.h" ++#include "ngtcp2_range.h" + + #define NGTCP2_KSL_DEGR 16 + /* NGTCP2_KSL_MAX_NBLK is the maximum number of nodes which a single + block can contain. */ +-#define NGTCP2_KSL_MAX_NBLK (2 * NGTCP2_KSL_DEGR - 1) ++#define NGTCP2_KSL_MAX_NBLK (2 * NGTCP2_KSL_DEGR) + /* NGTCP2_KSL_MIN_NBLK is the minimum number of nodes which a single + block other than root must contain. */ +-#define NGTCP2_KSL_MIN_NBLK (NGTCP2_KSL_DEGR - 1) ++#define NGTCP2_KSL_MIN_NBLK NGTCP2_KSL_DEGR + + /* + * ngtcp2_ksl_key represents key in ngtcp2_ksl. +@@ -55,22 +56,13 @@ typedef struct ngtcp2_ksl_blk ngtcp2_ksl_blk; + /* + * ngtcp2_ksl_node is a node which contains either ngtcp2_ksl_blk or + * opaque data. If a node is an internal node, it contains +- * ngtcp2_ksl_blk. Otherwise, it has data. The key is stored at the +- * location starting at key. ++ * ngtcp2_ksl_blk. Otherwise, it has data. + */ + struct ngtcp2_ksl_node { + union { + ngtcp2_ksl_blk *blk; + void *data; + }; +- union { +- uint64_t align; +- /* key is a buffer to include key associated to this node. +- Because the length of key is unknown until ngtcp2_ksl_init is +- called, the actual buffer will be allocated after this +- field. */ +- uint8_t key[1]; +- }; + }; + + /* +@@ -84,19 +76,19 @@ struct ngtcp2_ksl_blk { + /* prev points to the previous block if leaf field is + nonzero. */ + ngtcp2_ksl_blk *prev; ++ ngtcp2_ksl_node nodes[NGTCP2_KSL_MAX_NBLK]; ++ /* keys is a pointer to the buffer to include ++ NGTCP2_KSL_MAX_NBLK keys. Because the length of key is ++ unknown until ngtcp2_ksl_init is called, the actual buffer ++ will be allocated after this object. */ ++ uint8_t *keys; + /* n is the number of nodes this object contains in nodes. */ + uint32_t n; ++ /* aligned_keylen is the length of the single key including ++ alignment. */ ++ uint16_t aligned_keylen; + /* leaf is nonzero if this block contains leaf nodes. */ +- uint32_t leaf; +- union { +- uint64_t align; +- /* nodes is a buffer to contain NGTCP2_KSL_MAX_NBLK +- ngtcp2_ksl_node objects. Because ngtcp2_ksl_node object is +- allocated along with the additional variable length key +- storage, the size of buffer is unknown until ngtcp2_ksl_init is +- called. */ +- uint8_t nodes[1]; +- }; ++ uint8_t leaf; + }; + + ngtcp2_opl_entry oplent; +@@ -131,11 +123,10 @@ typedef size_t (*ngtcp2_ksl_search)(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, + static size_t ksl_##NAME##_search( \ + const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, const ngtcp2_ksl_key *key) { \ + size_t i; \ +- ngtcp2_ksl_node *node; \ ++ uint8_t *node_key; \ + \ +- for (i = 0, node = (ngtcp2_ksl_node *)(void *)blk->nodes; \ +- i < blk->n && COMPAR((ngtcp2_ksl_key *)node->key, key); ++i, \ +- node = (ngtcp2_ksl_node *)(void *)((uint8_t *)node + ksl->nodelen)) \ ++ for (i = 0, node_key = blk->keys; i < blk->n && COMPAR(node_key, key); \ ++ ++i, node_key += ksl->aligned_keylen) \ + ; \ + \ + return i; \ +@@ -147,7 +138,6 @@ typedef struct ngtcp2_ksl_it ngtcp2_ksl_it; + * ngtcp2_ksl_it is a bidirectional iterator to iterate nodes. + */ + struct ngtcp2_ksl_it { +- const ngtcp2_ksl *ksl; + ngtcp2_ksl_blk *blk; + size_t i; + }; +@@ -157,8 +147,8 @@ struct ngtcp2_ksl_it { + */ + struct ngtcp2_ksl { + ngtcp2_objalloc blkalloc; +- /* head points to the root block. */ +- ngtcp2_ksl_blk *head; ++ /* root points to the root block. */ ++ ngtcp2_ksl_blk *root; + /* front points to the first leaf block. */ + ngtcp2_ksl_blk *front; + /* back points to the last leaf block. */ +@@ -169,9 +159,7 @@ struct ngtcp2_ksl { + size_t n; + /* keylen is the size of key */ + size_t keylen; +- /* nodelen is the actual size of ngtcp2_ksl_node including key +- storage. */ +- size_t nodelen; ++ size_t aligned_keylen; + }; + + /* +@@ -290,10 +278,12 @@ size_t ngtcp2_ksl_len(const ngtcp2_ksl *ksl); + void ngtcp2_ksl_clear(ngtcp2_ksl *ksl); + + /* +- * ngtcp2_ksl_nth_node returns the |n|th node under |blk|. ++ * ngtcp2_ksl_blk_nth_key returns the |n|th key under |blk|. + */ +-#define ngtcp2_ksl_nth_node(KSL, BLK, N) \ +- ((ngtcp2_ksl_node *)(void *)((BLK)->nodes + (KSL)->nodelen * (N))) ++static inline const ngtcp2_ksl_key * ++ngtcp2_ksl_blk_nth_key(const ngtcp2_ksl_blk *blk, size_t n) { ++ return blk->keys + n * blk->aligned_keylen; ++} + + #ifndef WIN32 + /* +@@ -307,26 +297,28 @@ void ngtcp2_ksl_print(const ngtcp2_ksl *ksl); + /* + * ngtcp2_ksl_it_init initializes |it|. + */ +-void ngtcp2_ksl_it_init(ngtcp2_ksl_it *it, const ngtcp2_ksl *ksl, +- ngtcp2_ksl_blk *blk, size_t i); ++void ngtcp2_ksl_it_init(ngtcp2_ksl_it *it, ngtcp2_ksl_blk *blk, size_t i); + + /* + * ngtcp2_ksl_it_get returns the data associated to the node which + * |it| points to. It is undefined to call this function when + * ngtcp2_ksl_it_end(it) returns nonzero. + */ +-#define ngtcp2_ksl_it_get(IT) \ +- ngtcp2_ksl_nth_node((IT)->ksl, (IT)->blk, (IT)->i)->data ++static inline void *ngtcp2_ksl_it_get(const ngtcp2_ksl_it *it) { ++ return it->blk->nodes[it->i].data; ++} + + /* + * ngtcp2_ksl_it_next advances the iterator by one. It is undefined + * if this function is called when ngtcp2_ksl_it_end(it) returns + * nonzero. + */ +-#define ngtcp2_ksl_it_next(IT) \ +- (++(IT)->i == (IT)->blk->n && (IT)->blk->next \ +- ? ((IT)->blk = (IT)->blk->next, (IT)->i = 0) \ +- : 0) ++static inline void ngtcp2_ksl_it_next(ngtcp2_ksl_it *it) { ++ if (++it->i == it->blk->n && it->blk->next) { ++ it->blk = it->blk->next; ++ it->i = 0; ++ } ++} + + /* + * ngtcp2_ksl_it_prev moves backward the iterator by one. It is +@@ -339,8 +331,9 @@ void ngtcp2_ksl_it_prev(ngtcp2_ksl_it *it); + * ngtcp2_ksl_it_end returns nonzero if |it| points to the one beyond + * the last node. + */ +-#define ngtcp2_ksl_it_end(IT) \ +- ((IT)->blk->n == (IT)->i && (IT)->blk->next == NULL) ++static inline int ngtcp2_ksl_it_end(const ngtcp2_ksl_it *it) { ++ return it->blk->n == it->i && it->blk->next == NULL; ++} + + /* + * ngtcp2_ksl_it_begin returns nonzero if |it| points to the first +@@ -354,8 +347,9 @@ int ngtcp2_ksl_it_begin(const ngtcp2_ksl_it *it); + * It is undefined to call this function when ngtcp2_ksl_it_end(it) + * returns nonzero. + */ +-#define ngtcp2_ksl_it_key(IT) \ +- ((ngtcp2_ksl_key *)ngtcp2_ksl_nth_node((IT)->ksl, (IT)->blk, (IT)->i)->key) ++static inline const ngtcp2_ksl_key *ngtcp2_ksl_it_key(const ngtcp2_ksl_it *it) { ++ return ngtcp2_ksl_blk_nth_key(it->blk, it->i); ++} + + /* + * ngtcp2_ksl_range_compar is an implementation of ngtcp2_ksl_compar. +@@ -363,8 +357,12 @@ int ngtcp2_ksl_it_begin(const ngtcp2_ksl_it *it); + * returns nonzero if ((const ngtcp2_range *)lhs)->begin < ((const + * ngtcp2_range *)rhs)->begin. + */ +-int ngtcp2_ksl_range_compar(const ngtcp2_ksl_key *lhs, +- const ngtcp2_ksl_key *rhs); ++static inline int ngtcp2_ksl_range_compar(const ngtcp2_ksl_key *lhs, ++ const ngtcp2_ksl_key *rhs) { ++ const ngtcp2_range *a = (const ngtcp2_range *)lhs, ++ *b = (const ngtcp2_range *)rhs; ++ return a->begin < b->begin; ++} + + /* + * ngtcp2_ksl_range_search is an implementation of ngtcp2_ksl_search +@@ -380,8 +378,13 @@ size_t ngtcp2_ksl_range_search(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, + * *)lhs)->begin < ((const ngtcp2_range *)rhs)->begin, and the 2 + * ranges do not intersect. + */ +-int ngtcp2_ksl_range_exclusive_compar(const ngtcp2_ksl_key *lhs, +- const ngtcp2_ksl_key *rhs); ++static inline int ngtcp2_ksl_range_exclusive_compar(const ngtcp2_ksl_key *lhs, ++ const ngtcp2_ksl_key *rhs) { ++ const ngtcp2_range *a = (const ngtcp2_range *)lhs, ++ *b = (const ngtcp2_range *)rhs; ++ return a->begin < b->begin && !(ngtcp2_max_uint64(a->begin, b->begin) < ++ ngtcp2_min_uint64(a->end, b->end)); ++} + + /* + * ngtcp2_ksl_range_exclusive_search is an implementation of +@@ -396,8 +399,10 @@ size_t ngtcp2_ksl_range_exclusive_search(const ngtcp2_ksl *ksl, + * |lhs| and |rhs| must point to uint64_t objects, and the function + * returns nonzero if *(uint64_t *)|lhs| < *(uint64_t *)|rhs|. + */ +-int ngtcp2_ksl_uint64_less(const ngtcp2_ksl_key *lhs, +- const ngtcp2_ksl_key *rhs); ++static inline int ngtcp2_ksl_uint64_less(const ngtcp2_ksl_key *lhs, ++ const ngtcp2_ksl_key *rhs) { ++ return *(const uint64_t *)lhs < *(const uint64_t *)rhs; ++} + + /* + * ngtcp2_ksl_uint64_less_search is an implementation of +@@ -411,8 +416,10 @@ size_t ngtcp2_ksl_uint64_less_search(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, + * |lhs| and |rhs| must point to int64_t objects, and the function + * returns nonzero if *(int64_t *)|lhs| > *(int64_t *)|rhs|. + */ +-int ngtcp2_ksl_int64_greater(const ngtcp2_ksl_key *lhs, +- const ngtcp2_ksl_key *rhs); ++static inline int ngtcp2_ksl_int64_greater(const ngtcp2_ksl_key *lhs, ++ const ngtcp2_ksl_key *rhs) { ++ return *(const int64_t *)lhs > *(const int64_t *)rhs; ++} + + /* + * ngtcp2_ksl_int64_greater_search is an implementation of +diff --git a/third_party/ngtcp2/lib/ngtcp2_log.c b/third_party/ngtcp2/lib/ngtcp2_log.c +index fc4eb443517..191c64430b5 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_log.c ++++ b/third_party/ngtcp2/lib/ngtcp2_log.c +@@ -42,12 +42,12 @@ void ngtcp2_log_init(ngtcp2_log *log, const ngtcp2_cid *scid, + ngtcp2_printf log_printf, ngtcp2_tstamp ts, + void *user_data) { + if (scid) { +- ngtcp2_encode_hex(log->scid, scid->data, scid->datalen); ++ ngtcp2_encode_hex_cstr(log->scid, scid->data, scid->datalen); + } else { + log->scid[0] = '\0'; + } + log->log_printf = log_printf; +- log->events = 0xff; ++ log->events = 0xFF; + log->ts = log->last_ts = ts; + log->user_data = user_data; + } +@@ -90,23 +90,10 @@ void ngtcp2_log_init(ngtcp2_log *log, const ngtcp2_cid *scid, + * Frame type in hex string. + */ + +-#define NGTCP2_LOG_BUFLEN 4096 ++#define NGTCP2_LOG_PKT "%s %" PRId64 " %s" ++#define NGTCP2_LOG_TP "remote transport_parameters" + +-/* TODO Split second and remaining fraction with comma */ +-#define NGTCP2_LOG_HD "I%08" PRIu64 " 0x%s %s" +-#define NGTCP2_LOG_PKT NGTCP2_LOG_HD " %s %" PRId64 " %s" +-#define NGTCP2_LOG_TP NGTCP2_LOG_HD " remote transport_parameters" +- +-#define NGTCP2_LOG_FRM_HD_FIELDS(DIR) \ +- timestamp_cast(log->last_ts - log->ts), (const char *)log->scid, "frm", \ +- (DIR), hd->pkt_num, strpkttype(hd) +- +-#define NGTCP2_LOG_PKT_HD_FIELDS(DIR) \ +- timestamp_cast(log->last_ts - log->ts), (const char *)log->scid, "pkt", \ +- (DIR), hd->pkt_num, strpkttype(hd) +- +-#define NGTCP2_LOG_TP_HD_FIELDS \ +- timestamp_cast(log->last_ts - log->ts), (const char *)log->scid, "cry" ++#define NGTCP2_LOG_PKT_HD_FIELDS(DIR) (DIR), hd->pkt_num, strpkttype(hd) + + static const char *strerrorcode(uint64_t error_code) { + switch (error_code) { +@@ -140,10 +127,14 @@ static const char *strerrorcode(uint64_t error_code) { + return "CRYPTO_BUFFER_EXCEEDED"; + case NGTCP2_KEY_UPDATE_ERROR: + return "KEY_UPDATE_ERROR"; ++ case NGTCP2_AEAD_LIMIT_REACHED: ++ return "AEAD_LIMIT_REACHED"; ++ case NGTCP2_NO_VIABLE_PATH: ++ return "NO_VIABLE_PATH"; + case NGTCP2_VERSION_NEGOTIATION_ERROR: + return "VERSION_NEGOTIATION_ERROR"; + default: +- if (0x100u <= error_code && error_code <= 0x1ffu) { ++ if (0x100U <= error_code && error_code <= 0x1FFU) { + return "CRYPTO_ERROR"; + } + return "(unknown)"; +@@ -155,78 +146,41 @@ static const char *strapperrorcode(uint64_t app_error_code) { + return "(unknown)"; + } + +-static const char *strpkttype_long(uint8_t type) { +- switch (type) { ++static const char *strpkttype(const ngtcp2_pkt_hd *hd) { ++ switch (hd->type) { + case NGTCP2_PKT_INITIAL: + return "Initial"; +- case NGTCP2_PKT_RETRY: +- return "Retry"; +- case NGTCP2_PKT_HANDSHAKE: +- return "Handshake"; + case NGTCP2_PKT_0RTT: + return "0RTT"; +- default: +- return "(unknown)"; +- } +-} +- +-static const char *strpkttype(const ngtcp2_pkt_hd *hd) { +- if (hd->flags & NGTCP2_PKT_FLAG_LONG_FORM) { +- return strpkttype_long(hd->type); +- } +- +- switch (hd->type) { ++ case NGTCP2_PKT_HANDSHAKE: ++ return "Handshake"; ++ case NGTCP2_PKT_RETRY: ++ return "Retry"; ++ case NGTCP2_PKT_1RTT: ++ return "1RTT"; + case NGTCP2_PKT_VERSION_NEGOTIATION: + return "VN"; + case NGTCP2_PKT_STATELESS_RESET: + return "SR"; +- case NGTCP2_PKT_1RTT: +- return "1RTT"; + default: + return "(unknown)"; + } + } + + static const char *strpkttype_type_flags(uint8_t type, uint8_t flags) { +- ngtcp2_pkt_hd hd = {0}; +- +- hd.type = type; +- hd.flags = flags; +- +- return strpkttype(&hd); +-} +- +-static const char *strevent(ngtcp2_log_event ev) { +- switch (ev) { +- case NGTCP2_LOG_EVENT_CON: +- return "con"; +- case NGTCP2_LOG_EVENT_PKT: +- return "pkt"; +- case NGTCP2_LOG_EVENT_FRM: +- return "frm"; +- case NGTCP2_LOG_EVENT_LDC: +- return "ldc"; +- case NGTCP2_LOG_EVENT_CRY: +- return "cry"; +- case NGTCP2_LOG_EVENT_PTV: +- return "ptv"; +- case NGTCP2_LOG_EVENT_CCA: +- return "cca"; +- case NGTCP2_LOG_EVENT_NONE: +- default: +- return "non"; +- } ++ return strpkttype(&(ngtcp2_pkt_hd){ ++ .type = type, ++ .flags = flags, ++ }); + } + +-static uint64_t timestamp_cast(uint64_t ns) { return ns / NGTCP2_MILLISECONDS; } +- + static void log_fr_stream(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_stream *fr, const char *dir) { +- log->log_printf( +- log->user_data, +- (NGTCP2_LOG_PKT " STREAM(0x%02" PRIx64 ") id=0x%" PRIx64 +- " fin=%d offset=%" PRIu64 " len=%" PRIu64 " uni=%d"), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type | fr->flags, fr->stream_id, fr->fin, ++ ngtcp2_log_infof_raw( ++ log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " STREAM(0x%02" PRIx64 ") id=0x%" PRIx64 ++ " fin=%d offset=%" PRIu64 " len=%" PRIu64 " uni=%d", ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type | fr->flags, fr->stream_id, fr->fin, + fr->offset, ngtcp2_vec_len(fr->data, fr->datacnt), + (fr->stream_id & 0x2) != 0); + } +@@ -236,58 +190,58 @@ static void log_fr_ack(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + int64_t largest_ack, min_ack; + size_t i; + +- log->log_printf( +- log->user_data, +- (NGTCP2_LOG_PKT " ACK(0x%02" PRIx64 ") largest_ack=%" PRId64 +- " ack_delay=%" PRIu64 "(%" PRIu64 ") ack_range_count=%zu"), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->largest_ack, ++ ngtcp2_log_infof_raw( ++ log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " ACK(0x%02" PRIx64 ") largest_ack=%" PRId64 ++ " ack_delay=%" PRIu64 "(%" PRIu64 ") ack_range_count=%zu", ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, fr->largest_ack, + fr->ack_delay_unscaled / NGTCP2_MILLISECONDS, fr->ack_delay, fr->rangecnt); + + largest_ack = fr->largest_ack; + min_ack = fr->largest_ack - (int64_t)fr->first_ack_range; + +- log->log_printf(log->user_data, +- (NGTCP2_LOG_PKT " ACK(0x%02" PRIx64 ") range=[%" PRId64 +- "..%" PRId64 "] len=%" PRIu64), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, largest_ack, min_ack, +- fr->first_ack_range); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " ACK(0x%02" PRIx64 ") range=[%" PRId64 ++ "..%" PRId64 "] len=%" PRIu64, ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, largest_ack, ++ min_ack, fr->first_ack_range); + + for (i = 0; i < fr->rangecnt; ++i) { + const ngtcp2_ack_range *range = &fr->ranges[i]; + largest_ack = min_ack - (int64_t)range->gap - 2; + min_ack = largest_ack - (int64_t)range->len; +- log->log_printf(log->user_data, +- (NGTCP2_LOG_PKT " ACK(0x%02" PRIx64 ") range=[%" PRId64 +- "..%" PRId64 "] gap=%" PRIu64 +- " len=%" PRIu64), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, largest_ack, +- min_ack, range->gap, range->len); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " ACK(0x%02" PRIx64 ") range=[%" PRId64 ++ "..%" PRId64 "] gap=%" PRIu64 ++ " len=%" PRIu64, ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, largest_ack, ++ min_ack, range->gap, range->len); + } + + if (fr->type == NGTCP2_FRAME_ACK_ECN) { +- log->log_printf(log->user_data, +- (NGTCP2_LOG_PKT " ACK(0x%02" PRIx64 ") ect0=%" PRIu64 +- " ect1=%" PRIu64 " ce=%" PRIu64), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->ecn.ect0, +- fr->ecn.ect1, fr->ecn.ce); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " ACK(0x%02" PRIx64 ") ect0=%" PRIu64 ++ " ect1=%" PRIu64 " ce=%" PRIu64, ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, fr->ecn.ect0, ++ fr->ecn.ect1, fr->ecn.ce); + } + } + + static void log_fr_padding(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_padding *fr, const char *dir) { +- log->log_printf(log->user_data, +- (NGTCP2_LOG_PKT " PADDING(0x%02" PRIx64 ") len=%zu"), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->len); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " PADDING(0x%02" PRIx64 ") len=%zu", ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, fr->len); + } + + static void log_fr_reset_stream(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_reset_stream *fr, + const char *dir) { +- log->log_printf( +- log->user_data, +- (NGTCP2_LOG_PKT " RESET_STREAM(0x%02" PRIx64 ") id=0x%" PRIx64 +- " app_error_code=%s(0x%" PRIx64 ") final_size=%" PRIu64), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->stream_id, ++ ngtcp2_log_infof_raw( ++ log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " RESET_STREAM(0x%02" PRIx64 ") id=0x%" PRIx64 ++ " app_error_code=%s(0x%" PRIx64 ") final_size=%" PRIu64, ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, fr->stream_id, + strapperrorcode(fr->app_error_code), fr->app_error_code, fr->final_size); + } + +@@ -297,189 +251,191 @@ static void log_fr_connection_close(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + char reason[256]; + size_t reasonlen = ngtcp2_min_size(sizeof(reason) - 1, fr->reasonlen); + +- log->log_printf( +- log->user_data, +- (NGTCP2_LOG_PKT " CONNECTION_CLOSE(0x%02" PRIx64 +- ") error_code=%s(0x%" PRIx64 ") " +- "frame_type=%" PRIx64 " reason_len=%zu reason=[%s]"), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, ++ ngtcp2_log_infof_raw( ++ log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " CONNECTION_CLOSE(0x%02" PRIx64 ") error_code=%s(0x%" PRIx64 ++ ") " ++ "frame_type=0x%" PRIx64 " reason_len=%zu reason=[%s]", ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, + fr->type == NGTCP2_FRAME_CONNECTION_CLOSE ? strerrorcode(fr->error_code) + : strapperrorcode(fr->error_code), + fr->error_code, fr->frame_type, fr->reasonlen, +- ngtcp2_encode_printable_ascii(reason, fr->reason, reasonlen)); ++ ngtcp2_encode_printable_ascii_cstr(reason, fr->reason, reasonlen)); + } + + static void log_fr_max_data(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_max_data *fr, const char *dir) { +- log->log_printf( +- log->user_data, +- (NGTCP2_LOG_PKT " MAX_DATA(0x%02" PRIx64 ") max_data=%" PRIu64), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->max_data); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " MAX_DATA(0x%02" PRIx64 ++ ") max_data=%" PRIu64, ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, fr->max_data); + } + + static void log_fr_max_stream_data(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_max_stream_data *fr, + const char *dir) { +- log->log_printf(log->user_data, +- (NGTCP2_LOG_PKT " MAX_STREAM_DATA(0x%02" PRIx64 +- ") id=0x%" PRIx64 +- " max_stream_data=%" PRIu64), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->stream_id, +- fr->max_stream_data); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " MAX_STREAM_DATA(0x%02" PRIx64 ++ ") id=0x%" PRIx64 ++ " max_stream_data=%" PRIu64, ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, fr->stream_id, ++ fr->max_stream_data); + } + + static void log_fr_max_streams(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_max_streams *fr, const char *dir) { +- log->log_printf( +- log->user_data, +- (NGTCP2_LOG_PKT " MAX_STREAMS(0x%02" PRIx64 ") max_streams=%" PRIu64), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->max_streams); ++ ngtcp2_log_infof_raw( ++ log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " MAX_STREAMS(0x%02" PRIx64 ") max_streams=%" PRIu64, ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, fr->max_streams); + } + + static void log_fr_ping(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_ping *fr, const char *dir) { +- log->log_printf(log->user_data, (NGTCP2_LOG_PKT " PING(0x%02" PRIx64 ")"), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " PING(0x%02" PRIx64 ")", ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type); + } + + static void log_fr_data_blocked(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_data_blocked *fr, + const char *dir) { +- log->log_printf( +- log->user_data, +- (NGTCP2_LOG_PKT " DATA_BLOCKED(0x%02" PRIx64 ") offset=%" PRIu64), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->offset); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " DATA_BLOCKED(0x%02" PRIx64 ++ ") offset=%" PRIu64, ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, fr->offset); + } + + static void log_fr_stream_data_blocked(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_stream_data_blocked *fr, + const char *dir) { +- log->log_printf(log->user_data, +- (NGTCP2_LOG_PKT " STREAM_DATA_BLOCKED(0x%02" PRIx64 +- ") id=0x%" PRIx64 " offset=%" PRIu64), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->stream_id, +- fr->offset); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " STREAM_DATA_BLOCKED(0x%02" PRIx64 ++ ") id=0x%" PRIx64 " offset=%" PRIu64, ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, fr->stream_id, ++ fr->offset); + } + + static void log_fr_streams_blocked(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_streams_blocked *fr, + const char *dir) { +- log->log_printf( +- log->user_data, +- (NGTCP2_LOG_PKT " STREAMS_BLOCKED(0x%02" PRIx64 ") max_streams=%" PRIu64), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->max_streams); ++ ngtcp2_log_infof_raw( ++ log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " STREAMS_BLOCKED(0x%02" PRIx64 ") max_streams=%" PRIu64, ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, fr->max_streams); + } + + static void log_fr_new_connection_id(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_new_connection_id *fr, + const char *dir) { +- uint8_t buf[sizeof(fr->stateless_reset_token) * 2 + 1]; +- uint8_t cid[sizeof(fr->cid.data) * 2 + 1]; +- +- log->log_printf( +- log->user_data, +- (NGTCP2_LOG_PKT " NEW_CONNECTION_ID(0x%02" PRIx64 ") seq=%" PRIu64 +- " cid=0x%s retire_prior_to=%" PRIu64 +- " stateless_reset_token=0x%s"), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->seq, +- (const char *)ngtcp2_encode_hex(cid, fr->cid.data, fr->cid.datalen), ++ char buf[sizeof(fr->token.data) * 2 + 1]; ++ char cid[sizeof(fr->cid.data) * 2 + 1]; ++ ++ ngtcp2_log_infof_raw( ++ log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " NEW_CONNECTION_ID(0x%02" PRIx64 ") seq=%" PRIu64 ++ " cid=0x%s retire_prior_to=%" PRIu64 ++ " stateless_reset_token=0x%s", ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, fr->seq, ++ ngtcp2_encode_hex_cstr(cid, fr->cid.data, fr->cid.datalen), + fr->retire_prior_to, +- (const char *)ngtcp2_encode_hex(buf, fr->stateless_reset_token, +- sizeof(fr->stateless_reset_token))); ++ ngtcp2_encode_hex_cstr(buf, fr->token.data, sizeof(fr->token.data))); + } + + static void log_fr_stop_sending(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_stop_sending *fr, + const char *dir) { +- log->log_printf(log->user_data, +- (NGTCP2_LOG_PKT " STOP_SENDING(0x%02" PRIx64 ") id=0x%" PRIx64 +- " app_error_code=%s(0x%" PRIx64 ")"), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->stream_id, +- strapperrorcode(fr->app_error_code), fr->app_error_code); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " STOP_SENDING(0x%02" PRIx64 ++ ") id=0x%" PRIx64 ++ " app_error_code=%s(0x%" PRIx64 ")", ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, fr->stream_id, ++ strapperrorcode(fr->app_error_code), fr->app_error_code); + } + + static void log_fr_path_challenge(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_path_challenge *fr, + const char *dir) { +- uint8_t buf[sizeof(fr->data) * 2 + 1]; ++ char buf[sizeof(fr->data.data) * 2 + 1]; + +- log->log_printf( +- log->user_data, +- (NGTCP2_LOG_PKT " PATH_CHALLENGE(0x%02" PRIx64 ") data=0x%s"), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, +- (const char *)ngtcp2_encode_hex(buf, fr->data, sizeof(fr->data))); ++ ngtcp2_log_infof_raw( ++ log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " PATH_CHALLENGE(0x%02" PRIx64 ") data=0x%s", ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, ++ ngtcp2_encode_hex_cstr(buf, fr->data.data, sizeof(fr->data.data))); + } + + static void log_fr_path_response(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_path_response *fr, + const char *dir) { +- uint8_t buf[sizeof(fr->data) * 2 + 1]; ++ char buf[sizeof(fr->data.data) * 2 + 1]; + +- log->log_printf( +- log->user_data, +- (NGTCP2_LOG_PKT " PATH_RESPONSE(0x%02" PRIx64 ") data=0x%s"), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, +- (const char *)ngtcp2_encode_hex(buf, fr->data, sizeof(fr->data))); ++ ngtcp2_log_infof_raw( ++ log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " PATH_RESPONSE(0x%02" PRIx64 ") data=0x%s", ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, ++ ngtcp2_encode_hex_cstr(buf, fr->data.data, sizeof(fr->data.data))); + } + + static void log_fr_crypto(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_stream *fr, const char *dir) { +- log->log_printf( +- log->user_data, +- (NGTCP2_LOG_PKT " CRYPTO(0x%02" PRIx64 ") offset=%" PRIu64 " len=%" PRIu64), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->offset, +- ngtcp2_vec_len(fr->data, fr->datacnt)); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " CRYPTO(0x%02" PRIx64 ") offset=%" PRIu64 ++ " len=%" PRIu64, ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, fr->offset, ++ ngtcp2_vec_len(fr->data, fr->datacnt)); + } + + static void log_fr_new_token(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_new_token *fr, const char *dir) { + /* Show at most first 64 bytes of token. If token is longer than 64 + bytes, log first 64 bytes and then append "*" */ +- uint8_t buf[128 + 1 + 1]; +- uint8_t *p; ++ char buf[128 + 1 + 1]; ++ char *p; + + if (fr->tokenlen > 64) { +- p = ngtcp2_encode_hex(buf, fr->token, 64); ++ p = ngtcp2_encode_hex_cstr(buf, fr->token, 64); + p[128] = '*'; + p[129] = '\0'; + } else { +- p = ngtcp2_encode_hex(buf, fr->token, fr->tokenlen); ++ p = ngtcp2_encode_hex_cstr(buf, fr->token, fr->tokenlen); + } +- log->log_printf( +- log->user_data, +- (NGTCP2_LOG_PKT " NEW_TOKEN(0x%02" PRIx64 ") token=0x%s len=%zu"), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, (const char *)p, fr->tokenlen); ++ ++ ngtcp2_log_infof_raw( ++ log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " NEW_TOKEN(0x%02" PRIx64 ") token=0x%s len=%zu", ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, p, fr->tokenlen); + } + + static void log_fr_retire_connection_id(ngtcp2_log *log, + const ngtcp2_pkt_hd *hd, + const ngtcp2_retire_connection_id *fr, + const char *dir) { +- log->log_printf( +- log->user_data, +- (NGTCP2_LOG_PKT " RETIRE_CONNECTION_ID(0x%02" PRIx64 ") seq=%" PRIu64), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->seq); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " RETIRE_CONNECTION_ID(0x%02" PRIx64 ++ ") seq=%" PRIu64, ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, fr->seq); + } + + static void log_fr_handshake_done(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_handshake_done *fr, + const char *dir) { +- log->log_printf(log->user_data, +- (NGTCP2_LOG_PKT " HANDSHAKE_DONE(0x%02" PRIx64 ")"), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " HANDSHAKE_DONE(0x%02" PRIx64 ")", ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type); + } + + static void log_fr_datagram(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_datagram *fr, const char *dir) { +- log->log_printf(log->user_data, +- (NGTCP2_LOG_PKT " DATAGRAM(0x%02" PRIx64 ") len=%" PRIu64), +- NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, +- ngtcp2_vec_len(fr->data, fr->datacnt)); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_FRM, ++ NGTCP2_LOG_PKT " DATAGRAM(0x%02" PRIx64 ") len=%" PRIu64, ++ NGTCP2_LOG_PKT_HD_FIELDS(dir), fr->type, ++ ngtcp2_vec_len(fr->data, fr->datacnt)); + } + + static void log_fr(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_frame *fr, const char *dir) { +- switch (fr->type) { ++ switch (fr->hd.type) { + case NGTCP2_FRAME_STREAM: + log_fr_stream(log, hd, &fr->stream, dir); + break; +@@ -580,13 +536,13 @@ void ngtcp2_log_rx_vn(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + } + + for (i = 0; i < nsv; ++i) { +- log->log_printf(log->user_data, (NGTCP2_LOG_PKT " v=0x%08x"), +- NGTCP2_LOG_PKT_HD_FIELDS("rx"), sv[i]); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_PKT, NGTCP2_LOG_PKT " v=0x%08x", ++ NGTCP2_LOG_PKT_HD_FIELDS("rx"), sv[i]); + } + } + +-void ngtcp2_log_rx_sr(ngtcp2_log *log, const ngtcp2_pkt_stateless_reset *sr) { +- uint8_t buf[sizeof(sr->stateless_reset_token) * 2 + 1]; ++void ngtcp2_log_rx_sr(ngtcp2_log *log, const ngtcp2_pkt_stateless_reset2 *sr) { ++ char buf[sizeof(sr->token.data) * 2 + 1]; + ngtcp2_pkt_hd shd; + ngtcp2_pkt_hd *hd = &shd; + +@@ -594,23 +550,22 @@ void ngtcp2_log_rx_sr(ngtcp2_log *log, const ngtcp2_pkt_stateless_reset *sr) { + return; + } + +- memset(&shd, 0, sizeof(shd)); +- +- shd.type = NGTCP2_PKT_STATELESS_RESET; ++ shd = (ngtcp2_pkt_hd){ ++ .type = NGTCP2_PKT_STATELESS_RESET, ++ }; + +- log->log_printf( +- log->user_data, (NGTCP2_LOG_PKT " token=0x%s randlen=%zu"), ++ ngtcp2_log_infof_raw( ++ log, NGTCP2_LOG_EVENT_PKT, NGTCP2_LOG_PKT " token=0x%s randlen=%zu", + NGTCP2_LOG_PKT_HD_FIELDS("rx"), +- (const char *)ngtcp2_encode_hex(buf, sr->stateless_reset_token, +- sizeof(sr->stateless_reset_token)), ++ ngtcp2_encode_hex_cstr(buf, sr->token.data, sizeof(sr->token.data)), + sr->randlen); + } + + void ngtcp2_log_remote_tp(ngtcp2_log *log, + const ngtcp2_transport_params *params) { +- uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN * 2 + 1]; +- uint8_t addr[16 * 2 + 7 + 1]; +- uint8_t cid[NGTCP2_MAX_CIDLEN * 2 + 1]; ++ char token[sizeof(params->stateless_reset_token) * 2 + 1]; ++ char addr[16 * 2 + 7 + 1]; ++ char cid[NGTCP2_MAX_CIDLEN * 2 + 1]; + size_t i; + const ngtcp2_sockaddr_in *sa_in; + const ngtcp2_sockaddr_in6 *sa_in6; +@@ -622,127 +577,121 @@ void ngtcp2_log_remote_tp(ngtcp2_log *log, + } + + if (params->stateless_reset_token_present) { +- log->log_printf( +- log->user_data, (NGTCP2_LOG_TP " stateless_reset_token=0x%s"), +- NGTCP2_LOG_TP_HD_FIELDS, +- (const char *)ngtcp2_encode_hex(token, params->stateless_reset_token, +- sizeof(params->stateless_reset_token))); ++ ngtcp2_log_infof_raw( ++ log, NGTCP2_LOG_EVENT_CRY, NGTCP2_LOG_TP " stateless_reset_token=0x%s", ++ ngtcp2_encode_hex_cstr(token, params->stateless_reset_token, ++ sizeof(params->stateless_reset_token))); + } + + if (params->preferred_addr_present) { + if (params->preferred_addr.ipv4_present) { + sa_in = ¶ms->preferred_addr.ipv4; + +- log->log_printf(log->user_data, +- (NGTCP2_LOG_TP " preferred_address.ipv4_addr=%s"), +- NGTCP2_LOG_TP_HD_FIELDS, +- (const char *)ngtcp2_encode_ipv4( +- addr, (const uint8_t *)&sa_in->sin_addr)); +- log->log_printf(log->user_data, +- (NGTCP2_LOG_TP " preferred_address.ipv4_port=%u"), +- NGTCP2_LOG_TP_HD_FIELDS, ngtcp2_ntohs(sa_in->sin_port)); ++ ngtcp2_log_infof_raw( ++ log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP " preferred_address.ipv4_addr=%s", ++ ngtcp2_encode_ipv4_cstr(addr, (const uint8_t *)&sa_in->sin_addr)); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP " preferred_address.ipv4_port=%u", ++ ngtcp2_ntohs(sa_in->sin_port)); + } + + if (params->preferred_addr.ipv6_present) { + sa_in6 = ¶ms->preferred_addr.ipv6; + +- log->log_printf(log->user_data, +- (NGTCP2_LOG_TP " preferred_address.ipv6_addr=%s"), +- NGTCP2_LOG_TP_HD_FIELDS, +- (const char *)ngtcp2_encode_ipv6( +- addr, (const uint8_t *)&sa_in6->sin6_addr)); +- log->log_printf(log->user_data, +- (NGTCP2_LOG_TP " preferred_address.ipv6_port=%u"), +- NGTCP2_LOG_TP_HD_FIELDS, ngtcp2_ntohs(sa_in6->sin6_port)); ++ ngtcp2_log_infof_raw( ++ log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP " preferred_address.ipv6_addr=%s", ++ ngtcp2_encode_ipv6_cstr(addr, (const uint8_t *)&sa_in6->sin6_addr)); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP " preferred_address.ipv6_port=%u", ++ ngtcp2_ntohs(sa_in6->sin6_port)); + } + +- log->log_printf( +- log->user_data, (NGTCP2_LOG_TP " preferred_address.cid=0x%s"), +- NGTCP2_LOG_TP_HD_FIELDS, +- (const char *)ngtcp2_encode_hex(cid, params->preferred_addr.cid.data, +- params->preferred_addr.cid.datalen)); +- log->log_printf( +- log->user_data, +- (NGTCP2_LOG_TP " preferred_address.stateless_reset_token=0x%s"), +- NGTCP2_LOG_TP_HD_FIELDS, +- (const char *)ngtcp2_encode_hex( ++ ngtcp2_log_infof_raw( ++ log, NGTCP2_LOG_EVENT_CRY, NGTCP2_LOG_TP " preferred_address.cid=0x%s", ++ ngtcp2_encode_hex_cstr(cid, params->preferred_addr.cid.data, ++ params->preferred_addr.cid.datalen)); ++ ngtcp2_log_infof_raw( ++ log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP " preferred_address.stateless_reset_token=0x%s", ++ ngtcp2_encode_hex_cstr( + token, params->preferred_addr.stateless_reset_token, + sizeof(params->preferred_addr.stateless_reset_token))); + } + + if (params->original_dcid_present) { +- log->log_printf( +- log->user_data, +- (NGTCP2_LOG_TP " original_destination_connection_id=0x%s"), +- NGTCP2_LOG_TP_HD_FIELDS, +- (const char *)ngtcp2_encode_hex(cid, params->original_dcid.data, +- params->original_dcid.datalen)); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP ++ " original_destination_connection_id=0x%s", ++ ngtcp2_encode_hex_cstr(cid, params->original_dcid.data, ++ params->original_dcid.datalen)); + } + + if (params->retry_scid_present) { +- log->log_printf( +- log->user_data, (NGTCP2_LOG_TP " retry_source_connection_id=0x%s"), +- NGTCP2_LOG_TP_HD_FIELDS, +- (const char *)ngtcp2_encode_hex(cid, params->retry_scid.data, +- params->retry_scid.datalen)); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP " retry_source_connection_id=0x%s", ++ ngtcp2_encode_hex_cstr(cid, params->retry_scid.data, ++ params->retry_scid.datalen)); + } + + if (params->initial_scid_present) { +- log->log_printf( +- log->user_data, (NGTCP2_LOG_TP " initial_source_connection_id=0x%s"), +- NGTCP2_LOG_TP_HD_FIELDS, +- (const char *)ngtcp2_encode_hex(cid, params->initial_scid.data, +- params->initial_scid.datalen)); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP " initial_source_connection_id=0x%s", ++ ngtcp2_encode_hex_cstr(cid, params->initial_scid.data, ++ params->initial_scid.datalen)); + } + +- log->log_printf( +- log->user_data, +- (NGTCP2_LOG_TP " initial_max_stream_data_bidi_local=%" PRIu64), +- NGTCP2_LOG_TP_HD_FIELDS, params->initial_max_stream_data_bidi_local); +- log->log_printf( +- log->user_data, +- (NGTCP2_LOG_TP " initial_max_stream_data_bidi_remote=%" PRIu64), +- NGTCP2_LOG_TP_HD_FIELDS, params->initial_max_stream_data_bidi_remote); +- log->log_printf(log->user_data, +- (NGTCP2_LOG_TP " initial_max_stream_data_uni=%" PRIu64), +- NGTCP2_LOG_TP_HD_FIELDS, params->initial_max_stream_data_uni); +- log->log_printf(log->user_data, (NGTCP2_LOG_TP " initial_max_data=%" PRIu64), +- NGTCP2_LOG_TP_HD_FIELDS, params->initial_max_data); +- log->log_printf(log->user_data, +- (NGTCP2_LOG_TP " initial_max_streams_bidi=%" PRIu64), +- NGTCP2_LOG_TP_HD_FIELDS, params->initial_max_streams_bidi); +- log->log_printf(log->user_data, +- (NGTCP2_LOG_TP " initial_max_streams_uni=%" PRIu64), +- NGTCP2_LOG_TP_HD_FIELDS, params->initial_max_streams_uni); +- log->log_printf(log->user_data, (NGTCP2_LOG_TP " max_idle_timeout=%" PRIu64), +- NGTCP2_LOG_TP_HD_FIELDS, +- params->max_idle_timeout / NGTCP2_MILLISECONDS); +- log->log_printf(log->user_data, +- (NGTCP2_LOG_TP " max_udp_payload_size=%" PRIu64), +- NGTCP2_LOG_TP_HD_FIELDS, params->max_udp_payload_size); +- log->log_printf(log->user_data, +- (NGTCP2_LOG_TP " ack_delay_exponent=%" PRIu64), +- NGTCP2_LOG_TP_HD_FIELDS, params->ack_delay_exponent); +- log->log_printf(log->user_data, (NGTCP2_LOG_TP " max_ack_delay=%" PRIu64), +- NGTCP2_LOG_TP_HD_FIELDS, +- params->max_ack_delay / NGTCP2_MILLISECONDS); +- log->log_printf(log->user_data, +- (NGTCP2_LOG_TP " active_connection_id_limit=%" PRIu64), +- NGTCP2_LOG_TP_HD_FIELDS, params->active_connection_id_limit); +- log->log_printf(log->user_data, +- (NGTCP2_LOG_TP " disable_active_migration=%d"), +- NGTCP2_LOG_TP_HD_FIELDS, params->disable_active_migration); +- log->log_printf(log->user_data, +- (NGTCP2_LOG_TP " max_datagram_frame_size=%" PRIu64), +- NGTCP2_LOG_TP_HD_FIELDS, params->max_datagram_frame_size); +- log->log_printf(log->user_data, (NGTCP2_LOG_TP " grease_quic_bit=%d"), +- NGTCP2_LOG_TP_HD_FIELDS, params->grease_quic_bit); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP ++ " initial_max_stream_data_bidi_local=%" PRIu64, ++ params->initial_max_stream_data_bidi_local); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP ++ " initial_max_stream_data_bidi_remote=%" PRIu64, ++ params->initial_max_stream_data_bidi_remote); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP " initial_max_stream_data_uni=%" PRIu64, ++ params->initial_max_stream_data_uni); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP " initial_max_data=%" PRIu64, ++ params->initial_max_data); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP " initial_max_streams_bidi=%" PRIu64, ++ params->initial_max_streams_bidi); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP " initial_max_streams_uni=%" PRIu64, ++ params->initial_max_streams_uni); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP " max_idle_timeout=%" PRIu64, ++ params->max_idle_timeout / NGTCP2_MILLISECONDS); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP " max_udp_payload_size=%" PRIu64, ++ params->max_udp_payload_size); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP " ack_delay_exponent=%" PRIu64, ++ params->ack_delay_exponent); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP " max_ack_delay=%" PRIu64, ++ params->max_ack_delay / NGTCP2_MILLISECONDS); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP " active_connection_id_limit=%" PRIu64, ++ params->active_connection_id_limit); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP " disable_active_migration=%d", ++ params->disable_active_migration); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP " max_datagram_frame_size=%" PRIu64, ++ params->max_datagram_frame_size); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP " grease_quic_bit=%d", ++ params->grease_quic_bit); + + if (params->version_info_present) { +- log->log_printf( +- log->user_data, +- (NGTCP2_LOG_TP " version_information.chosen_version=0x%08x"), +- NGTCP2_LOG_TP_HD_FIELDS, params->version_info.chosen_version); ++ ngtcp2_log_infof_raw(log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP ++ " version_information.chosen_version=0x%08x", ++ params->version_info.chosen_version); + + assert(!(params->version_info.available_versionslen & 0x3)); + +@@ -751,10 +700,10 @@ void ngtcp2_log_remote_tp(ngtcp2_log *log, + i += sizeof(uint32_t)) { + p = ngtcp2_get_uint32be(&version, p); + +- log->log_printf( +- log->user_data, +- (NGTCP2_LOG_TP " version_information.available_versions[%zu]=0x%08x"), +- NGTCP2_LOG_TP_HD_FIELDS, i >> 2, version); ++ ngtcp2_log_infof_raw( ++ log, NGTCP2_LOG_EVENT_CRY, ++ NGTCP2_LOG_TP " version_information.available_versions[%zu]=0x%08x", ++ i >> 2, version); + } + } + } +@@ -765,33 +714,33 @@ void ngtcp2_log_pkt_lost(ngtcp2_log *log, int64_t pkt_num, uint8_t type, + return; + } + +- ngtcp2_log_info(log, NGTCP2_LOG_EVENT_LDC, +- "pkn=%" PRId64 " lost type=%s sent_ts=%" PRIu64, pkt_num, +- strpkttype_type_flags(type, flags), sent_ts); ++ ngtcp2_log_infof(log, NGTCP2_LOG_EVENT_LDC, ++ "pkn=%" PRId64 " lost type=%s sent_ts=%" PRIu64, pkt_num, ++ strpkttype_type_flags(type, flags), sent_ts); + } + + static void log_pkt_hd(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const char *dir) { +- uint8_t dcid[sizeof(hd->dcid.data) * 2 + 1]; +- uint8_t scid[sizeof(hd->scid.data) * 2 + 1]; ++ char dcid[sizeof(hd->dcid.data) * 2 + 1]; ++ char scid[sizeof(hd->scid.data) * 2 + 1]; + + if (!log->log_printf || !(log->events & NGTCP2_LOG_EVENT_PKT)) { + return; + } + + if (hd->type == NGTCP2_PKT_1RTT) { +- ngtcp2_log_info( ++ ngtcp2_log_infof( + log, NGTCP2_LOG_EVENT_PKT, "%s pkn=%" PRId64 " dcid=0x%s type=%s k=%d", + dir, hd->pkt_num, +- (const char *)ngtcp2_encode_hex(dcid, hd->dcid.data, hd->dcid.datalen), ++ ngtcp2_encode_hex_cstr(dcid, hd->dcid.data, hd->dcid.datalen), + strpkttype(hd), (hd->flags & NGTCP2_PKT_FLAG_KEY_PHASE) != 0); + } else { +- ngtcp2_log_info( ++ ngtcp2_log_infof( + log, NGTCP2_LOG_EVENT_PKT, + "%s pkn=%" PRId64 " dcid=0x%s scid=0x%s version=0x%08x type=%s len=%zu", + dir, hd->pkt_num, +- (const char *)ngtcp2_encode_hex(dcid, hd->dcid.data, hd->dcid.datalen), +- (const char *)ngtcp2_encode_hex(scid, hd->scid.data, hd->scid.datalen), ++ ngtcp2_encode_hex_cstr(dcid, hd->dcid.data, hd->dcid.datalen), ++ ngtcp2_encode_hex_cstr(scid, hd->scid.data, hd->scid.datalen), + hd->version, strpkttype(hd), hd->len); + } + } +@@ -804,31 +753,6 @@ void ngtcp2_log_tx_pkt_hd(ngtcp2_log *log, const ngtcp2_pkt_hd *hd) { + log_pkt_hd(log, hd, "tx"); + } + +-void ngtcp2_log_info(ngtcp2_log *log, ngtcp2_log_event ev, const char *fmt, +- ...) { +- va_list ap; +- int n; +- char buf[NGTCP2_LOG_BUFLEN]; +- +- if (!log->log_printf || !(log->events & ev)) { +- return; +- } +- +- va_start(ap, fmt); +- n = vsnprintf(buf, sizeof(buf), fmt, ap); +- va_end(ap); +- +- if (n < 0 || (size_t)n >= sizeof(buf)) { +- return; +- } +- +- log->log_printf(log->user_data, (NGTCP2_LOG_HD " %s"), +- timestamp_cast(log->last_ts - log->ts), log->scid, +- strevent(ev), buf); +-} +- +-void ngtcp2_log_tx_cancel(ngtcp2_log *log, const ngtcp2_pkt_hd *hd) { +- ngtcp2_log_info(log, NGTCP2_LOG_EVENT_PKT, +- "cancel tx pkn=%" PRId64 " type=%s", hd->pkt_num, +- strpkttype(hd)); ++uint64_t ngtcp2_log_timestamp(const ngtcp2_log *log) { ++ return (log->last_ts - log->ts) / NGTCP2_MILLISECONDS; + } +diff --git a/third_party/ngtcp2/lib/ngtcp2_log.h b/third_party/ngtcp2/lib/ngtcp2_log.h +index 13fb81a72e1..b8bccb9d09e 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_log.h ++++ b/third_party/ngtcp2/lib/ngtcp2_log.h +@@ -49,7 +49,7 @@ typedef struct ngtcp2_log { + log_pritnf. */ + void *user_data; + /* scid is SCID encoded as NULL-terminated hex string. */ +- uint8_t scid[NGTCP2_MAX_CIDLEN * 2 + 1]; ++ char scid[NGTCP2_MAX_CIDLEN * 2 + 1]; + } ngtcp2_log; + + /** +@@ -107,7 +107,7 @@ void ngtcp2_log_tx_fr(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + void ngtcp2_log_rx_vn(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const uint32_t *sv, size_t nsv); + +-void ngtcp2_log_rx_sr(ngtcp2_log *log, const ngtcp2_pkt_stateless_reset *sr); ++void ngtcp2_log_rx_sr(ngtcp2_log *log, const ngtcp2_pkt_stateless_reset2 *sr); + + void ngtcp2_log_remote_tp(ngtcp2_log *log, + const ngtcp2_transport_params *params); +@@ -119,14 +119,71 @@ void ngtcp2_log_rx_pkt_hd(ngtcp2_log *log, const ngtcp2_pkt_hd *hd); + + void ngtcp2_log_tx_pkt_hd(ngtcp2_log *log, const ngtcp2_pkt_hd *hd); + +-void ngtcp2_log_tx_cancel(ngtcp2_log *log, const ngtcp2_pkt_hd *hd); ++#define NGTCP2_LOG_HD "I%08" PRIu64 " 0x%s %s" ++ ++uint64_t ngtcp2_log_timestamp(const ngtcp2_log *log); ++ ++static inline const char *ngtcp2_log_event_str(ngtcp2_log_event ev) { ++ switch (ev) { ++ case NGTCP2_LOG_EVENT_CON: ++ return "con"; ++ case NGTCP2_LOG_EVENT_PKT: ++ return "pkt"; ++ case NGTCP2_LOG_EVENT_FRM: ++ return "frm"; ++ case NGTCP2_LOG_EVENT_LDC: ++ return "ldc"; ++ case NGTCP2_LOG_EVENT_CRY: ++ return "cry"; ++ case NGTCP2_LOG_EVENT_PTV: ++ return "ptv"; ++ case NGTCP2_LOG_EVENT_CCA: ++ return "cca"; ++ case NGTCP2_LOG_EVENT_NONE: ++ default: ++ return "non"; ++ } ++} ++ ++#define ngtcp2_log_infof_raw(LOG, EV, FMT, ...) \ ++ (LOG)->log_printf((LOG)->user_data, NGTCP2_LOG_HD " " FMT, \ ++ ngtcp2_log_timestamp(LOG), (LOG)->scid, \ ++ ngtcp2_log_event_str(EV), __VA_ARGS__); + + /** + * @function + * +- * `ngtcp2_log_info` writes info level log. ++ * `ngtcp2_log_infof` writes info level log with printf like ++ * formatting. + */ +-void ngtcp2_log_info(ngtcp2_log *log, ngtcp2_log_event ev, const char *fmt, +- ...); ++#define ngtcp2_log_infof(LOG, EV, FMT, ...) \ ++ do { \ ++ if (!(LOG)->log_printf || !((LOG)->events & (EV))) { \ ++ break; \ ++ } \ ++ \ ++ ngtcp2_log_infof_raw((LOG), (EV), FMT, __VA_ARGS__); \ ++ } while (0) ++ ++#define ngtcp2_log_info_raw(LOG, EV, FMT) \ ++ (LOG)->log_printf((LOG)->user_data, NGTCP2_LOG_HD " " FMT, \ ++ ngtcp2_log_timestamp(LOG), (LOG)->scid, \ ++ ngtcp2_log_event_str(EV)) ++ ++/** ++ * @function ++ * ++ * `ngtcp2_log_info` writes info level log. FMT should not contain ++ * formatting directive. This function exists to workaround the issue ++ * that __VA_ARGS__ cannot be empty. ++ */ ++#define ngtcp2_log_info(LOG, EV, FMT) \ ++ do { \ ++ if (!(LOG)->log_printf || !((LOG)->events & (EV))) { \ ++ break; \ ++ } \ ++ \ ++ ngtcp2_log_info_raw((LOG), (EV), FMT); \ ++ } while (0) + + #endif /* !defined(NGTCP2_LOG_H) */ +diff --git a/third_party/ngtcp2/lib/ngtcp2_macro.h b/third_party/ngtcp2/lib/ngtcp2_macro.h +index dfe5e0aed22..12cba12719a 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_macro.h ++++ b/third_party/ngtcp2/lib/ngtcp2_macro.h +@@ -52,6 +52,13 @@ + */ + #define ngtcp2_arraylen(A) (sizeof(A) / sizeof(A[0])) + ++/* ++ * ngtcp2_strlen_lit returns the length of string literal |S|. This ++ * macro assumes |S| is NULL-terminated string literal. It must not ++ * be used with pointers. ++ */ ++#define ngtcp2_strlen_lit(S) (sizeof(S) - 1) ++ + #define ngtcp2_max_def(SUFFIX, T) \ + static inline T ngtcp2_max_##SUFFIX(T a, T b) { return a < b ? b : a; } + +diff --git a/third_party/ngtcp2/lib/ngtcp2_map.c b/third_party/ngtcp2/lib/ngtcp2_map.c +index 5e4726e63ff..3ad4b9f1ef0 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_map.c ++++ b/third_party/ngtcp2/lib/ngtcp2_map.c +@@ -33,11 +33,11 @@ + + #define NGTCP2_INITIAL_HASHBITS 4 + +-void ngtcp2_map_init(ngtcp2_map *map, const ngtcp2_mem *mem) { +- map->mem = mem; +- map->hashbits = 0; +- map->table = NULL; +- map->size = 0; ++void ngtcp2_map_init(ngtcp2_map *map, uint64_t seed, const ngtcp2_mem *mem) { ++ *map = (ngtcp2_map){ ++ .mem = mem, ++ .seed = seed, ++ }; + } + + void ngtcp2_map_free(ngtcp2_map *map) { +@@ -45,30 +45,27 @@ void ngtcp2_map_free(ngtcp2_map *map) { + return; + } + +- ngtcp2_mem_free(map->mem, map->table); ++ ngtcp2_mem_free(map->mem, map->keys); + } + + int ngtcp2_map_each(const ngtcp2_map *map, int (*func)(void *data, void *ptr), + void *ptr) { + int rv; + size_t i; +- ngtcp2_map_bucket *bkt; + size_t tablelen; + + if (map->size == 0) { + return 0; + } + +- tablelen = 1u << map->hashbits; ++ tablelen = (size_t)1 << map->hashbits; + + for (i = 0; i < tablelen; ++i) { +- bkt = &map->table[i]; +- +- if (bkt->data == NULL) { ++ if (map->psl[i] == 0) { + continue; + } + +- rv = func(bkt->data, ptr); ++ rv = func(map->data[i], ptr); + if (rv != 0) { + return rv; + } +@@ -77,168 +74,228 @@ int ngtcp2_map_each(const ngtcp2_map *map, int (*func)(void *data, void *ptr), + return 0; + } + +-static size_t hash(ngtcp2_map_key_type key, size_t bits) { +- return (size_t)((key * 11400714819323198485llu) >> (64 - bits)); +-} +- +-static void map_bucket_swap(ngtcp2_map_bucket *a, ngtcp2_map_bucket *b) { +- ngtcp2_map_bucket c = *a; +- +- *a = *b; +- *b = c; ++/* Hasher from ++ https://github.com/rust-lang/rustc-hash/blob/dc5c33f1283de2da64d8d7a06401d91aded03ad4/src/lib.rs ++ to maximize the output's sensitivity to all input bits. */ ++#define NGTCP2_MAP_HASHER 0xF1357AEA2E62A9C5ULL ++/* 64-bit Fibonacci hashing constant, Golden Ratio constant, to get ++ the high bits with the good distribution. */ ++#define NGTCP2_MAP_FIBO 0x9E3779B97F4A7C15ULL ++ ++static size_t map_index(const ngtcp2_map *map, ngtcp2_map_key_type key) { ++ key += map->seed; ++ key *= NGTCP2_MAP_HASHER; ++ return (size_t)((key * NGTCP2_MAP_FIBO) >> (64 - map->hashbits)); + } + + #ifndef WIN32 + void ngtcp2_map_print_distance(const ngtcp2_map *map) { + size_t i; + size_t idx; +- ngtcp2_map_bucket *bkt; + size_t tablelen; + + if (map->size == 0) { + return; + } + +- tablelen = 1u << map->hashbits; ++ tablelen = (size_t)1 << map->hashbits; + + for (i = 0; i < tablelen; ++i) { +- bkt = &map->table[i]; +- +- if (bkt->data == NULL) { ++ if (map->psl[i] == 0) { + fprintf(stderr, "@%zu \n", i); + continue; + } + +- idx = hash(bkt->key, map->hashbits); +- fprintf(stderr, "@%zu hash=%zu key=%" PRIu64 " base=%zu distance=%u\n", i, +- hash(bkt->key, map->hashbits), bkt->key, idx, bkt->psl); ++ idx = map_index(map, map->keys[i]); ++ fprintf(stderr, "@%zu key=%" PRIu64 " base=%zu distance=%u\n", i, ++ map->keys[i], idx, map->psl[i] - 1); + } + } + #endif /* !defined(WIN32) */ + +-static int insert(ngtcp2_map_bucket *table, size_t hashbits, +- ngtcp2_map_key_type key, void *data) { +- size_t idx = hash(key, hashbits); +- ngtcp2_map_bucket b = { +- .key = key, +- .data = data, +- }; +- ngtcp2_map_bucket *bkt; +- size_t mask = (1u << hashbits) - 1; ++static void map_set_entry(ngtcp2_map *map, size_t idx, ngtcp2_map_key_type key, ++ void *data, size_t psl) { ++ map->keys[idx] = key; ++ map->data[idx] = data; ++ map->psl[idx] = (uint8_t)psl; ++} ++ ++#define NGTCP2_SWAP(TYPE, A, B) \ ++ do { \ ++ TYPE t = (TYPE) * (A); \ ++ \ ++ *(A) = *(B); \ ++ *(B) = t; \ ++ } while (0) ++ ++/* ++ * map_insert inserts |key| and |data| to |map|, and returns the index ++ * where the pair is stored if it succeeds. Otherwise, it returns one ++ * of the following negative error codes: ++ * ++ * NGTCP2_ERR_INVALID_ARGUMENT ++ * The another data associated to |key| is already present. ++ */ ++static ngtcp2_ssize map_insert(ngtcp2_map *map, ngtcp2_map_key_type key, ++ void *data) { ++ size_t idx = map_index(map, key); ++ size_t mask = ((size_t)1 << map->hashbits) - 1; ++ size_t psl = 1; ++ size_t kpsl; + + for (;;) { +- bkt = &table[idx]; ++ kpsl = map->psl[idx]; + +- if (bkt->data == NULL) { +- *bkt = b; +- return 0; ++ if (kpsl == 0) { ++ map_set_entry(map, idx, key, data, psl); ++ ++map->size; ++ ++ return (ngtcp2_ssize)idx; + } + +- if (b.psl > bkt->psl) { +- map_bucket_swap(bkt, &b); +- } else if (bkt->key == key) { +- /* TODO This check is just a waste after first swap or if this +- function is called from map_resize. That said, there is no +- difference with or without this conditional in performance +- wise. */ ++ if (psl > kpsl) { ++ NGTCP2_SWAP(ngtcp2_map_key_type, &key, &map->keys[idx]); ++ NGTCP2_SWAP(void *, &data, &map->data[idx]); ++ NGTCP2_SWAP(uint8_t, &psl, &map->psl[idx]); ++ } else if (map->keys[idx] == key) { ++ /* This check ensures that no duplicate keys are inserted. But ++ it is just a waste after first swap or if this function is ++ called from map_resize. That said, there is no difference ++ with or without this conditional in performance wise. */ + return NGTCP2_ERR_INVALID_ARGUMENT; + } + +- ++b.psl; ++ ++psl; + idx = (idx + 1) & mask; + } + } + ++/* NGTCP2_MAP_MAX_HASHBITS is the maximum number of bits used for hash ++ table. The theoretical limit of the maximum number of keys that ++ can be stored is 1 << NGTCP2_MAP_MAX_HASHBITS. */ ++#define NGTCP2_MAP_MAX_HASHBITS (sizeof(size_t) * 8 - 1) ++ + static int map_resize(ngtcp2_map *map, size_t new_hashbits) { + size_t i; +- ngtcp2_map_bucket *new_table; +- ngtcp2_map_bucket *bkt; + size_t tablelen; +- int rv; +- (void)rv; ++ ngtcp2_ssize idx; ++ ngtcp2_map new_map = { ++ .mem = map->mem, ++ .seed = map->seed, ++ .hashbits = new_hashbits, ++ }; ++ void *buf; ++ (void)idx; ++ ++ if (new_hashbits > NGTCP2_MAP_MAX_HASHBITS) { ++ return NGTCP2_ERR_NOMEM; ++ } ++ ++ tablelen = (size_t)1 << new_hashbits; + +- new_table = +- ngtcp2_mem_calloc(map->mem, 1u << new_hashbits, sizeof(ngtcp2_map_bucket)); +- if (new_table == NULL) { ++ buf = ngtcp2_mem_calloc(map->mem, tablelen, ++ sizeof(ngtcp2_map_key_type) + sizeof(void *) + ++ sizeof(uint8_t)); ++ if (buf == NULL) { + return NGTCP2_ERR_NOMEM; + } + ++ new_map.keys = buf; ++ new_map.data = ++ (void *)((uint8_t *)new_map.keys + tablelen * sizeof(ngtcp2_map_key_type)); ++ new_map.psl = (uint8_t *)new_map.data + tablelen * sizeof(void *); ++ + if (map->size) { +- tablelen = 1u << map->hashbits; ++ tablelen = (size_t)1 << map->hashbits; + + for (i = 0; i < tablelen; ++i) { +- bkt = &map->table[i]; +- if (bkt->data == NULL) { ++ if (map->psl[i] == 0) { + continue; + } + +- rv = insert(new_table, new_hashbits, bkt->key, bkt->data); ++ idx = map_insert(&new_map, map->keys[i], map->data[i]); + +- assert(0 == rv); ++ /* map_insert must not fail because all keys are unique during ++ resize. */ ++ assert(idx >= 0); + } + } + +- ngtcp2_mem_free(map->mem, map->table); ++ ngtcp2_mem_free(map->mem, map->keys); ++ map->keys = new_map.keys; ++ map->data = new_map.data; ++ map->psl = new_map.psl; + map->hashbits = new_hashbits; +- map->table = new_table; + + return 0; + } + ++/* NGTCP2_MAX_PSL_RESIZE_THRESH is the maximum psl threshold. If ++ reached, resize the table. */ ++#define NGTCP2_MAX_PSL_RESIZE_THRESH 128 ++ + int ngtcp2_map_insert(ngtcp2_map *map, ngtcp2_map_key_type key, void *data) { + int rv; ++ size_t tablelen; ++ ngtcp2_ssize idx; + + assert(data); + +- /* Load factor is 0.75 */ +- /* Under the very initial condition, that is map->size == 0 and +- map->hashbits == 0, 4 > 3 still holds nicely. */ +- if ((map->size + 1) * 4 > (1u << map->hashbits) * 3) { +- if (map->hashbits) { +- rv = map_resize(map, map->hashbits + 1); +- if (rv != 0) { +- return rv; +- } +- } else { +- rv = map_resize(map, NGTCP2_INITIAL_HASHBITS); +- if (rv != 0) { +- return rv; +- } ++ /* tablelen is incorrect if map->hashbits == 0 which leads to ++ tablelen = 1, but it is only used to check the load factor, and ++ it works in this special case. */ ++ tablelen = (size_t)1 << map->hashbits; ++ ++ /* Load factor is 7 / 8. Because tablelen is power of 2, (tablelen ++ - (tablelen >> 3)) computes tablelen * 7 / 8. */ ++ if (map->size + 1 >= (tablelen - (tablelen >> 3))) { ++ rv = map_resize(map, map->hashbits ? map->hashbits + 1 ++ : NGTCP2_INITIAL_HASHBITS); ++ if (rv != 0) { ++ return rv; + } ++ ++ idx = map_insert(map, key, data); ++ if (idx < 0) { ++ return (int)idx; ++ } ++ ++ return 0; + } + +- rv = insert(map->table, map->hashbits, key, data); +- if (rv != 0) { +- return rv; ++ idx = map_insert(map, key, data); ++ if (idx < 0) { ++ return (int)idx; + } + +- ++map->size; ++ /* Resize if psl reaches really large value which is almost ++ improbable, but just in case. */ ++ if (map->psl[idx] - 1 < NGTCP2_MAX_PSL_RESIZE_THRESH) { ++ return 0; ++ } + +- return 0; ++ return map_resize(map, map->hashbits + 1); + } + + void *ngtcp2_map_find(const ngtcp2_map *map, ngtcp2_map_key_type key) { + size_t idx; +- ngtcp2_map_bucket *bkt; +- size_t psl = 0; ++ size_t psl = 1; + size_t mask; + + if (map->size == 0) { + return NULL; + } + +- idx = hash(key, map->hashbits); +- mask = (1u << map->hashbits) - 1; ++ idx = map_index(map, key); ++ mask = ((size_t)1 << map->hashbits) - 1; + + for (;;) { +- bkt = &map->table[idx]; +- +- if (bkt->data == NULL || psl > bkt->psl) { ++ if (psl > map->psl[idx]) { + return NULL; + } + +- if (bkt->key == key) { +- return bkt->data; ++ if (map->keys[idx] == key) { ++ return map->data[idx]; + } + + ++psl; +@@ -248,38 +305,36 @@ void *ngtcp2_map_find(const ngtcp2_map *map, ngtcp2_map_key_type key) { + + int ngtcp2_map_remove(ngtcp2_map *map, ngtcp2_map_key_type key) { + size_t idx; +- ngtcp2_map_bucket *b, *bkt; +- size_t psl = 0; ++ size_t dest; ++ size_t psl = 1, kpsl; + size_t mask; + + if (map->size == 0) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + +- idx = hash(key, map->hashbits); +- mask = (1u << map->hashbits) - 1; ++ idx = map_index(map, key); ++ mask = ((size_t)1 << map->hashbits) - 1; + + for (;;) { +- bkt = &map->table[idx]; +- +- if (bkt->data == NULL || psl > bkt->psl) { ++ if (psl > map->psl[idx]) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + +- if (bkt->key == key) { +- b = bkt; ++ if (map->keys[idx] == key) { ++ dest = idx; + idx = (idx + 1) & mask; + + for (;;) { +- bkt = &map->table[idx]; +- if (bkt->data == NULL || bkt->psl == 0) { +- b->data = NULL; ++ kpsl = map->psl[idx]; ++ if (kpsl <= 1) { ++ map->psl[dest] = 0; + break; + } + +- --bkt->psl; +- *b = *bkt; +- b = bkt; ++ map_set_entry(map, dest, map->keys[idx], map->data[idx], kpsl - 1); ++ ++ dest = idx; + + idx = (idx + 1) & mask; + } +@@ -299,7 +354,7 @@ void ngtcp2_map_clear(ngtcp2_map *map) { + return; + } + +- memset(map->table, 0, sizeof(*map->table) * (1u << map->hashbits)); ++ memset(map->psl, 0, sizeof(*map->psl) * ((size_t)1 << map->hashbits)); + map->size = 0; + } + +diff --git a/third_party/ngtcp2/lib/ngtcp2_map.h b/third_party/ngtcp2/lib/ngtcp2_map.h +index 9d882fb2008..1afe3167e65 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_map.h ++++ b/third_party/ngtcp2/lib/ngtcp2_map.h +@@ -38,15 +38,15 @@ + + typedef uint64_t ngtcp2_map_key_type; + +-typedef struct ngtcp2_map_bucket { +- uint32_t psl; +- ngtcp2_map_key_type key; +- void *data; +-} ngtcp2_map_bucket; +- + typedef struct ngtcp2_map { +- ngtcp2_map_bucket *table; ++ ngtcp2_map_key_type *keys; ++ void **data; ++ /* psl is the Probe Sequence Length. 0 has special meaning that the ++ element is not stored at i-th position if psl[i] == 0. Because ++ of this, the actual psl value is psl[i] - 1 if psl[i] > 0. */ ++ uint8_t *psl; + const ngtcp2_mem *mem; ++ uint64_t seed; + size_t size; + size_t hashbits; + } ngtcp2_map; +@@ -54,7 +54,7 @@ typedef struct ngtcp2_map { + /* + * ngtcp2_map_init initializes the map |map|. + */ +-void ngtcp2_map_init(ngtcp2_map *map, const ngtcp2_mem *mem); ++void ngtcp2_map_init(ngtcp2_map *map, uint64_t seed, const ngtcp2_mem *mem); + + /* + * ngtcp2_map_free deallocates any resources allocated for |map|. The +diff --git a/third_party/ngtcp2/lib/ngtcp2_net.h b/third_party/ngtcp2/lib/ngtcp2_net.h +index 103a2fb2d80..35a807f9fed 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_net.h ++++ b/third_party/ngtcp2/lib/ngtcp2_net.h +@@ -97,9 +97,9 @@ STIN uint32_t ngtcp2_htonl(uint32_t hostlong) { + uint32_t res; + unsigned char *p = (unsigned char *)&res; + *p++ = (unsigned char)(hostlong >> 24); +- *p++ = (hostlong >> 16) & 0xffu; +- *p++ = (hostlong >> 8) & 0xffu; +- *p = hostlong & 0xffu; ++ *p++ = (hostlong >> 16) & 0xFFU; ++ *p++ = (hostlong >> 8) & 0xFFU; ++ *p = hostlong & 0xFFU; + return res; + } + +@@ -107,7 +107,7 @@ STIN uint16_t ngtcp2_htons(uint16_t hostshort) { + uint16_t res; + unsigned char *p = (unsigned char *)&res; + *p++ = (unsigned char)(hostshort >> 8); +- *p = hostshort & 0xffu; ++ *p = hostshort & 0xFFU; + return res; + } + +diff --git a/third_party/ngtcp2/lib/ngtcp2_objalloc.h b/third_party/ngtcp2/lib/ngtcp2_objalloc.h +index cf23de7b2b7..4b6df53857a 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_objalloc.h ++++ b/third_party/ngtcp2/lib/ngtcp2_objalloc.h +@@ -69,7 +69,7 @@ void ngtcp2_objalloc_clear(ngtcp2_objalloc *objalloc); + inline static void ngtcp2_objalloc_##NAME##_init( \ + ngtcp2_objalloc *objalloc, size_t nmemb, const ngtcp2_mem *mem) { \ + ngtcp2_objalloc_init( \ +- objalloc, ((sizeof(TYPE) + 0xfu) & ~(uintptr_t)0xfu) * nmemb, mem); \ ++ objalloc, ((sizeof(TYPE) + 0xFU) & ~(uintptr_t)0xFU) * nmemb, mem); \ + } \ + \ + TYPE *ngtcp2_objalloc_##NAME##_get(ngtcp2_objalloc *objalloc); \ +@@ -123,7 +123,7 @@ void ngtcp2_objalloc_clear(ngtcp2_objalloc *objalloc); + inline static void ngtcp2_objalloc_##NAME##_init( \ + ngtcp2_objalloc *objalloc, size_t nmemb, const ngtcp2_mem *mem) { \ + ngtcp2_objalloc_init( \ +- objalloc, ((sizeof(TYPE) + 0xfu) & ~(uintptr_t)0xfu) * nmemb, mem); \ ++ objalloc, ((sizeof(TYPE) + 0xFU) & ~(uintptr_t)0xFU) * nmemb, mem); \ + } \ + \ + inline static TYPE *ngtcp2_objalloc_##NAME##_get( \ +diff --git a/third_party/ngtcp2/lib/ngtcp2_path.c b/third_party/ngtcp2/lib/ngtcp2_path.c +index 83238730033..c9636a8db9a 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_path.c ++++ b/third_party/ngtcp2/lib/ngtcp2_path.c +@@ -28,12 +28,6 @@ + + #include "ngtcp2_addr.h" + +-void ngtcp2_path_init(ngtcp2_path *path, const ngtcp2_addr *local, +- const ngtcp2_addr *remote) { +- path->local = *local; +- path->remote = *remote; +-} +- + void ngtcp2_path_copy(ngtcp2_path *dest, const ngtcp2_path *src) { + ngtcp2_addr_copy(&dest->local, &src->local); + ngtcp2_addr_copy(&dest->remote, &src->remote); +diff --git a/third_party/ngtcp2/lib/ngtcp2_path.h b/third_party/ngtcp2/lib/ngtcp2_path.h +index a708378db32..9d4205cee22 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_path.h ++++ b/third_party/ngtcp2/lib/ngtcp2_path.h +@@ -31,14 +31,6 @@ + + #include + +-/* +- * ngtcp2_path_init initializes |path| with the given addresses. Note +- * that the buffer pointed by local->addr and remote->addr are not +- * copied. Their pointer values are assigned instead. +- */ +-void ngtcp2_path_init(ngtcp2_path *path, const ngtcp2_addr *local, +- const ngtcp2_addr *remote); +- + /* + * ngtcp2_path_storage_init2 initializes |ps| using |path| as initial + * data. +diff --git a/third_party/ngtcp2/lib/ngtcp2_pcg.c b/third_party/ngtcp2/lib/ngtcp2_pcg.c +new file mode 100644 +index 00000000000..ddaf94a779a +--- /dev/null ++++ b/third_party/ngtcp2/lib/ngtcp2_pcg.c +@@ -0,0 +1,88 @@ ++/* ++ * ngtcp2 ++ * ++ * Copyright (c) 2025 ngtcp2 contributors ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ */ ++#include "ngtcp2_pcg.h" ++ ++#include ++ ++/* ++ * PCG implementation from ++ * https://github.com/imneme/pcg-c/blob/83252d9c23df9c82ecb42210afed61a7b42402d7/include/pcg_variants.h ++ * ++ * PCG Random Number Generation for C. ++ * ++ * Copyright 2014-2019 Melissa O'Neill , ++ * and the PCG Project contributors. ++ * ++ * SPDX-License-Identifier: (Apache-2.0 OR MIT) ++ * ++ * Licensed under the Apache License, Version 2.0 (provided in ++ * LICENSE-APACHE.txt and at http://www.apache.org/licenses/LICENSE-2.0) ++ * or under the MIT license (provided in LICENSE-MIT.txt and at ++ * http://opensource.org/licenses/MIT), at your option. This file may not ++ * be copied, modified, or distributed except according to those terms. ++ * ++ * Distributed on an "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, either ++ * express or implied. See your chosen license for details. ++ * ++ * For additional information about the PCG random number generation scheme, ++ * visit http://www.pcg-random.org/. ++ */ ++ ++#define NGTCP2_PCG_DEFAULT_MULTIPLIER_64 6364136223846793005ULL ++#define NGTCP2_PCG_DEFAULT_INCREMENT_64 1442695040888963407ULL ++ ++static void pcg_oneseq_64_step_r(ngtcp2_pcg32 *pcg) { ++ pcg->state = pcg->state * NGTCP2_PCG_DEFAULT_MULTIPLIER_64 + ++ NGTCP2_PCG_DEFAULT_INCREMENT_64; ++} ++ ++void ngtcp2_pcg32_init(ngtcp2_pcg32 *pcg, uint64_t seed) { ++ pcg->state = 0; ++ pcg_oneseq_64_step_r(pcg); ++ pcg->state += seed; ++ pcg_oneseq_64_step_r(pcg); ++} ++ ++static uint32_t pcg_rotr_32(uint32_t value, unsigned int rot) { ++ return (value >> rot) | (value << ((32 - rot) & 31)); ++} ++ ++static uint32_t pcg_output_xsh_rr_64_32(uint64_t state) { ++ return pcg_rotr_32((uint32_t)(((state >> 18U) ^ state) >> 27U), ++ (unsigned int)(state >> 59U)); ++} ++ ++uint32_t ngtcp2_pcg32_rand(ngtcp2_pcg32 *pcg) { ++ uint64_t oldstate = pcg->state; ++ ++ pcg_oneseq_64_step_r(pcg); ++ ++ return pcg_output_xsh_rr_64_32(oldstate); ++} ++ ++uint32_t ngtcp2_pcg32_rand_n(ngtcp2_pcg32 *pcg, uint32_t n) { ++ assert(n); ++ return (uint32_t)(((uint64_t)ngtcp2_pcg32_rand(pcg) * n) >> 32); ++} +diff --git a/third_party/ngtcp2/lib/ngtcp2_pcg.h b/third_party/ngtcp2/lib/ngtcp2_pcg.h +new file mode 100644 +index 00000000000..a637183efc1 +--- /dev/null ++++ b/third_party/ngtcp2/lib/ngtcp2_pcg.h +@@ -0,0 +1,54 @@ ++/* ++ * ngtcp2 ++ * ++ * Copyright (c) 2025 ngtcp2 contributors ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ */ ++#ifndef NGTCP2_PCG_H ++#define NGTCP2_PCG_H ++ ++#ifdef HAVE_CONFIG_H ++# include ++#endif /* defined(HAVE_CONFIG_H) */ ++ ++#include ++ ++typedef struct ngtcp2_pcg32 { ++ uint64_t state; ++} ngtcp2_pcg32; ++ ++/* ++ * ngtcp2_pcg32_init initializes |pcg| with |seed|. ++ */ ++void ngtcp2_pcg32_init(ngtcp2_pcg32 *pcg, uint64_t seed); ++ ++/* ++ * ngtcp2_pcg32_rand returns a random value in [0, UINT32_MAX]. ++ */ ++uint32_t ngtcp2_pcg32_rand(ngtcp2_pcg32 *pcg); ++ ++/* ++ * ngtcp2_pcg32_rand_n returns a random value in [0, n). |n| must not ++ * be zero. ++ */ ++uint32_t ngtcp2_pcg32_rand_n(ngtcp2_pcg32 *pcg, uint32_t n); ++ ++#endif /* !defined(NGTCP2_PCG_H) */ +diff --git a/third_party/ngtcp2/lib/ngtcp2_pkt.c b/third_party/ngtcp2/lib/ngtcp2_pkt.c +index af8a059d08f..f86e65118df 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_pkt.c ++++ b/third_party/ngtcp2/lib/ngtcp2_pkt.c +@@ -33,7 +33,9 @@ + #include "ngtcp2_cid.h" + #include "ngtcp2_mem.h" + #include "ngtcp2_vec.h" ++#include "ngtcp2_buf.h" + #include "ngtcp2_unreachable.h" ++#include "ngtcp2_pcg.h" + + int ngtcp2_pkt_chain_new(ngtcp2_pkt_chain **ppc, const ngtcp2_path *path, + const ngtcp2_pkt_info *pi, const uint8_t *pkt, +@@ -480,8 +482,10 @@ ngtcp2_ssize ngtcp2_pkt_encode_hd_short(uint8_t *out, size_t outlen, + return (ngtcp2_ssize)len; + } + +-ngtcp2_ssize ngtcp2_pkt_decode_frame(ngtcp2_frame *dest, const uint8_t *payload, +- size_t payloadlen) { ++ngtcp2_ssize ngtcp2_frame_decoder_decode(ngtcp2_frame_decoder *frd, ++ ngtcp2_frame *dest, ++ const uint8_t *payload, ++ size_t payloadlen) { + uint8_t type; + + if (payloadlen == 0) { +@@ -530,6 +534,7 @@ ngtcp2_ssize ngtcp2_pkt_decode_frame(ngtcp2_frame *dest, const uint8_t *payload, + payloadlen); + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: ++ dest->ack.ranges = frd->buf.ack_ranges; + return ngtcp2_pkt_decode_ack_frame(&dest->ack, payload, payloadlen); + case NGTCP2_FRAME_PATH_CHALLENGE: + return ngtcp2_pkt_decode_path_challenge_frame(&dest->path_challenge, +@@ -538,6 +543,7 @@ ngtcp2_ssize ngtcp2_pkt_decode_frame(ngtcp2_frame *dest, const uint8_t *payload, + return ngtcp2_pkt_decode_path_response_frame(&dest->path_response, payload, + payloadlen); + case NGTCP2_FRAME_CRYPTO: ++ dest->stream.data = &frd->buf.data; + return ngtcp2_pkt_decode_crypto_frame(&dest->stream, payload, payloadlen); + case NGTCP2_FRAME_NEW_TOKEN: + return ngtcp2_pkt_decode_new_token_frame(&dest->new_token, payload, +@@ -550,14 +556,16 @@ ngtcp2_ssize ngtcp2_pkt_decode_frame(ngtcp2_frame *dest, const uint8_t *payload, + payload, payloadlen); + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: ++ dest->datagram.data = &frd->buf.data; + return ngtcp2_pkt_decode_datagram_frame(&dest->datagram, payload, + payloadlen); + default: + if ((type & ~(NGTCP2_FRAME_STREAM - 1)) == NGTCP2_FRAME_STREAM) { ++ dest->stream.data = &frd->buf.data; + return ngtcp2_pkt_decode_stream_frame(&dest->stream, payload, payloadlen); + } + +- /* For frame types > 0xff, use ngtcp2_get_uvarintlen and ++ /* For frame types > 0xFF, use ngtcp2_get_uvarintlen and + ngtcp2_get_uvarint to get a frame type, and then switch over + it. Verify that payloadlen >= ngtcp2_get_uvarintlen(payload) + before calling ngtcp2_get_uvarint(payload). */ +@@ -916,7 +924,7 @@ ngtcp2_ssize ngtcp2_pkt_decode_connection_close_frame( + return NGTCP2_ERR_FRAME_ENCODING; + } + +- p = ngtcp2_get_uvarint(&vi, p); ++ ngtcp2_get_uvarint(&vi, p); + if (payloadlen - len < vi) { + return NGTCP2_ERR_FRAME_ENCODING; + } +@@ -1051,6 +1059,8 @@ ngtcp2_ssize ngtcp2_pkt_decode_ping_frame(ngtcp2_ping *dest, + (void)payload; + (void)payloadlen; + ++ assert(payloadlen > 0); ++ + dest->type = NGTCP2_FRAME_PING; + return 1; + } +@@ -1198,8 +1208,7 @@ ngtcp2_ssize ngtcp2_pkt_decode_new_connection_id_frame( + ++p; + ngtcp2_cid_init(&dest->cid, p, cil); + p += cil; +- p = ngtcp2_get_bytes(dest->stateless_reset_token, p, +- NGTCP2_STATELESS_RESET_TOKENLEN); ++ p = ngtcp2_get_bytes(dest->token.data, p, sizeof(dest->token.data)); + + assert((size_t)(p - payload) == len); + +@@ -1258,7 +1267,7 @@ ngtcp2_ssize ngtcp2_pkt_decode_path_challenge_frame(ngtcp2_path_challenge *dest, + p = payload + 1; + + dest->type = NGTCP2_FRAME_PATH_CHALLENGE; +- ngtcp2_cpymem(dest->data, p, sizeof(dest->data)); ++ ngtcp2_cpymem(dest->data.data, p, sizeof(dest->data.data)); + p += sizeof(dest->data); + + assert((size_t)(p - payload) == len); +@@ -1279,7 +1288,7 @@ ngtcp2_ssize ngtcp2_pkt_decode_path_response_frame(ngtcp2_path_response *dest, + p = payload + 1; + + dest->type = NGTCP2_FRAME_PATH_RESPONSE; +- ngtcp2_cpymem(dest->data, p, sizeof(dest->data)); ++ ngtcp2_cpymem(dest->data.data, p, sizeof(dest->data.data)); + p += sizeof(dest->data); + + assert((size_t)(p - payload) == len); +@@ -1319,7 +1328,7 @@ ngtcp2_ssize ngtcp2_pkt_decode_crypto_frame(ngtcp2_stream *dest, + return NGTCP2_ERR_FRAME_ENCODING; + } + +- p = ngtcp2_get_uvarint(&vi, p); ++ ngtcp2_get_uvarint(&vi, p); + if (payloadlen - len < vi) { + return NGTCP2_ERR_FRAME_ENCODING; + } +@@ -1426,6 +1435,8 @@ ngtcp2_ssize ngtcp2_pkt_decode_handshake_done_frame(ngtcp2_handshake_done *dest, + (void)payload; + (void)payloadlen; + ++ assert(payloadlen > 0); ++ + dest->type = NGTCP2_FRAME_HANDSHAKE_DONE; + return 1; + } +@@ -1440,9 +1451,7 @@ ngtcp2_ssize ngtcp2_pkt_decode_datagram_frame(ngtcp2_datagram *dest, + size_t n; + uint64_t vi; + +- if (payloadlen < len) { +- return NGTCP2_ERR_FRAME_ENCODING; +- } ++ assert(payloadlen > 0); + + type = payload[0]; + +@@ -1481,16 +1490,13 @@ ngtcp2_ssize ngtcp2_pkt_decode_datagram_frame(ngtcp2_datagram *dest, + + dest->type = type; + +- if (datalen == 0) { +- dest->datacnt = 0; +- dest->data = NULL; +- } else { ++ if (datalen) { ++ dest->data[0].len = datalen; ++ dest->data[0].base = (uint8_t *)p; + dest->datacnt = 1; +- dest->data = dest->rdata; +- dest->rdata[0].len = datalen; +- +- dest->rdata[0].base = (uint8_t *)p; + p += datalen; ++ } else { ++ dest->datacnt = 0; + } + + assert((size_t)(p - payload) == len); +@@ -1500,7 +1506,7 @@ ngtcp2_ssize ngtcp2_pkt_decode_datagram_frame(ngtcp2_datagram *dest, + + ngtcp2_ssize ngtcp2_pkt_encode_frame(uint8_t *out, size_t outlen, + ngtcp2_frame *fr) { +- switch (fr->type) { ++ switch (fr->hd.type) { + case NGTCP2_FRAME_STREAM: + return ngtcp2_pkt_encode_stream_frame(out, outlen, &fr->stream); + case NGTCP2_FRAME_ACK: +@@ -1876,7 +1882,7 @@ ngtcp2_pkt_encode_new_connection_id_frame(uint8_t *out, size_t outlen, + const ngtcp2_new_connection_id *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen(fr->seq) + + ngtcp2_put_uvarintlen(fr->retire_prior_to) + 1 + +- fr->cid.datalen + NGTCP2_STATELESS_RESET_TOKENLEN; ++ fr->cid.datalen + sizeof(fr->token.data); + uint8_t *p; + + if (outlen < len) { +@@ -1890,8 +1896,7 @@ ngtcp2_pkt_encode_new_connection_id_frame(uint8_t *out, size_t outlen, + p = ngtcp2_put_uvarint(p, fr->retire_prior_to); + *p++ = (uint8_t)fr->cid.datalen; + p = ngtcp2_cpymem(p, fr->cid.data, fr->cid.datalen); +- p = ngtcp2_cpymem(p, fr->stateless_reset_token, +- NGTCP2_STATELESS_RESET_TOKENLEN); ++ p = ngtcp2_cpymem(p, fr->token.data, sizeof(fr->token.data)); + + assert((size_t)(p - out) == len); + +@@ -1933,7 +1938,7 @@ ngtcp2_pkt_encode_path_challenge_frame(uint8_t *out, size_t outlen, + p = out; + + *p++ = NGTCP2_FRAME_PATH_CHALLENGE; +- p = ngtcp2_cpymem(p, fr->data, sizeof(fr->data)); ++ p = ngtcp2_cpymem(p, fr->data.data, sizeof(fr->data.data)); + + assert((size_t)(p - out) == len); + +@@ -1953,7 +1958,7 @@ ngtcp2_pkt_encode_path_response_frame(uint8_t *out, size_t outlen, + p = out; + + *p++ = NGTCP2_FRAME_PATH_RESPONSE; +- p = ngtcp2_cpymem(p, fr->data, sizeof(fr->data)); ++ p = ngtcp2_cpymem(p, fr->data.data, sizeof(fr->data.data)); + + assert((size_t)(p - out) == len); + +@@ -2109,7 +2114,7 @@ ngtcp2_ssize ngtcp2_pkt_write_version_negotiation( + + p = dest; + +- *p++ = 0xc0 | unused_random; ++ *p++ = 0xC0 | unused_random; + p = ngtcp2_put_uint32be(p, 0); + *p++ = (uint8_t)dcidlen; + +@@ -2146,20 +2151,20 @@ size_t ngtcp2_pkt_decode_version_negotiation(uint32_t *dest, + return payloadlen / sizeof(uint32_t); + } + +-int ngtcp2_pkt_decode_stateless_reset(ngtcp2_pkt_stateless_reset *sr, ++int ngtcp2_pkt_decode_stateless_reset(ngtcp2_pkt_stateless_reset2 *sr, + const uint8_t *payload, + size_t payloadlen) { + const uint8_t *p = payload; + + if (payloadlen < +- NGTCP2_MIN_STATELESS_RESET_RANDLEN + NGTCP2_STATELESS_RESET_TOKENLEN) { ++ NGTCP2_MIN_STATELESS_RESET_RANDLEN + sizeof(sr->token.data)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + sr->rand = p; +- sr->randlen = payloadlen - NGTCP2_STATELESS_RESET_TOKENLEN; ++ sr->randlen = payloadlen - sizeof(sr->token.data); + p += sr->randlen; +- memcpy(sr->stateless_reset_token, p, NGTCP2_STATELESS_RESET_TOKENLEN); ++ memcpy(sr->token.data, p, sizeof(sr->token.data)); + + return 0; + } +@@ -2238,10 +2243,21 @@ ngtcp2_ssize + ngtcp2_pkt_write_stateless_reset(uint8_t *dest, size_t destlen, + const uint8_t *stateless_reset_token, + const uint8_t *rand, size_t randlen) { ++ ngtcp2_stateless_reset_token token; ++ ++ memcpy(token.data, stateless_reset_token, sizeof(token.data)); ++ ++ return ngtcp2_pkt_write_stateless_reset2(dest, destlen, &token, rand, ++ randlen); ++} ++ ++ngtcp2_ssize ++ngtcp2_pkt_write_stateless_reset2(uint8_t *dest, size_t destlen, ++ const ngtcp2_stateless_reset_token *token, ++ const uint8_t *rand, size_t randlen) { + uint8_t *p; + +- if (destlen < +- NGTCP2_MIN_STATELESS_RESET_RANDLEN + NGTCP2_STATELESS_RESET_TOKENLEN) { ++ if (destlen < NGTCP2_MIN_STATELESS_RESET_RANDLEN + sizeof(token->data)) { + return NGTCP2_ERR_NOBUF; + } + +@@ -2251,11 +2267,11 @@ ngtcp2_pkt_write_stateless_reset(uint8_t *dest, size_t destlen, + + p = dest; + +- randlen = ngtcp2_min_size(destlen - NGTCP2_STATELESS_RESET_TOKENLEN, randlen); ++ randlen = ngtcp2_min_size(destlen - sizeof(token->data), randlen); + + p = ngtcp2_cpymem(p, rand, randlen); +- p = ngtcp2_cpymem(p, stateless_reset_token, NGTCP2_STATELESS_RESET_TOKENLEN); +- *dest = (uint8_t)((*dest & 0x7fu) | 0x40u); ++ p = ngtcp2_cpymem(p, token->data, sizeof(token->data)); ++ *dest = (uint8_t)((*dest & 0x3FU) | 0x40U); + + return p - dest; + } +@@ -2299,11 +2315,11 @@ ngtcp2_ssize ngtcp2_pkt_write_retry( + case NGTCP2_PROTO_VER_V1: + default: + nonce = (const uint8_t *)NGTCP2_RETRY_NONCE_V1; +- noncelen = sizeof(NGTCP2_RETRY_NONCE_V1) - 1; ++ noncelen = ngtcp2_strlen_lit(NGTCP2_RETRY_NONCE_V1); + break; + case NGTCP2_PROTO_VER_V2: + nonce = (const uint8_t *)NGTCP2_RETRY_NONCE_V2; +- noncelen = sizeof(NGTCP2_RETRY_NONCE_V2) - 1; ++ noncelen = ngtcp2_strlen_lit(NGTCP2_RETRY_NONCE_V2); + break; + } + +@@ -2349,7 +2365,7 @@ ngtcp2_ssize ngtcp2_pkt_encode_pseudo_retry( + return NGTCP2_ERR_NOBUF; + } + +- *p &= 0xf0; ++ *p &= 0xF0; + *p |= unused; + + p += nwrite; +@@ -2389,11 +2405,11 @@ int ngtcp2_pkt_verify_retry_tag(uint32_t version, const ngtcp2_pkt_retry *retry, + case NGTCP2_PROTO_VER_V1: + default: + nonce = (const uint8_t *)NGTCP2_RETRY_NONCE_V1; +- noncelen = sizeof(NGTCP2_RETRY_NONCE_V1) - 1; ++ noncelen = ngtcp2_strlen_lit(NGTCP2_RETRY_NONCE_V1); + break; + case NGTCP2_PROTO_VER_V2: + nonce = (const uint8_t *)NGTCP2_RETRY_NONCE_V2; +- noncelen = sizeof(NGTCP2_RETRY_NONCE_V2) - 1; ++ noncelen = ngtcp2_strlen_lit(NGTCP2_RETRY_NONCE_V2); + break; + } + +@@ -2423,7 +2439,7 @@ size_t ngtcp2_pkt_stream_max_datalen(int64_t stream_id, uint64_t offset, + left -= n; + + if (left > 8 + 1073741823 && len > 1073741823) { +- len = ngtcp2_min_uint64(len, 4611686018427387903lu); ++ len = ngtcp2_min_uint64(len, 4611686018427387903UL); + return (size_t)ngtcp2_min_uint64(len, (uint64_t)(left - 8)); + } + +@@ -2454,7 +2470,7 @@ size_t ngtcp2_pkt_crypto_max_datalen(uint64_t offset, size_t len, size_t left) { + + if (left > 8 + 1073741823 && len > 1073741823) { + #if SIZE_MAX == UINT64_MAX +- len = ngtcp2_min_size(len, 4611686018427387903lu); ++ len = ngtcp2_min_size(len, 4611686018427387903UL); + #endif /* SIZE_MAX == UINT64_MAX */ + return ngtcp2_min_size(len, left - 8); + } +@@ -2571,3 +2587,299 @@ int ngtcp2_pkt_verify_reserved_bits(uint8_t c) { + + return (c & NGTCP2_SHORT_RESERVED_BIT_MASK) == 0 ? 0 : NGTCP2_ERR_PROTO; + } ++ ++size_t ngtcp2_pkt_split_vec_rand(ngtcp2_vec *data, size_t datacnt, ++ uint64_t *offsets, ngtcp2_pcg32 *pcg, ++ size_t max_add) { ++ ngtcp2_vec *v; ++ size_t idx; ++ size_t len; ++ ++ for (; max_add; --max_add) { ++ idx = ngtcp2_pcg32_rand_n(pcg, (uint32_t)datacnt); ++ assert(idx < datacnt); ++ ++ v = &data[idx]; ++ ++ if (v->len <= 1) { ++ continue; ++ } ++ ++ len = v->len / 2; ++ ++ ngtcp2_vec_split_at(&data[datacnt], v, len); ++ ++ offsets[datacnt] = offsets[idx] + len; ++ ++ ++datacnt; ++ } ++ ++ return datacnt; ++} ++ ++size_t ngtcp2_pkt_split_vec_at(ngtcp2_vec *data, size_t datacnt, ++ uint64_t *offsets, size_t at) { ++ assert(at < data[0].len); ++ ++ ngtcp2_vec_split_at(&data[datacnt], &data[0], at); ++ ++ offsets[datacnt] = offsets[0] + at; ++ ++ return datacnt + 1; ++} ++ ++static int pkt_tls_skip8(ngtcp2_buf *buf) { ++ size_t len; ++ ++ if (ngtcp2_buf_len(buf) < 1) { ++ return -1; ++ } ++ ++ len = *buf->pos++; ++ ++ if (ngtcp2_buf_len(buf) < len) { ++ return -1; ++ } ++ ++ buf->pos += len; ++ ++ return 0; ++} ++ ++static int pkt_tls_skip16(ngtcp2_buf *buf) { ++ uint16_t len; ++ ++ if (ngtcp2_buf_len(buf) < sizeof(len)) { ++ return -1; ++ } ++ ++ buf->pos = (uint8_t *)ngtcp2_get_uint16be(&len, buf->pos); ++ ++ if (ngtcp2_buf_len(buf) < len) { ++ return -1; ++ } ++ ++ buf->pos += len; ++ ++ return 0; ++} ++ ++int ngtcp2_pkt_find_server_name(ngtcp2_vec *server_name, const ngtcp2_vec *v) { ++ ngtcp2_buf buf; ++ uint32_t msglen; ++ uint16_t len; ++ uint16_t legacy_ver; ++ uint16_t ext_type; ++ ++ assert(v->len); ++ ++ ngtcp2_buf_init(&buf, v->base, v->len); ++ buf.last += v->len; ++ ++ /* Handshake msg_type and length */ ++ if (ngtcp2_buf_len(&buf) < 1 + 3) { ++ return 0; ++ } ++ ++ /* Keep parsing only when msg_type is client_hello(1). */ ++ if (*buf.pos++ != 1) { ++ return 0; ++ } ++ ++ buf.pos = (uint8_t *)ngtcp2_get_uint24be(&msglen, buf.pos); ++ ++ /* Truncate the buffer to msglen */ ++ ngtcp2_buf_trunc(&buf, msglen); ++ ++ /* legacy_version(0x0303) */ ++ if (ngtcp2_buf_len(&buf) < sizeof(uint16_t)) { ++ return 0; ++ } ++ ++ buf.pos = (uint8_t *)ngtcp2_get_uint16be(&legacy_ver, buf.pos); ++ if (legacy_ver != 0x0303) { ++ return 0; ++ } ++ ++ /* random */ ++ if (ngtcp2_buf_len(&buf) < 32) { ++ return 0; ++ } ++ ++ buf.pos += 32; ++ ++ /* legacy_session_id */ ++ if (pkt_tls_skip8(&buf) != 0) { ++ return 0; ++ } ++ ++ /* cipher_suites */ ++ if (pkt_tls_skip16(&buf) != 0) { ++ return 0; ++ } ++ ++ /* legacy_compression_methods */ ++ if (pkt_tls_skip8(&buf) != 0) { ++ return 0; ++ } ++ ++ /* extensions */ ++ if (ngtcp2_buf_len(&buf) < sizeof(uint16_t)) { ++ return 0; ++ } ++ ++ buf.pos = (uint8_t *)ngtcp2_get_uint16be(&len, buf.pos); ++ ++ /* Truncate the buffer to extensions length */ ++ ngtcp2_buf_trunc(&buf, len); ++ ++ for (;;) { ++ /* Verify that extension_type and length of extension_data are ++ available */ ++ if (ngtcp2_buf_len(&buf) < sizeof(uint16_t) * 2) { ++ return 0; ++ } ++ ++ /* extension_type */ ++ buf.pos = (uint8_t *)ngtcp2_get_uint16be(&ext_type, buf.pos); ++ if (ext_type != 0) { ++ /* extension_data */ ++ if (pkt_tls_skip16(&buf) != 0) { ++ return 0; ++ } ++ ++ continue; ++ } ++ ++ /* Server Name Indication extension(0) */ ++ ++ /* extension_data */ ++ buf.pos = (uint8_t *)ngtcp2_get_uint16be(&len, buf.pos); ++ if (ngtcp2_buf_len(&buf) < len || len < 2) { ++ return 0; ++ } ++ ++ /* Truncate the buffer to extension_data length */ ++ ngtcp2_buf_trunc(&buf, len); ++ ++ /* server_name_list */ ++ buf.pos = (uint8_t *)ngtcp2_get_uint16be(&len, buf.pos); ++ if (ngtcp2_buf_len(&buf) < len || len < 1 + 2) { ++ return 0; ++ } ++ ++ /* We deliberately do not check server_name_list length + 2 == ++ extension_data length. They most likely match, and even if ++ not, no problem at all. */ ++ ++ /* Truncate the buffer to server_name_list length */ ++ ngtcp2_buf_trunc(&buf, len); ++ ++ /* name_type */ ++ if (*buf.pos++ != 0) { ++ return 0; ++ } ++ ++ /* name */ ++ buf.pos = (uint8_t *)ngtcp2_get_uint16be(&len, buf.pos); ++ if (ngtcp2_buf_len(&buf) < len) { ++ return 0; ++ } ++ ++ server_name->base = buf.pos; ++ server_name->len = len; ++ ++ return 1; ++ } ++} ++ ++size_t ngtcp2_pkt_append_ping_and_padding(ngtcp2_vec *data, size_t datacnt, ++ ngtcp2_pcg32 *pcg, size_t n) { ++ uint32_t k; ++ ++ for (; n && datacnt < NGTCP2_MAX_STREAM_DATACNT;) { ++ k = ngtcp2_pcg32_rand_n(pcg, (uint32_t)n + 1); ++ if (k == 0) { ++ /* PING */ ++ data[datacnt] = (ngtcp2_vec){ ++ .base = NULL, ++ .len = 0, ++ }; ++ ++ ++k; ++ } else { ++ /* PADDING of k length */ ++ data[datacnt] = (ngtcp2_vec){ ++ .base = NULL, ++ .len = k, ++ }; ++ } ++ ++ ++datacnt; ++ n -= k; ++ } ++ ++ return datacnt; ++} ++ ++void ngtcp2_pkt_permutate_vec(ngtcp2_vec *data, size_t datacnt, ++ uint64_t *offsets, ngtcp2_pcg32 *pcg) { ++ size_t i, j; ++ ngtcp2_vec v; ++ uint64_t o; ++ ++ if (datacnt < 2) { ++ return; ++ } ++ ++ for (i = datacnt - 1; i > 0; --i) { ++ j = ngtcp2_pcg32_rand_n(pcg, (uint32_t)i); ++ ++ if (i == j) { ++ continue; ++ } ++ ++ v = data[i]; ++ data[i] = data[j]; ++ data[j] = v; ++ ++ o = offsets[i]; ++ offsets[i] = offsets[j]; ++ offsets[j] = o; ++ } ++} ++ ++size_t ngtcp2_pkt_remove_vec_partial(ngtcp2_vec *removed_data, ngtcp2_vec *data, ++ size_t datacnt, uint64_t *offsets, ++ ngtcp2_pcg32 *pcg, ++ const ngtcp2_vec *part) { ++ ngtcp2_vec *v = &data[0]; ++ size_t len; ++ ++ assert(datacnt); ++ assert(v->base < part->base); ++ assert(ngtcp2_vec_end(part) <= ngtcp2_vec_end(v)); ++ ++ len = (size_t)(part->base - v->base) + part->len / 2; ++ ++ ngtcp2_vec_split_at(removed_data, v, len); ++ ++ if (removed_data->len == 1) { ++ return datacnt; ++ } ++ ++ len = 1 + ngtcp2_pcg32_rand_n( ++ pcg, (uint32_t)ngtcp2_min_size(30, removed_data->len - 1)); ++ assert(len < removed_data->len); ++ ++ ngtcp2_vec_split_at(&data[datacnt], removed_data, len); ++ ++ offsets[datacnt] = offsets[0] + v->len + removed_data->len; ++ ++ return datacnt + 1; ++} ++ ++int ngtcp2_stateless_reset_token_eq(const ngtcp2_stateless_reset_token *a, ++ const ngtcp2_stateless_reset_token *b) { ++ return memcmp(a->data, b->data, sizeof(a->data)) == 0; ++} +diff --git a/third_party/ngtcp2/lib/ngtcp2_pkt.h b/third_party/ngtcp2/lib/ngtcp2_pkt.h +index 756076e7a7f..bfdb065b74c 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_pkt.h ++++ b/third_party/ngtcp2/lib/ngtcp2_pkt.h +@@ -38,14 +38,14 @@ + + /* Long header specific macros */ + #define NGTCP2_LONG_TYPE_MASK 0x30 +-#define NGTCP2_LONG_RESERVED_BIT_MASK 0x0c ++#define NGTCP2_LONG_RESERVED_BIT_MASK 0x0C + + /* Short header specific macros */ + #define NGTCP2_SHORT_RESERVED_BIT_MASK 0x18 + #define NGTCP2_SHORT_KEY_PHASE_BIT 0x04 + + /* NGTCP2_SR_TYPE is a Type field of Stateless Reset. */ +-#define NGTCP2_SR_TYPE 0x1f ++#define NGTCP2_SR_TYPE 0x1F + + /* NGTCP2_MIN_LONG_HEADERLEN is the minimum length of long header. + That is (1|1|TT|RR|PP)<1> + VERSION<4> + DCIL<1> + SCIL<1> + +@@ -78,23 +78,23 @@ + + /* NGTCP2_MAX_SERVER_STREAM_ID_BIDI is the maximum bidirectional + server stream ID. */ +-#define NGTCP2_MAX_SERVER_STREAM_ID_BIDI ((int64_t)0x3ffffffffffffffdll) ++#define NGTCP2_MAX_SERVER_STREAM_ID_BIDI ((int64_t)0x3FFFFFFFFFFFFFFDLL) + /* NGTCP2_MAX_CLIENT_STREAM_ID_BIDI is the maximum bidirectional + client stream ID. */ +-#define NGTCP2_MAX_CLIENT_STREAM_ID_BIDI ((int64_t)0x3ffffffffffffffcll) ++#define NGTCP2_MAX_CLIENT_STREAM_ID_BIDI ((int64_t)0x3FFFFFFFFFFFFFFCLL) + /* NGTCP2_MAX_SERVER_STREAM_ID_UNI is the maximum unidirectional + server stream ID. */ +-#define NGTCP2_MAX_SERVER_STREAM_ID_UNI ((int64_t)0x3fffffffffffffffll) ++#define NGTCP2_MAX_SERVER_STREAM_ID_UNI ((int64_t)0x3FFFFFFFFFFFFFFFLL) + /* NGTCP2_MAX_CLIENT_STREAM_ID_UNI is the maximum unidirectional + client stream ID. */ +-#define NGTCP2_MAX_CLIENT_STREAM_ID_UNI ((int64_t)0x3ffffffffffffffell) ++#define NGTCP2_MAX_CLIENT_STREAM_ID_UNI ((int64_t)0x3FFFFFFFFFFFFFFELL) + + /* NGTCP2_MAX_NUM_ACK_RANGES is the maximum number of Additional ACK + ranges which this library can create, or decode. */ + #define NGTCP2_MAX_ACK_RANGES 32 + + /* NGTCP2_MAX_PKT_NUM is the maximum packet number. */ +-#define NGTCP2_MAX_PKT_NUM ((int64_t)((1ll << 62) - 1)) ++#define NGTCP2_MAX_PKT_NUM ((int64_t)((1LL << 62) - 1)) + + /* NGTCP2_MIN_PKT_EXPANDLEN is the minimum packet size expansion to + hide/trigger Stateless Reset. */ +@@ -103,10 +103,6 @@ + /* NGTCP2_RETRY_TAGLEN is the length of Retry packet integrity tag. */ + #define NGTCP2_RETRY_TAGLEN 16 + +-/* NGTCP2_HARD_MAX_UDP_PAYLOAD_SIZE is the maximum UDP datagram +- payload size that this library can write. */ +-#define NGTCP2_HARD_MAX_UDP_PAYLOAD_SIZE ((1 << 24) - 1) +- + /* NGTCP2_PKT_LENGTHLEN is the number of bytes that is occupied by + Length field in Long packet header. */ + #define NGTCP2_PKT_LENGTHLEN 4 +@@ -137,6 +133,19 @@ + v2. */ + #define NGTCP2_PKT_TYPE_RETRY_V2 0x0 + ++/* NGTCP2_MIN_STREAM_DATALEN is the minimum length of STREAM frame to ++ avoid too small frame. It is not always enforced for various ++ reasons. For example, due to flow control, we might have fewer ++ bytes available to send. Therefore, it is only applied when the ++ length of data to send is larger than this limit. */ ++#define NGTCP2_MIN_STREAM_DATALEN 256 ++ ++/* NGTCP2_MAX_STREAM_DATACNT is the maximum number of ngtcp2_vec that ++ a ngtcp2_stream can include. */ ++#define NGTCP2_MAX_STREAM_DATACNT 256 ++ ++typedef struct ngtcp2_pcg32 ngtcp2_pcg32; ++ + typedef struct ngtcp2_pkt_retry { + ngtcp2_cid odcid; + uint8_t *token; +@@ -163,14 +172,18 @@ typedef struct ngtcp2_pkt_retry { + #define NGTCP2_FRAME_STREAMS_BLOCKED_UNI 0x17 + #define NGTCP2_FRAME_NEW_CONNECTION_ID 0x18 + #define NGTCP2_FRAME_RETIRE_CONNECTION_ID 0x19 +-#define NGTCP2_FRAME_PATH_CHALLENGE 0x1a +-#define NGTCP2_FRAME_PATH_RESPONSE 0x1b +-#define NGTCP2_FRAME_CONNECTION_CLOSE 0x1c +-#define NGTCP2_FRAME_CONNECTION_CLOSE_APP 0x1d +-#define NGTCP2_FRAME_HANDSHAKE_DONE 0x1e ++#define NGTCP2_FRAME_PATH_CHALLENGE 0x1A ++#define NGTCP2_FRAME_PATH_RESPONSE 0x1B ++#define NGTCP2_FRAME_CONNECTION_CLOSE 0x1C ++#define NGTCP2_FRAME_CONNECTION_CLOSE_APP 0x1D ++#define NGTCP2_FRAME_HANDSHAKE_DONE 0x1E + #define NGTCP2_FRAME_DATAGRAM 0x30 + #define NGTCP2_FRAME_DATAGRAM_LEN 0x31 + ++typedef struct ngtcp2_frame_hd { ++ uint64_t type; ++} ngtcp2_frame_hd; ++ + /* ngtcp2_stream represents STREAM and CRYPTO frames. */ + typedef struct ngtcp2_stream { + uint64_t type; +@@ -182,7 +195,7 @@ typedef struct ngtcp2_stream { + uint8_t flags; + /* CRYPTO frame does not include this field, and must set it to + 0. */ +- uint8_t fin; ++ int fin; + /* CRYPTO frame does not include this field, and must set it to + 0. */ + int64_t stream_id; +@@ -191,8 +204,9 @@ typedef struct ngtcp2_stream { + the length of data is 1 in this definition, the library may + allocate extra bytes to hold more elements. */ + size_t datacnt; +- /* data is the array of ngtcp2_vec which references data. */ +- ngtcp2_vec data[1]; ++ /* data points to ngtcp2_vec array which references data. If ++ datacnt == 0, this field may be NULL. */ ++ ngtcp2_vec *data; + } ngtcp2_stream; + + typedef struct ngtcp2_ack_range { +@@ -216,7 +230,7 @@ typedef struct ngtcp2_ack { + } ecn; + uint64_t first_ack_range; + size_t rangecnt; +- ngtcp2_ack_range ranges[1]; ++ ngtcp2_ack_range *ranges; + } ngtcp2_ack; + + typedef struct ngtcp2_padding { +@@ -286,7 +300,7 @@ typedef struct ngtcp2_new_connection_id { + uint64_t seq; + uint64_t retire_prior_to; + ngtcp2_cid cid; +- uint8_t stateless_reset_token[NGTCP2_STATELESS_RESET_TOKENLEN]; ++ ngtcp2_stateless_reset_token token; + } ngtcp2_new_connection_id; + + typedef struct ngtcp2_stop_sending { +@@ -297,12 +311,12 @@ typedef struct ngtcp2_stop_sending { + + typedef struct ngtcp2_path_challenge { + uint64_t type; +- uint8_t data[NGTCP2_PATH_CHALLENGE_DATALEN]; ++ ngtcp2_path_challenge_data data; + } ngtcp2_path_challenge; + + typedef struct ngtcp2_path_response { + uint64_t type; +- uint8_t data[NGTCP2_PATH_CHALLENGE_DATALEN]; ++ ngtcp2_path_challenge_data data; + } ngtcp2_path_response; + + typedef struct ngtcp2_new_token { +@@ -326,17 +340,13 @@ typedef struct ngtcp2_datagram { + uint64_t dgram_id; + /* datacnt is the number of elements that data contains. */ + size_t datacnt; +- /* data is a pointer to ngtcp2_vec array that stores data. */ ++ /* data is a pointer to ngtcp2_vec array that stores data. If ++ datacnt == 0, this field may be NULL.*/ + ngtcp2_vec *data; +- /* rdata is conveniently embedded to ngtcp2_datagram, so that data +- field can just point to the address of this field to store a +- single vector which is the case when DATAGRAM is received from a +- remote endpoint. */ +- ngtcp2_vec rdata[1]; + } ngtcp2_datagram; + + typedef union ngtcp2_frame { +- uint64_t type; ++ ngtcp2_frame_hd hd; + ngtcp2_stream stream; + ngtcp2_ack ack; + ngtcp2_padding padding; +@@ -357,11 +367,6 @@ typedef union ngtcp2_frame { + ngtcp2_retire_connection_id retire_connection_id; + ngtcp2_handshake_done handshake_done; + ngtcp2_datagram datagram; +- /* Extend ngtcp2_frame so that ngtcp2_stream has at least additional +- 3 ngtcp2_vec, totaling 4 slots, which can store HEADERS header, +- HEADERS payload, DATA header, and DATA payload in the standard +- sized ngtcp2_frame_chain. */ +- uint8_t pad[sizeof(ngtcp2_stream) + sizeof(ngtcp2_vec) * 3]; + } ngtcp2_frame; + + typedef struct ngtcp2_pkt_chain ngtcp2_pkt_chain; +@@ -441,10 +446,22 @@ ngtcp2_ssize ngtcp2_pkt_encode_hd_long(uint8_t *out, size_t outlen, + ngtcp2_ssize ngtcp2_pkt_encode_hd_short(uint8_t *out, size_t outlen, + const ngtcp2_pkt_hd *hd); + ++/* ++ * ngtcp2_frame_decoder is QUIC frame decoder. For frames that ++ * require the external buffers (e.g., ngtcp2_stream and ngtcp2_ack), ++ * it provides those buffers on demand. ++ */ ++typedef struct ngtcp2_frame_decoder { ++ union { ++ ngtcp2_vec data; ++ ngtcp2_ack_range ack_ranges[NGTCP2_MAX_ACK_RANGES]; ++ } buf; ++} ngtcp2_frame_decoder; ++ + /** + * @function + * +- * `ngtcp2_pkt_decode_frame` decodes a QUIC frame from the buffer ++ * `ngtcp2_frame_decoder_decode` decodes a QUIC frame from the buffer + * pointed by |payload| whose length is |payloadlen|. + * + * This function returns the number of bytes read to decode a single +@@ -454,8 +471,10 @@ ngtcp2_ssize ngtcp2_pkt_encode_hd_short(uint8_t *out, size_t outlen, + * Frame is badly formatted; or frame type is unknown; or + * |payloadlen| is 0. + */ +-ngtcp2_ssize ngtcp2_pkt_decode_frame(ngtcp2_frame *dest, const uint8_t *payload, +- size_t payloadlen); ++ngtcp2_ssize ngtcp2_frame_decoder_decode(ngtcp2_frame_decoder *frd, ++ ngtcp2_frame *dest, ++ const uint8_t *payload, ++ size_t payloadlen); + + /** + * @function +@@ -495,7 +514,7 @@ size_t ngtcp2_pkt_decode_version_negotiation(uint32_t *dest, + * NGTCP2_ERR_INVALID_ARGUMENT + * Payloadlen is too short. + */ +-int ngtcp2_pkt_decode_stateless_reset(ngtcp2_pkt_stateless_reset *sr, ++int ngtcp2_pkt_decode_stateless_reset(ngtcp2_pkt_stateless_reset2 *sr, + const uint8_t *payload, + size_t payloadlen); + +@@ -1227,4 +1246,88 @@ uint8_t ngtcp2_pkt_versioned_type(uint32_t version, uint32_t pkt_type); + */ + uint8_t ngtcp2_pkt_get_type_long(uint32_t version, uint8_t c); + ++/* ++ * ngtcp2_pkt_split_vec_rand appends ngtcp2_vec at most |max_add| ++ * times to the array pointed by |data| of length |datacnt| by ++ * splitting the existing ngtcp2_vec into two. Which ngtcp2_vec to ++ * split is chosen randomly. |offsets| contains the offset of each ++ * ngtcp2_vec pointed by |data|. |offsets| is also updated. The ++ * arrays must have the capacity at least |datacnt| + |max_add|. ++ * |pcg| is a random number generator. ++ * ++ * This function returns |datacnt| plus the number of ngtcp2_vec that ++ * are appended. ++ */ ++size_t ngtcp2_pkt_split_vec_rand(ngtcp2_vec *data, size_t datacnt, ++ uint64_t *offsets, ngtcp2_pcg32 *pcg, ++ size_t max_add); ++ ++/* ++ * ngtcp2_pkt_split_vec_at splits data[0] at offset |at|, and the ++ * right side of ngtcp2_vec is assigned to data[datacnt]. Similarly, ++ * offsets[0] + |at| is assigned to offsets[datacnt]. |data| must ++ * point to the array of ngtcp2_vec of length |datacnt|, and |datacnt| ++ * must be greater than 0. |at| must be strictly less than data->len. ++ * ++ * This function returns |datacnt| + 1. ++ */ ++size_t ngtcp2_pkt_split_vec_at(ngtcp2_vec *data, size_t datacnt, ++ uint64_t *offsets, size_t at); ++ ++/* ++ * ngtcp2_pkt_find_server_name searches TLS Server Name Indication ++ * extension in |v|. If it is found, assign the portion of server ++ * name to the object pointed by |server_name|, and returns nonzero. ++ * Otherwise, it returns 0. If |v| contains the extension partially, ++ * the function returns 0. |v| must not be empty. ++ */ ++int ngtcp2_pkt_find_server_name(ngtcp2_vec *server_name, const ngtcp2_vec *v); ++ ++/* ++ * ngtcp2_pkt_append_ping_and_padding appends PING and PADDING frames ++ * to the array pointed by |data| of length |datacnt|. The capacity ++ * of array must be at least NGTCP2_MAX_STREAM_DATACNT. |n| is the ++ * number of bytes available for serialized PING and PADDING frames. ++ * |pcg| is a random number generator. Which frames to add is ++ * determined randomly. ++ * ++ * The special encoding of PING and PADDING frames into ngtcp2_vec: ++ * ++ * - .base is NULL. ++ * - If .len is 0, it represents PING. Otherwise, PADDING of .len ++ * length. ++ * ++ * This function returns |datacnt| plus the number of frames added. ++ */ ++size_t ngtcp2_pkt_append_ping_and_padding(ngtcp2_vec *data, size_t datacnt, ++ ngtcp2_pcg32 *pcg, size_t n); ++ ++/* ++ * ngtcp2_pkt_permutate_vec permutates |data| and |offsets|, both have ++ * the |datacnt| elements. |pcg| is a random number generator. ++ */ ++void ngtcp2_pkt_permutate_vec(ngtcp2_vec *data, size_t datacnt, ++ uint64_t *offsets, ngtcp2_pcg32 *pcg); ++ ++/* ++ * ngtcp2_pkt_remove_vec_partial removes the portion of data that ++ * contains part of |part| from data[0]. This function does not ++ * remove whole range of |part|. The length of removed data is chosen ++ * randomly. The removed portion of data is assigned to the object ++ * pointed by |removed_data|. If there is data located after the ++ * removed data, it will be assigned to data[datacnt]. ++ * offsets[datacnt] is also updated, and the function returns ++ * |datacnt| + 1. Otherwise, this function returns |datacnt|. ++ */ ++size_t ngtcp2_pkt_remove_vec_partial(ngtcp2_vec *removed_data, ngtcp2_vec *data, ++ size_t datacnt, uint64_t *offsets, ++ ngtcp2_pcg32 *pcg, const ngtcp2_vec *part); ++ ++/* ++ * ngtcp2_stateless_reset_token_eq returns nonzero if |a| and |b| ++ * share the same token. ++ */ ++int ngtcp2_stateless_reset_token_eq(const ngtcp2_stateless_reset_token *a, ++ const ngtcp2_stateless_reset_token *b); ++ + #endif /* !defined(NGTCP2_PKT_H) */ +diff --git a/third_party/ngtcp2/lib/ngtcp2_ppe.c b/third_party/ngtcp2/lib/ngtcp2_ppe.c +index 4d193125ae8..3054732db04 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_ppe.c ++++ b/third_party/ngtcp2/lib/ngtcp2_ppe.c +@@ -154,9 +154,9 @@ ngtcp2_ssize ngtcp2_ppe_final(ngtcp2_ppe *ppe, const uint8_t **ppkt) { + + p = buf->begin; + if (*p & NGTCP2_HEADER_FORM_BIT) { +- *p = (uint8_t)(*p ^ (mask[0] & 0x0f)); ++ *p = (uint8_t)(*p ^ (mask[0] & 0x0F)); + } else { +- *p = (uint8_t)(*p ^ (mask[0] & 0x1f)); ++ *p = (uint8_t)(*p ^ (mask[0] & 0x1F)); + } + + p = buf->begin + ppe->pkt_num_offset; +diff --git a/third_party/ngtcp2/lib/ngtcp2_pv.c b/third_party/ngtcp2/lib/ngtcp2_pv.c +index 471f84c7644..972c27f395a 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_pv.c ++++ b/third_party/ngtcp2/lib/ngtcp2_pv.c +@@ -31,12 +31,16 @@ + #include "ngtcp2_log.h" + #include "ngtcp2_macro.h" + #include "ngtcp2_addr.h" ++#include "ngtcp2_str.h" + +-void ngtcp2_pv_entry_init(ngtcp2_pv_entry *pvent, const uint8_t *data, ++void ngtcp2_pv_entry_init(ngtcp2_pv_entry *pvent, ++ const ngtcp2_path_challenge_data *data, + ngtcp2_tstamp expiry, uint8_t flags) { +- memcpy(pvent->data, data, sizeof(pvent->data)); +- pvent->expiry = expiry; +- pvent->flags = flags; ++ *pvent = (ngtcp2_pv_entry){ ++ .expiry = expiry, ++ .flags = flags, ++ .data = *data, ++ }; + } + + int ngtcp2_pv_new(ngtcp2_pv **ppv, const ngtcp2_dcid *dcid, +@@ -70,7 +74,7 @@ void ngtcp2_pv_del(ngtcp2_pv *pv) { + ngtcp2_mem_free(pv->mem, pv); + } + +-void ngtcp2_pv_add_entry(ngtcp2_pv *pv, const uint8_t *data, ++void ngtcp2_pv_add_entry(ngtcp2_pv *pv, const ngtcp2_path_challenge_data *data, + ngtcp2_tstamp expiry, uint8_t flags, + ngtcp2_tstamp ts) { + ngtcp2_pv_entry *ent; +@@ -88,7 +92,8 @@ void ngtcp2_pv_add_entry(ngtcp2_pv *pv, const uint8_t *data, + --pv->probe_pkt_left; + } + +-int ngtcp2_pv_validate(ngtcp2_pv *pv, uint8_t *pflags, const uint8_t *data) { ++int ngtcp2_pv_validate(ngtcp2_pv *pv, uint8_t *pflags, ++ const ngtcp2_path_challenge_data *data) { + size_t len = ngtcp2_ringbuf_len(&pv->ents.rb); + size_t i; + ngtcp2_pv_entry *ent; +@@ -99,7 +104,7 @@ int ngtcp2_pv_validate(ngtcp2_pv *pv, uint8_t *pflags, const uint8_t *data) { + + for (i = 0; i < len; ++i) { + ent = ngtcp2_ringbuf_get(&pv->ents.rb, i); +- if (memcmp(ent->data, data, sizeof(ent->data)) == 0) { ++ if (ngtcp2_cmemeq(ent->data.data, data->data, sizeof(ent->data.data))) { + *pflags = ent->flags; + ngtcp2_log_info(pv->log, NGTCP2_LOG_EVENT_PTV, "path has been validated"); + return 0; +diff --git a/third_party/ngtcp2/lib/ngtcp2_pv.h b/third_party/ngtcp2/lib/ngtcp2_pv.h +index 2d07e41648d..28bdf722b20 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_pv.h ++++ b/third_party/ngtcp2/lib/ngtcp2_pv.h +@@ -46,10 +46,10 @@ typedef struct ngtcp2_log ngtcp2_log; + typedef struct ngtcp2_frame_chain ngtcp2_frame_chain; + + /* NGTCP2_PV_ENTRY_FLAG_NONE indicates that no flag is set. */ +-#define NGTCP2_PV_ENTRY_FLAG_NONE 0x00u ++#define NGTCP2_PV_ENTRY_FLAG_NONE 0x00U + /* NGTCP2_PV_ENTRY_FLAG_UNDERSIZED indicates that UDP datagram which + contains PATH_CHALLENGE is undersized (< 1200 bytes) */ +-#define NGTCP2_PV_ENTRY_FLAG_UNDERSIZED 0x01u ++#define NGTCP2_PV_ENTRY_FLAG_UNDERSIZED 0x01U + + typedef struct ngtcp2_pv_entry { + /* expiry is the timestamp when this PATH_CHALLENGE expires. */ +@@ -57,30 +57,31 @@ typedef struct ngtcp2_pv_entry { + /* flags is zero or more of NGTCP2_PV_ENTRY_FLAG_*. */ + uint8_t flags; + /* data is a byte string included in PATH_CHALLENGE. */ +- uint8_t data[8]; ++ ngtcp2_path_challenge_data data; + } ngtcp2_pv_entry; + +-void ngtcp2_pv_entry_init(ngtcp2_pv_entry *pvent, const uint8_t *data, ++void ngtcp2_pv_entry_init(ngtcp2_pv_entry *pvent, ++ const ngtcp2_path_challenge_data *data, + ngtcp2_tstamp expiry, uint8_t flags); + + /* NGTCP2_PV_FLAG_NONE indicates no flag is set. */ +-#define NGTCP2_PV_FLAG_NONE 0x00u ++#define NGTCP2_PV_FLAG_NONE 0x00U + /* NGTCP2_PV_FLAG_DONT_CARE indicates that the outcome of path + validation should be ignored entirely. */ +-#define NGTCP2_PV_FLAG_DONT_CARE 0x01u ++#define NGTCP2_PV_FLAG_DONT_CARE 0x01U + /* NGTCP2_PV_FLAG_CANCEL_TIMER indicates that the expiry timer is + cancelled. */ +-#define NGTCP2_PV_FLAG_CANCEL_TIMER 0x02u ++#define NGTCP2_PV_FLAG_CANCEL_TIMER 0x02U + /* NGTCP2_PV_FLAG_FALLBACK_PRESENT indicates that a fallback + Destination Connection ID and PTO are available in ngtcp2_pv. If + path validation fails, then fallback to them. If path validation + succeeds, the fallback Destination Connection ID is retired if it + is not zero length, and does not equal to the current Destination + Connection ID. */ +-#define NGTCP2_PV_FLAG_FALLBACK_PRESENT 0x04u ++#define NGTCP2_PV_FLAG_FALLBACK_PRESENT 0x04U + /* NGTCP2_PV_FLAG_PREFERRED_ADDR indicates that client is migrating to + server's preferred address. This flag is only used by client. */ +-#define NGTCP2_PV_FLAG_PREFERRED_ADDR 0x10u ++#define NGTCP2_PV_FLAG_PREFERRED_ADDR 0x10U + + typedef struct ngtcp2_pv ngtcp2_pv; + +@@ -141,7 +142,7 @@ void ngtcp2_pv_del(ngtcp2_pv *pv); + * ngtcp2_pv_add_entry adds new entry with |data|. |expiry| is the + * expiry time of the entry. + */ +-void ngtcp2_pv_add_entry(ngtcp2_pv *pv, const uint8_t *data, ++void ngtcp2_pv_add_entry(ngtcp2_pv *pv, const ngtcp2_path_challenge_data *data, + ngtcp2_tstamp expiry, uint8_t flags, ngtcp2_tstamp ts); + + /* +@@ -164,7 +165,8 @@ int ngtcp2_pv_full(ngtcp2_pv *pv); + * NGTCP2_ERR_INVALID_ARGUMENT + * |pv| does not have an entry which has |data| and |path| + */ +-int ngtcp2_pv_validate(ngtcp2_pv *pv, uint8_t *pflags, const uint8_t *data); ++int ngtcp2_pv_validate(ngtcp2_pv *pv, uint8_t *pflags, ++ const ngtcp2_path_challenge_data *data); + + /* + * ngtcp2_pv_handle_entry_expiry checks expiry of existing entries. +diff --git a/third_party/ngtcp2/lib/ngtcp2_qlog.c b/third_party/ngtcp2/lib/ngtcp2_qlog.c +index c0f920746a4..42609481ec4 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_qlog.c ++++ b/third_party/ngtcp2/lib/ngtcp2_qlog.c +@@ -40,7 +40,7 @@ void ngtcp2_qlog_init(ngtcp2_qlog *qlog, ngtcp2_qlog_write write, + qlog->user_data = user_data; + } + +-#define write_verbatim(DEST, S) ngtcp2_cpymem((DEST), (S), sizeof(S) - 1) ++#define write_verbatim(DEST, S) ngtcp2_cpymem((DEST), (S), ngtcp2_strlen_lit(S)) + + static uint8_t *write_string_impl(uint8_t *p, const uint8_t *data, + size_t datalen) { +@@ -53,17 +53,11 @@ static uint8_t *write_string_impl(uint8_t *p, const uint8_t *data, + } + + #define write_string(DEST, S) \ +- write_string_impl((DEST), (const uint8_t *)(S), sizeof(S) - 1) +- +-#define NGTCP2_LOWER_XDIGITS "0123456789abcdef" ++ write_string_impl((DEST), (const uint8_t *)(S), ngtcp2_strlen_lit(S)) + + static uint8_t *write_hex(uint8_t *p, const uint8_t *data, size_t datalen) { +- const uint8_t *b = data, *end = data + datalen; + *p++ = '"'; +- for (; b != end; ++b) { +- *p++ = (uint8_t)NGTCP2_LOWER_XDIGITS[*b >> 4]; +- *p++ = (uint8_t)NGTCP2_LOWER_XDIGITS[*b & 0xf]; +- } ++ p = ngtcp2_encode_hex(p, data, datalen); + *p++ = '"'; + return p; + } +@@ -72,38 +66,19 @@ static uint8_t *write_cid(uint8_t *p, const ngtcp2_cid *cid) { + return write_hex(p, cid->data, cid->datalen); + } + +-static uint8_t *write_number(uint8_t *p, uint64_t n) { +- size_t nlen = 0; +- uint64_t t; +- uint8_t *res; +- +- if (n == 0) { +- *p++ = '0'; +- return p; +- } +- for (t = n; t; t /= 10, ++nlen) +- ; +- p += nlen; +- res = p; +- for (; n; n /= 10) { +- *--p = (uint8_t)((n % 10) + '0'); +- } +- return res; +-} +- + static uint8_t *write_tstamp(uint8_t *p, ngtcp2_tstamp ts) { +- return write_number(p, ts / NGTCP2_MILLISECONDS); ++ return ngtcp2_encode_uint(p, ts / NGTCP2_MILLISECONDS); + } + + static uint8_t *write_duration(uint8_t *p, ngtcp2_duration duration) { +- return write_number(p, duration / NGTCP2_MILLISECONDS); ++ return ngtcp2_encode_uint(p, duration / NGTCP2_MILLISECONDS); + } + + static uint8_t *write_bool(uint8_t *p, int b) { + if (b) { +- return ngtcp2_cpymem(p, "true", sizeof("true") - 1); ++ return ngtcp2_cpymem(p, "true", ngtcp2_strlen_lit("true")); + } +- return ngtcp2_cpymem(p, "false", sizeof("false") - 1); ++ return ngtcp2_cpymem(p, "false", ngtcp2_strlen_lit("false")); + } + + static uint8_t *write_pair_impl(uint8_t *p, const uint8_t *name, size_t namelen, +@@ -114,7 +89,8 @@ static uint8_t *write_pair_impl(uint8_t *p, const uint8_t *name, size_t namelen, + } + + #define write_pair(DEST, NAME, VALUE) \ +- write_pair_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, (VALUE)) ++ write_pair_impl((DEST), (const uint8_t *)(NAME), ngtcp2_strlen_lit(NAME), \ ++ (VALUE)) + + static uint8_t *write_pair_hex_impl(uint8_t *p, const uint8_t *name, + size_t namelen, const uint8_t *value, +@@ -125,19 +101,19 @@ static uint8_t *write_pair_hex_impl(uint8_t *p, const uint8_t *name, + } + + #define write_pair_hex(DEST, NAME, VALUE, VALUELEN) \ +- write_pair_hex_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, \ +- (VALUE), (VALUELEN)) ++ write_pair_hex_impl((DEST), (const uint8_t *)(NAME), \ ++ ngtcp2_strlen_lit(NAME), (VALUE), (VALUELEN)) + + static uint8_t *write_pair_number_impl(uint8_t *p, const uint8_t *name, + size_t namelen, uint64_t value) { + p = write_string_impl(p, name, namelen); + *p++ = ':'; +- return write_number(p, value); ++ return ngtcp2_encode_uint(p, value); + } + + #define write_pair_number(DEST, NAME, VALUE) \ +- write_pair_number_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, \ +- (VALUE)) ++ write_pair_number_impl((DEST), (const uint8_t *)(NAME), \ ++ ngtcp2_strlen_lit(NAME), (VALUE)) + + static uint8_t *write_pair_duration_impl(uint8_t *p, const uint8_t *name, + size_t namelen, +@@ -148,8 +124,8 @@ static uint8_t *write_pair_duration_impl(uint8_t *p, const uint8_t *name, + } + + #define write_pair_duration(DEST, NAME, VALUE) \ +- write_pair_duration_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, \ +- (VALUE)) ++ write_pair_duration_impl((DEST), (const uint8_t *)(NAME), \ ++ ngtcp2_strlen_lit(NAME), (VALUE)) + + static uint8_t *write_pair_tstamp_impl(uint8_t *p, const uint8_t *name, + size_t namelen, ngtcp2_tstamp ts) { +@@ -159,8 +135,8 @@ static uint8_t *write_pair_tstamp_impl(uint8_t *p, const uint8_t *name, + } + + #define write_pair_tstamp(DEST, NAME, VALUE) \ +- write_pair_tstamp_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, \ +- (VALUE)) ++ write_pair_tstamp_impl((DEST), (const uint8_t *)(NAME), \ ++ ngtcp2_strlen_lit(NAME), (VALUE)) + + static uint8_t *write_pair_bool_impl(uint8_t *p, const uint8_t *name, + size_t namelen, int b) { +@@ -170,8 +146,8 @@ static uint8_t *write_pair_bool_impl(uint8_t *p, const uint8_t *name, + } + + #define write_pair_bool(DEST, NAME, VALUE) \ +- write_pair_bool_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, \ +- (VALUE)) ++ write_pair_bool_impl((DEST), (const uint8_t *)(NAME), \ ++ ngtcp2_strlen_lit(NAME), (VALUE)) + + static uint8_t *write_pair_cid_impl(uint8_t *p, const uint8_t *name, + size_t namelen, const ngtcp2_cid *cid) { +@@ -181,10 +157,10 @@ static uint8_t *write_pair_cid_impl(uint8_t *p, const uint8_t *name, + } + + #define write_pair_cid(DEST, NAME, VALUE) \ +- write_pair_cid_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, \ +- (VALUE)) ++ write_pair_cid_impl((DEST), (const uint8_t *)(NAME), \ ++ ngtcp2_strlen_lit(NAME), (VALUE)) + +-#define ngtcp2_make_vec_lit(S) {(uint8_t *)(S), sizeof((S)) - 1} ++#define ngtcp2_make_vec_lit(S) {(uint8_t *)(S), ngtcp2_strlen_lit((S))} + + static uint8_t *write_common_fields(uint8_t *p, const ngtcp2_cid *odcid) { + p = write_verbatim( +@@ -218,7 +194,7 @@ void ngtcp2_qlog_start(ngtcp2_qlog *qlog, const ngtcp2_cid *odcid, int server) { + } + + p = write_verbatim( +- p, "\x1e{\"qlog_format\":\"JSON-SEQ\",\"qlog_version\":\"0.3\","); ++ p, "\x1E{\"qlog_format\":\"JSON-SEQ\",\"qlog_version\":\"0.3\","); + p = write_trace(p, server, odcid); + p = write_verbatim(p, "}\n"); + +@@ -227,49 +203,41 @@ void ngtcp2_qlog_start(ngtcp2_qlog *qlog, const ngtcp2_cid *odcid, int server) { + } + + void ngtcp2_qlog_end(ngtcp2_qlog *qlog) { +- uint8_t buf[1] = {0}; +- + if (!qlog->write) { + return; + } + +- qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_FIN, &buf, 0); ++ qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_FIN, "", 0); + } + +-static ngtcp2_vec vec_pkt_type_initial = ngtcp2_make_vec_lit("initial"); +-static ngtcp2_vec vec_pkt_type_handshake = ngtcp2_make_vec_lit("handshake"); +-static ngtcp2_vec vec_pkt_type_0rtt = ngtcp2_make_vec_lit("0RTT"); +-static ngtcp2_vec vec_pkt_type_1rtt = ngtcp2_make_vec_lit("1RTT"); +-static ngtcp2_vec vec_pkt_type_retry = ngtcp2_make_vec_lit("retry"); +-static ngtcp2_vec vec_pkt_type_version_negotiation = ++static const ngtcp2_vec vec_pkt_type_initial = ngtcp2_make_vec_lit("initial"); ++static const ngtcp2_vec vec_pkt_type_handshake = ++ ngtcp2_make_vec_lit("handshake"); ++static const ngtcp2_vec vec_pkt_type_0rtt = ngtcp2_make_vec_lit("0RTT"); ++static const ngtcp2_vec vec_pkt_type_1rtt = ngtcp2_make_vec_lit("1RTT"); ++static const ngtcp2_vec vec_pkt_type_retry = ngtcp2_make_vec_lit("retry"); ++static const ngtcp2_vec vec_pkt_type_version_negotiation = + ngtcp2_make_vec_lit("version_negotiation"); +-static ngtcp2_vec vec_pkt_type_stateless_reset = ++static const ngtcp2_vec vec_pkt_type_stateless_reset = + ngtcp2_make_vec_lit("stateless_reset"); +-static ngtcp2_vec vec_pkt_type_unknown = ngtcp2_make_vec_lit("unknown"); ++static const ngtcp2_vec vec_pkt_type_unknown = ngtcp2_make_vec_lit("unknown"); + + static const ngtcp2_vec *qlog_pkt_type(const ngtcp2_pkt_hd *hd) { +- if (hd->flags & NGTCP2_PKT_FLAG_LONG_FORM) { +- switch (hd->type) { +- case NGTCP2_PKT_INITIAL: +- return &vec_pkt_type_initial; +- case NGTCP2_PKT_HANDSHAKE: +- return &vec_pkt_type_handshake; +- case NGTCP2_PKT_0RTT: +- return &vec_pkt_type_0rtt; +- case NGTCP2_PKT_RETRY: +- return &vec_pkt_type_retry; +- default: +- return &vec_pkt_type_unknown; +- } +- } +- + switch (hd->type) { ++ case NGTCP2_PKT_INITIAL: ++ return &vec_pkt_type_initial; ++ case NGTCP2_PKT_0RTT: ++ return &vec_pkt_type_0rtt; ++ case NGTCP2_PKT_HANDSHAKE: ++ return &vec_pkt_type_handshake; ++ case NGTCP2_PKT_RETRY: ++ return &vec_pkt_type_retry; ++ case NGTCP2_PKT_1RTT: ++ return &vec_pkt_type_1rtt; + case NGTCP2_PKT_VERSION_NEGOTIATION: + return &vec_pkt_type_version_negotiation; + case NGTCP2_PKT_STATELESS_RESET: + return &vec_pkt_type_stateless_reset; +- case NGTCP2_PKT_1RTT: +- return &vec_pkt_type_1rtt; + default: + return &vec_pkt_type_unknown; + } +@@ -340,10 +308,10 @@ static uint8_t *write_ack_frame(uint8_t *p, const ngtcp2_ack *fr) { + min_ack = fr->largest_ack - (int64_t)fr->first_ack_range; + + *p++ = '['; +- p = write_number(p, (uint64_t)min_ack); ++ p = ngtcp2_encode_uint(p, (uint64_t)min_ack); + if (largest_ack != min_ack) { + *p++ = ','; +- p = write_number(p, (uint64_t)largest_ack); ++ p = ngtcp2_encode_uint(p, (uint64_t)largest_ack); + } + *p++ = ']'; + +@@ -353,10 +321,10 @@ static uint8_t *write_ack_frame(uint8_t *p, const ngtcp2_ack *fr) { + min_ack = largest_ack - (int64_t)range->len; + *p++ = ','; + *p++ = '['; +- p = write_number(p, (uint64_t)min_ack); ++ p = ngtcp2_encode_uint(p, (uint64_t)min_ack); + if (largest_ack != min_ack) { + *p++ = ','; +- p = write_number(p, (uint64_t)largest_ack); ++ p = ngtcp2_encode_uint(p, (uint64_t)largest_ack); + } + *p++ = ']'; + } +@@ -579,8 +547,7 @@ write_new_connection_id_frame(uint8_t *p, const ngtcp2_new_connection_id *fr) { + *p++ = ','; + p = write_pair_cid(p, "connection_id", &fr->cid); + p = write_verbatim(p, ",\"stateless_reset_token\":{"); +- p = write_pair_hex(p, "data", fr->stateless_reset_token, +- sizeof(fr->stateless_reset_token)); ++ p = write_pair_hex(p, "data", fr->token.data, sizeof(fr->token.data)); + *p++ = '}'; + *p++ = '}'; + +@@ -610,7 +577,7 @@ static uint8_t *write_path_challenge_frame(uint8_t *p, + #define NGTCP2_QLOG_PATH_CHALLENGE_FRAME_OVERHEAD 57 + + p = write_verbatim(p, "{\"frame_type\":\"path_challenge\","); +- p = write_pair_hex(p, "data", fr->data, sizeof(fr->data)); ++ p = write_pair_hex(p, "data", fr->data.data, sizeof(fr->data.data)); + *p++ = '}'; + + return p; +@@ -624,7 +591,7 @@ static uint8_t *write_path_response_frame(uint8_t *p, + #define NGTCP2_QLOG_PATH_RESPONSE_FRAME_OVERHEAD 56 + + p = write_verbatim(p, "{\"frame_type\":\"path_response\","); +- p = write_pair_hex(p, "data", fr->data, sizeof(fr->data)); ++ p = write_pair_hex(p, "data", fr->data.data, sizeof(fr->data.data)); + *p++ = '}'; + + return p; +@@ -694,7 +661,7 @@ static void qlog_pkt_write_start(ngtcp2_qlog *qlog, int sent) { + ngtcp2_buf_reset(&qlog->buf); + p = qlog->buf.last; + +- *p++ = '\x1e'; ++ *p++ = '\x1E'; + *p++ = '{'; + p = qlog_write_time(qlog, p); + p = write_verbatim(p, ",\"name\":"); +@@ -738,7 +705,7 @@ static void qlog_pkt_write_end(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd, + p = write_verbatim(p, "],\"header\":"); + p = write_pkt_hd(p, hd); + p = write_verbatim(p, ",\"raw\":{\"length\":"); +- p = write_number(p, pktlen); ++ p = ngtcp2_encode_uint(p, pktlen); + p = write_verbatim(p, "}}}\n"); + + qlog->buf.last = p; +@@ -754,7 +721,7 @@ void ngtcp2_qlog_write_frame(ngtcp2_qlog *qlog, const ngtcp2_frame *fr) { + return; + } + +- switch (fr->type) { ++ switch (fr->hd.type) { + case NGTCP2_FRAME_PADDING: + if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_PADDING_FRAME_OVERHEAD + 1) { + return; +@@ -771,7 +738,7 @@ void ngtcp2_qlog_write_frame(ngtcp2_qlog *qlog, const ngtcp2_frame *fr) { + case NGTCP2_FRAME_ACK_ECN: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_ACK_FRAME_BASE_OVERHEAD + +- (size_t)(fr->type == NGTCP2_FRAME_ACK_ECN ++ (size_t)(fr->ack.type == NGTCP2_FRAME_ACK_ECN + ? NGTCP2_QLOG_ACK_FRAME_ECN_OVERHEAD + : 0) + + NGTCP2_QLOG_ACK_FRAME_RANGE_OVERHEAD * (1 + fr->ack.rangecnt) + 1) { +@@ -935,7 +902,7 @@ void ngtcp2_qlog_pkt_sent_end(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd, + void ngtcp2_qlog_parameters_set_transport_params( + ngtcp2_qlog *qlog, const ngtcp2_transport_params *params, int server, + ngtcp2_qlog_side side) { +- uint8_t buf[1024]; ++ uint8_t buf[2048]; + uint8_t *p = buf; + const ngtcp2_preferred_addr *paddr; + const ngtcp2_sockaddr_in *sa_in; +@@ -945,7 +912,7 @@ void ngtcp2_qlog_parameters_set_transport_params( + return; + } + +- *p++ = '\x1e'; ++ *p++ = '\x1E'; + *p++ = '{'; + p = qlog_write_time(qlog, p); + p = write_verbatim( +@@ -1061,7 +1028,7 @@ void ngtcp2_qlog_metrics_updated(ngtcp2_qlog *qlog, + return; + } + +- *p++ = '\x1e'; ++ *p++ = '\x1E'; + *p++ = '{'; + p = qlog_write_time(qlog, p); + p = write_verbatim(p, ",\"name\":\"recovery:metrics_updated\",\"data\":{"); +@@ -1095,23 +1062,22 @@ void ngtcp2_qlog_metrics_updated(ngtcp2_qlog *qlog, + void ngtcp2_qlog_pkt_lost(ngtcp2_qlog *qlog, ngtcp2_rtb_entry *ent) { + uint8_t buf[256]; + uint8_t *p = buf; +- ngtcp2_pkt_hd hd = {0}; + + if (!qlog->write) { + return; + } + +- *p++ = '\x1e'; ++ *p++ = '\x1E'; + *p++ = '{'; + p = qlog_write_time(qlog, p); + p = write_verbatim( + p, ",\"name\":\"recovery:packet_lost\",\"data\":{\"header\":"); + +- hd.type = ent->hd.type; +- hd.flags = ent->hd.flags; +- hd.pkt_num = ent->hd.pkt_num; +- +- p = write_pkt_hd(p, &hd); ++ p = write_pkt_hd(p, &(ngtcp2_pkt_hd){ ++ .pkt_num = ent->hd.pkt_num, ++ .type = ent->hd.type, ++ .flags = ent->hd.flags, ++ }); + p = write_verbatim(p, "}}\n"); + + qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf, +@@ -1129,7 +1095,7 @@ void ngtcp2_qlog_retry_pkt_received(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd, + + ngtcp2_buf_init(&buf, rawbuf, sizeof(rawbuf)); + +- *buf.last++ = '\x1e'; ++ *buf.last++ = '\x1E'; + *buf.last++ = '{'; + buf.last = qlog_write_time(qlog, buf.last); + buf.last = write_verbatim( +@@ -1151,26 +1117,25 @@ void ngtcp2_qlog_retry_pkt_received(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd, + } + + void ngtcp2_qlog_stateless_reset_pkt_received( +- ngtcp2_qlog *qlog, const ngtcp2_pkt_stateless_reset *sr) { ++ ngtcp2_qlog *qlog, const ngtcp2_pkt_stateless_reset2 *sr) { + uint8_t buf[256]; + uint8_t *p = buf; +- ngtcp2_pkt_hd hd = {0}; + + if (!qlog->write) { + return; + } + +- hd.type = NGTCP2_PKT_STATELESS_RESET; +- +- *p++ = '\x1e'; ++ *p++ = '\x1E'; + *p++ = '{'; + p = qlog_write_time(qlog, p); + p = write_verbatim( + p, ",\"name\":\"transport:packet_received\",\"data\":{\"header\":"); +- p = write_pkt_hd(p, &hd); ++ p = write_pkt_hd(p, &(ngtcp2_pkt_hd){ ++ .type = NGTCP2_PKT_STATELESS_RESET, ++ }); + *p++ = ','; +- p = write_pair_hex(p, "stateless_reset_token", sr->stateless_reset_token, +- NGTCP2_STATELESS_RESET_TOKENLEN); ++ p = write_pair_hex(p, "stateless_reset_token", sr->token.data, ++ sizeof(sr->token.data)); + p = write_verbatim(p, "}}\n"); + + qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf, +@@ -1192,7 +1157,7 @@ void ngtcp2_qlog_version_negotiation_pkt_received(ngtcp2_qlog *qlog, + + ngtcp2_buf_init(&buf, rawbuf, sizeof(rawbuf)); + +- *buf.last++ = '\x1e'; ++ *buf.last++ = '\x1E'; + *buf.last++ = '{'; + buf.last = qlog_write_time(qlog, buf.last); + buf.last = write_verbatim( +@@ -1201,8 +1166,8 @@ void ngtcp2_qlog_version_negotiation_pkt_received(ngtcp2_qlog *qlog, + buf.last = write_verbatim(buf.last, ",\"supported_versions\":["); + + if (nsv) { +- if (ngtcp2_buf_left(&buf) < +- (sizeof("\"xxxxxxxx\",") - 1) * nsv - 1 + sizeof("]}}\n") - 1) { ++ if (ngtcp2_buf_left(&buf) < ngtcp2_strlen_lit("\"xxxxxxxx\",") * nsv - 1 + ++ ngtcp2_strlen_lit("]}}\n")) { + return; + } + +diff --git a/third_party/ngtcp2/lib/ngtcp2_qlog.h b/third_party/ngtcp2/lib/ngtcp2_qlog.h +index d2a5f1038c0..17668a50233 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_qlog.h ++++ b/third_party/ngtcp2/lib/ngtcp2_qlog.h +@@ -147,7 +147,7 @@ void ngtcp2_qlog_retry_pkt_received(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd, + * event for a received Stateless Reset packet. + */ + void ngtcp2_qlog_stateless_reset_pkt_received( +- ngtcp2_qlog *qlog, const ngtcp2_pkt_stateless_reset *sr); ++ ngtcp2_qlog *qlog, const ngtcp2_pkt_stateless_reset2 *sr); + + /* + * ngtcp2_qlog_version_negotiation_pkt_received writes packet_received +diff --git a/third_party/ngtcp2/lib/ngtcp2_range.c b/third_party/ngtcp2/lib/ngtcp2_range.c +index e8989153293..a949a657338 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_range.c ++++ b/third_party/ngtcp2/lib/ngtcp2_range.c +@@ -26,18 +26,22 @@ + #include "ngtcp2_macro.h" + + void ngtcp2_range_init(ngtcp2_range *r, uint64_t begin, uint64_t end) { +- r->begin = begin; +- r->end = end; ++ *r = (ngtcp2_range){ ++ .begin = begin, ++ .end = end, ++ }; + } + + ngtcp2_range ngtcp2_range_intersect(const ngtcp2_range *a, + const ngtcp2_range *b) { +- ngtcp2_range r = {0}; ++ ngtcp2_range r; + uint64_t begin = ngtcp2_max_uint64(a->begin, b->begin); + uint64_t end = ngtcp2_min_uint64(a->end, b->end); + + if (begin < end) { + ngtcp2_range_init(&r, begin, end); ++ } else { ++ r = (ngtcp2_range){0}; + } + + return r; +@@ -52,10 +56,14 @@ int ngtcp2_range_eq(const ngtcp2_range *a, const ngtcp2_range *b) { + void ngtcp2_range_cut(ngtcp2_range *left, ngtcp2_range *right, + const ngtcp2_range *a, const ngtcp2_range *b) { + /* Assume that b is included in a */ +- left->begin = a->begin; +- left->end = b->begin; +- right->begin = b->end; +- right->end = a->end; ++ *left = (ngtcp2_range){ ++ .begin = a->begin, ++ .end = b->begin, ++ }; ++ *right = (ngtcp2_range){ ++ .begin = b->end, ++ .end = a->end, ++ }; + } + + int ngtcp2_range_not_after(const ngtcp2_range *a, const ngtcp2_range *b) { +diff --git a/third_party/ngtcp2/lib/ngtcp2_ratelim.c b/third_party/ngtcp2/lib/ngtcp2_ratelim.c +new file mode 100644 +index 00000000000..d13ed9a14aa +--- /dev/null ++++ b/third_party/ngtcp2/lib/ngtcp2_ratelim.c +@@ -0,0 +1,84 @@ ++/* ++ * ngtcp2 ++ * ++ * Copyright (c) 2025 ngtcp2 contributors ++ * Copyright (c) 2023 nghttp2 contributors ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ */ ++#include "ngtcp2_ratelim.h" ++ ++#include ++ ++#include "ngtcp2_macro.h" ++ ++void ngtcp2_ratelim_init(ngtcp2_ratelim *rlim, uint64_t burst, uint64_t rate, ++ ngtcp2_tstamp ts) { ++ *rlim = (ngtcp2_ratelim){ ++ .burst = burst, ++ .rate = rate, ++ .tokens = burst, ++ .ts = ts, ++ }; ++} ++ ++/* ratelim_update updates rlim->tokens with the current |ts|. */ ++static void ratelim_update(ngtcp2_ratelim *rlim, ngtcp2_tstamp ts) { ++ uint64_t d, gain, gps; ++ ++ assert(ts >= rlim->ts); ++ ++ if (ts == rlim->ts) { ++ return; ++ } ++ ++ d = ts - rlim->ts; ++ rlim->ts = ts; ++ ++ if (rlim->rate > (UINT64_MAX - rlim->carry) / d) { ++ gain = UINT64_MAX; ++ } else { ++ gain = rlim->rate * d + rlim->carry; ++ } ++ ++ gps = gain / NGTCP2_SECONDS; ++ ++ if (gps < rlim->burst && rlim->tokens < rlim->burst - gps) { ++ rlim->tokens += gps; ++ rlim->carry = gain % NGTCP2_SECONDS; ++ ++ return; ++ } ++ ++ rlim->tokens = rlim->burst; ++ rlim->carry = 0; ++} ++ ++int ngtcp2_ratelim_drain(ngtcp2_ratelim *rlim, uint64_t n, ngtcp2_tstamp ts) { ++ ratelim_update(rlim, ts); ++ ++ if (rlim->tokens < n) { ++ return -1; ++ } ++ ++ rlim->tokens -= n; ++ ++ return 0; ++} +diff --git a/third_party/ngtcp2/lib/ngtcp2_ratelim.h b/third_party/ngtcp2/lib/ngtcp2_ratelim.h +new file mode 100644 +index 00000000000..231f5d1b018 +--- /dev/null ++++ b/third_party/ngtcp2/lib/ngtcp2_ratelim.h +@@ -0,0 +1,59 @@ ++/* ++ * ngtcp2 ++ * ++ * Copyright (c) 2025 ngtcp2 contributors ++ * Copyright (c) 2023 nghttp2 contributors ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ */ ++#ifndef NGTCP2_RATELIM_H ++#define NGTCP2_RATELIM_H ++ ++#ifdef HAVE_CONFIG_H ++# include ++#endif /* defined(HAVE_CONFIG_H) */ ++ ++#include ++ ++typedef struct ngtcp2_ratelim { ++ /* burst is the maximum number of tokens. */ ++ uint64_t burst; ++ /* rate is the rate of token generation measured by token / ++ second. */ ++ uint64_t rate; ++ /* tokens is the amount of tokens available to drain. */ ++ uint64_t tokens; ++ /* carry is the partial token gained in sub-second period. It is ++ added to the computation in the next update round. */ ++ uint64_t carry; ++ /* ts is the last timestamp that is known to this object. */ ++ ngtcp2_tstamp ts; ++} ngtcp2_ratelim; ++ ++/* ngtcp2_ratelim_init initializes |rlim| with the given ++ parameters. */ ++void ngtcp2_ratelim_init(ngtcp2_ratelim *rlim, uint64_t burst, uint64_t rate, ++ ngtcp2_tstamp ts); ++ ++/* ngtcp2_ratelim_drain drains |n| from rlim->tokens. It returns 0 if ++ it succeeds, or -1. */ ++int ngtcp2_ratelim_drain(ngtcp2_ratelim *rlim, uint64_t n, ngtcp2_tstamp ts); ++ ++#endif /* !defined(NGTCP2_RATELIM_H) */ +diff --git a/third_party/ngtcp2/lib/ngtcp2_ringbuf.c b/third_party/ngtcp2/lib/ngtcp2_ringbuf.c +index 353afca4d48..40c25f2a3d1 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_ringbuf.c ++++ b/third_party/ngtcp2/lib/ngtcp2_ringbuf.c +@@ -32,18 +32,8 @@ + #include "ngtcp2_macro.h" + + #ifndef NDEBUG +-static int ispow2(size_t n) { +-# if defined(_MSC_VER) && !defined(__clang__) && \ +- (defined(_M_ARM) || (defined(_M_ARM64) && _MSC_VER < 1941)) +- return n && !(n & (n - 1)); +-# elif defined(WIN32) +- return 1 == __popcnt((unsigned int)n); +-# else /* !((defined(_MSC_VER) && !defined(__clang__) && (defined(_M_ARM) || \ +- (defined(_M_ARM64) && _MSC_VER < 1941))) || defined(WIN32)) */ +- return 1 == __builtin_popcount((unsigned int)n); +-# endif /* !((defined(_MSC_VER) && !defined(__clang__) && (defined(_M_ARM) || \ +- (defined(_M_ARM64) && _MSC_VER < 1941))) || defined(WIN32)) */ +-} ++/* Power-of-two test; simple portable bit trick. */ ++static int ispow2(size_t n) { return n && !(n & (n - 1)); } + #endif /* !defined(NDEBUG) */ + + int ngtcp2_ringbuf_init(ngtcp2_ringbuf *rb, size_t nmemb, size_t size, +diff --git a/third_party/ngtcp2/lib/ngtcp2_ringbuf.h b/third_party/ngtcp2/lib/ngtcp2_ringbuf.h +index d490524805b..73efb5255fc 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_ringbuf.h ++++ b/third_party/ngtcp2/lib/ngtcp2_ringbuf.h +@@ -110,7 +110,9 @@ void ngtcp2_ringbuf_resize(ngtcp2_ringbuf *rb, size_t len); + void *ngtcp2_ringbuf_get(const ngtcp2_ringbuf *rb, size_t offset); + + /* ngtcp2_ringbuf_len returns the number of elements stored. */ +-#define ngtcp2_ringbuf_len(RB) ((RB)->len) ++static inline size_t ngtcp2_ringbuf_len(const ngtcp2_ringbuf *rb) { ++ return rb->len; ++} + + /* ngtcp2_ringbuf_full returns nonzero if |rb| is full. */ + int ngtcp2_ringbuf_full(const ngtcp2_ringbuf *rb); +diff --git a/third_party/ngtcp2/lib/ngtcp2_rob.c b/third_party/ngtcp2/lib/ngtcp2_rob.c +index 853f1d650ea..20eae1d8819 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_rob.c ++++ b/third_party/ngtcp2/lib/ngtcp2_rob.c +@@ -36,8 +36,13 @@ int ngtcp2_rob_gap_new(ngtcp2_rob_gap **pg, uint64_t begin, uint64_t end, + return NGTCP2_ERR_NOMEM; + } + +- (*pg)->range.begin = begin; +- (*pg)->range.end = end; ++ **pg = (ngtcp2_rob_gap){ ++ .range = ++ { ++ .begin = begin, ++ .end = end, ++ }, ++ }; + + return 0; + } +@@ -53,9 +58,14 @@ int ngtcp2_rob_data_new(ngtcp2_rob_data **pd, uint64_t offset, size_t chunk, + return NGTCP2_ERR_NOMEM; + } + +- (*pd)->range.begin = offset; +- (*pd)->range.end = offset + chunk; +- (*pd)->begin = (uint8_t *)(*pd) + sizeof(ngtcp2_rob_data); ++ **pd = (ngtcp2_rob_data){ ++ .range = ++ { ++ .begin = offset, ++ .end = offset + chunk, ++ }, ++ .begin = (uint8_t *)(*pd) + sizeof(ngtcp2_rob_data), ++ }; + + return 0; + } +@@ -162,8 +172,8 @@ static int rob_write_data(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data, + return 0; + } + +-int ngtcp2_rob_push(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data, +- size_t datalen) { ++ngtcp2_ssize ngtcp2_rob_push(ngtcp2_rob *rob, uint64_t offset, ++ const uint8_t *data, size_t datalen) { + int rv; + ngtcp2_rob_gap *g; + ngtcp2_range m, l, r; +@@ -172,6 +182,8 @@ int ngtcp2_rob_push(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data, + .end = offset + datalen, + }; + ngtcp2_ksl_it it; ++ ngtcp2_ssize nwrite = 0; ++ size_t mlen; + + it = ngtcp2_ksl_lower_bound_search(&rob->gapksl, &q, + ngtcp2_ksl_range_exclusive_search); +@@ -180,7 +192,9 @@ int ngtcp2_rob_push(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data, + g = ngtcp2_ksl_it_get(&it); + + m = ngtcp2_range_intersect(&q, &g->range); +- if (!ngtcp2_range_len(&m)) { ++ ++ mlen = (size_t)ngtcp2_range_len(&m); ++ if (mlen == 0) { + break; + } + +@@ -188,12 +202,13 @@ int ngtcp2_rob_push(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data, + ngtcp2_ksl_remove_hint(&rob->gapksl, &it, &it, &g->range); + ngtcp2_rob_gap_del(g, rob->mem); + +- rv = rob_write_data(rob, m.begin, data + (m.begin - offset), +- (size_t)ngtcp2_range_len(&m)); ++ rv = rob_write_data(rob, m.begin, data + (m.begin - offset), mlen); + if (rv != 0) { + return rv; + } + ++ nwrite += (ngtcp2_ssize)mlen; ++ + continue; + } + +@@ -222,16 +237,17 @@ int ngtcp2_rob_push(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data, + g->range = r; + } + +- rv = rob_write_data(rob, m.begin, data + (m.begin - offset), +- (size_t)ngtcp2_range_len(&m)); ++ rv = rob_write_data(rob, m.begin, data + (m.begin - offset), mlen); + if (rv != 0) { + return rv; + } + ++ nwrite += (ngtcp2_ssize)mlen; ++ + ngtcp2_ksl_it_next(&it); + } + +- return 0; ++ return nwrite; + } + + void ngtcp2_rob_remove_prefix(ngtcp2_rob *rob, uint64_t offset) { +@@ -248,9 +264,11 @@ void ngtcp2_rob_remove_prefix(ngtcp2_rob *rob, uint64_t offset) { + } + + if (offset < g->range.end) { +- ngtcp2_range r = {offset, g->range.end}; +- +- ngtcp2_ksl_update_key(&rob->gapksl, &g->range, &r); ++ ngtcp2_ksl_update_key(&rob->gapksl, &g->range, ++ &(ngtcp2_range){ ++ .begin = offset, ++ .end = g->range.end, ++ }); + g->range.begin = offset; + + break; +diff --git a/third_party/ngtcp2/lib/ngtcp2_rob.h b/third_party/ngtcp2/lib/ngtcp2_rob.h +index d53b5160b10..60a1c5b46a0 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_rob.h ++++ b/third_party/ngtcp2/lib/ngtcp2_rob.h +@@ -138,14 +138,14 @@ void ngtcp2_rob_free(ngtcp2_rob *rob); + * ngtcp2_rob_push adds new data pointed by |data| of length |datalen| + * at the stream offset |offset|. + * +- * This function returns 0 if it succeeds, or one of the following +- * negative error codes: ++ * This function returns the number of data newly buffered if it ++ * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +-int ngtcp2_rob_push(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data, +- size_t datalen); ++ngtcp2_ssize ngtcp2_rob_push(ngtcp2_rob *rob, uint64_t offset, ++ const uint8_t *data, size_t datalen); + + /* + * ngtcp2_rob_remove_prefix removes gap up to |offset|, exclusive. It +diff --git a/third_party/ngtcp2/lib/ngtcp2_rst.c b/third_party/ngtcp2/lib/ngtcp2_rst.c +index 181691f3e69..89b395cd954 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_rst.c ++++ b/third_party/ngtcp2/lib/ngtcp2_rst.c +@@ -32,17 +32,11 @@ + #include "ngtcp2_conn_stat.h" + + void ngtcp2_rs_init(ngtcp2_rs *rs) { +- rs->interval = UINT64_MAX; +- rs->delivered = 0; +- rs->prior_delivered = 0; +- rs->prior_ts = UINT64_MAX; +- rs->tx_in_flight = 0; +- rs->lost = 0; +- rs->prior_lost = 0; +- rs->send_elapsed = 0; +- rs->ack_elapsed = 0; +- rs->last_end_seq = -1; +- rs->is_app_limited = 0; ++ *rs = (ngtcp2_rs){ ++ .interval = UINT64_MAX, ++ .prior_ts = UINT64_MAX, ++ .last_end_seq = -1, ++ }; + } + + void ngtcp2_rst_init(ngtcp2_rst *rst) { +@@ -58,19 +52,29 @@ void ngtcp2_rst_reset(ngtcp2_rst *rst) { + rst->app_limited = 0; + rst->is_cwnd_limited = 0; + rst->lost = 0; +- rst->valid_after_seq = rst->last_seq; ++} ++ ++void ngtcp2_rst_reset_rate_sample(ngtcp2_rst *rst, ngtcp2_conn_stat *cstat) { ++ ngtcp2_rs *rs = &rst->rs; ++ ++ rs->interval = UINT64_MAX; ++ rs->prior_ts = UINT64_MAX; ++ ++ cstat->delivery_rate_sec = 0; + } + + void ngtcp2_rst_on_pkt_sent(ngtcp2_rst *rst, ngtcp2_rtb_entry *ent, + const ngtcp2_conn_stat *cstat) { +- if (cstat->bytes_in_flight == 0) { ++ /* cstat->bytes_in_flight includes ent->pktlen. If they are the ++ same, there is no in-flight packets. */ ++ if (cstat->bytes_in_flight == ent->pktlen) { + rst->first_sent_ts = rst->delivered_ts = ent->ts; + } + ent->rst.first_sent_ts = rst->first_sent_ts; + ent->rst.delivered_ts = rst->delivered_ts; + ent->rst.delivered = rst->delivered; + ent->rst.is_app_limited = rst->app_limited != 0; +- ent->rst.tx_in_flight = cstat->bytes_in_flight + ent->pktlen; ++ ent->rst.tx_in_flight = cstat->bytes_in_flight; + ent->rst.lost = rst->lost; + ent->rst.end_seq = ++rst->last_seq; + } +@@ -89,10 +93,8 @@ void ngtcp2_rst_on_ack_recv(ngtcp2_rst *rst, ngtcp2_conn_stat *cstat) { + rs->interval = ngtcp2_max_uint64(rs->send_elapsed, rs->ack_elapsed); + + rs->delivered = rst->delivered - rs->prior_delivered; +- rs->lost = rst->lost - rs->prior_lost; + + if (rs->interval < cstat->min_rtt) { +- rs->interval = UINT64_MAX; + return; + } + +@@ -103,31 +105,23 @@ void ngtcp2_rst_on_ack_recv(ngtcp2_rst *rst, ngtcp2_conn_stat *cstat) { + cstat->delivery_rate_sec = rs->delivered * NGTCP2_SECONDS / rs->interval; + } + +-static int rst_is_newest_pkt(const ngtcp2_rst *rst, const ngtcp2_rtb_entry *ent, +- const ngtcp2_rs *rs) { +- return ent->ts > rst->first_sent_ts || +- (ent->ts == rst->first_sent_ts && ent->rst.end_seq > rs->last_end_seq); ++static int is_newest_pkt(const ngtcp2_rtb_entry *ent, const ngtcp2_rs *rs) { ++ return ent->rst.end_seq > rs->last_end_seq; + } + + void ngtcp2_rst_update_rate_sample(ngtcp2_rst *rst, const ngtcp2_rtb_entry *ent, + ngtcp2_tstamp ts) { + ngtcp2_rs *rs = &rst->rs; + +- if (ent->rst.end_seq <= rst->valid_after_seq) { +- return; +- } +- + rst->delivered += ent->pktlen; + rst->delivered_ts = ts; + +- if (rs->prior_ts == UINT64_MAX || rst_is_newest_pkt(rst, ent, rs)) { ++ if (rs->prior_ts == UINT64_MAX || is_newest_pkt(ent, rs)) { + rs->prior_delivered = ent->rst.delivered; + rs->prior_ts = ent->rst.delivered_ts; + rs->is_app_limited = ent->rst.is_app_limited; + rs->send_elapsed = ent->ts - ent->rst.first_sent_ts; + rs->ack_elapsed = rst->delivered_ts - ent->rst.delivered_ts; +- rs->tx_in_flight = ent->rst.tx_in_flight; +- rs->prior_lost = ent->rst.lost; + rs->last_end_seq = ent->rst.end_seq; + rst->first_sent_ts = ent->ts; + } +diff --git a/third_party/ngtcp2/lib/ngtcp2_rst.h b/third_party/ngtcp2/lib/ngtcp2_rst.h +index c2580306cc5..b0bc2d06c9c 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_rst.h ++++ b/third_party/ngtcp2/lib/ngtcp2_rst.h +@@ -48,7 +48,6 @@ typedef struct ngtcp2_rs { + ngtcp2_tstamp prior_ts; + uint64_t tx_in_flight; + uint64_t lost; +- uint64_t prior_lost; + ngtcp2_duration send_elapsed; + ngtcp2_duration ack_elapsed; + int64_t last_end_seq; +@@ -73,10 +72,6 @@ typedef struct ngtcp2_rst { + across all packet number spaces, we can replace this with a + packet number. */ + int64_t last_seq; +- /* valid_after_seq is the sequence number, and ignore a packet if +- the sequence number of the packet is less than or equal to this +- number. */ +- int64_t valid_after_seq; + int is_cwnd_limited; + } ngtcp2_rst; + +@@ -84,6 +79,8 @@ void ngtcp2_rst_init(ngtcp2_rst *rst); + + void ngtcp2_rst_reset(ngtcp2_rst *rst); + ++void ngtcp2_rst_reset_rate_sample(ngtcp2_rst *rst, ngtcp2_conn_stat *cstat); ++ + void ngtcp2_rst_on_pkt_sent(ngtcp2_rst *rst, ngtcp2_rtb_entry *ent, + const ngtcp2_conn_stat *cstat); + void ngtcp2_rst_on_ack_recv(ngtcp2_rst *rst, ngtcp2_conn_stat *cstat); +diff --git a/third_party/ngtcp2/lib/ngtcp2_rtb.c b/third_party/ngtcp2/lib/ngtcp2_rtb.c +index 101dcaa99f8..67aaa049302 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_rtb.c ++++ b/third_party/ngtcp2/lib/ngtcp2_rtb.c +@@ -43,16 +43,19 @@ ngtcp2_objalloc_def(rtb_entry, ngtcp2_rtb_entry, oplent) + static void rtb_entry_init(ngtcp2_rtb_entry *ent, const ngtcp2_pkt_hd *hd, + ngtcp2_frame_chain *frc, ngtcp2_tstamp ts, + size_t pktlen, uint16_t flags) { +- memset(ent, 0, sizeof(*ent)); +- +- ent->hd.pkt_num = hd->pkt_num; +- ent->hd.type = hd->type; +- ent->hd.flags = hd->flags; +- ent->frc = frc; +- ent->ts = ts; +- ent->lost_ts = UINT64_MAX; +- ent->pktlen = pktlen; +- ent->flags = flags; ++ *ent = (ngtcp2_rtb_entry){ ++ .hd = ++ { ++ .pkt_num = hd->pkt_num, ++ .type = hd->type, ++ .flags = hd->flags, ++ }, ++ .frc = frc, ++ .ts = ts, ++ .lost_ts = UINT64_MAX, ++ .pktlen = pktlen, ++ .flags = flags, ++ }; + } + + int ngtcp2_rtb_entry_objalloc_new(ngtcp2_rtb_entry **pent, +@@ -125,13 +128,12 @@ void ngtcp2_rtb_free(ngtcp2_rtb *rtb) { + + static void rtb_on_add(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, + ngtcp2_conn_stat *cstat) { +- ngtcp2_rst_on_pkt_sent(rtb->rst, ent, cstat); +- + assert(rtb->cc_pkt_num <= ent->hd.pkt_num); + + cstat->bytes_in_flight += ent->pktlen; + rtb->cc_bytes_in_flight += ent->pktlen; + ++ ngtcp2_rst_on_pkt_sent(rtb->rst, ent, cstat); + ngtcp2_rst_update_app_limited(rtb->rst, cstat); + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) { +@@ -198,10 +200,10 @@ static size_t rtb_on_remove(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, + } + + /* NGTCP2_RECLAIM_FLAG_NONE indicates that no flag is set. */ +-#define NGTCP2_RECLAIM_FLAG_NONE 0x00u ++#define NGTCP2_RECLAIM_FLAG_NONE 0x00U + /* NGTCP2_RECLAIM_FLAG_ON_LOSS indicates that frames are reclaimed + because of the packet loss.*/ +-#define NGTCP2_RECLAIM_FLAG_ON_LOSS 0x01u ++#define NGTCP2_RECLAIM_FLAG_ON_LOSS 0x01U + + /* + * rtb_reclaim_frame copies and queues frames included in |ent| for +@@ -231,7 +233,7 @@ static ngtcp2_ssize rtb_reclaim_frame(ngtcp2_rtb *rtb, uint8_t flags, + continue; + } + +- switch (frc->fr.type) { ++ switch (frc->fr.hd.type) { + case NGTCP2_FRAME_STREAM: + strm = ngtcp2_conn_find_stream(conn, fr->stream.stream_id); + if (strm == NULL || (strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM)) { +@@ -269,7 +271,12 @@ static ngtcp2_ssize rtb_reclaim_frame(ngtcp2_rtb *rtb, uint8_t flags, + return rv; + } + +- nfrc->fr = *fr; ++ nfrc->fr.stream.type = fr->stream.type; ++ nfrc->fr.stream.flags = fr->stream.flags; ++ nfrc->fr.stream.fin = fr->stream.fin; ++ nfrc->fr.stream.stream_id = fr->stream.stream_id; ++ nfrc->fr.stream.offset = fr->stream.offset; ++ nfrc->fr.stream.datacnt = fr->stream.datacnt; + ngtcp2_vec_copy(nfrc->fr.stream.data, fr->stream.data, + fr->stream.datacnt); + +@@ -312,7 +319,12 @@ static ngtcp2_ssize rtb_reclaim_frame(ngtcp2_rtb *rtb, uint8_t flags, + return rv; + } + +- nfrc->fr = *fr; ++ nfrc->fr.stream.type = fr->stream.type; ++ nfrc->fr.stream.flags = 0; ++ nfrc->fr.stream.fin = 0; ++ nfrc->fr.stream.stream_id = 0; ++ nfrc->fr.stream.offset = fr->stream.offset; ++ nfrc->fr.stream.datacnt = fr->stream.datacnt; + ngtcp2_vec_copy(nfrc->fr.stream.data, fr->stream.data, + fr->stream.datacnt); + +@@ -413,7 +425,7 @@ static int conn_process_lost_datagram(ngtcp2_conn *conn, + int rv; + + for (frc = ent->frc; frc; frc = frc->next) { +- switch (frc->fr.type) { ++ switch (frc->fr.hd.type) { + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + assert(conn->callbacks.lost_datagram); +@@ -451,19 +463,28 @@ static int rtb_on_pkt_lost(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, + if (ent->flags & + (NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE | NGTCP2_RTB_ENTRY_FLAG_SKIP)) { + ++rtb->num_lost_ignore_pkts; +- } else if (rtb->cc->on_pkt_lost) { +- cc->on_pkt_lost(cc, cstat, +- ngtcp2_cc_pkt_init(&pkt, ent->hd.pkt_num, ent->pktlen, +- pktns->id, ent->ts, ent->rst.lost, +- ent->rst.tx_in_flight, +- ent->rst.is_app_limited), +- ts); ++ } else { ++ ++cstat->pkt_lost; ++ cstat->bytes_lost += ent->pktlen; ++ ++ if (ent->hd.pkt_num >= rtb->cc_pkt_num) { ++ rtb->rst->lost += ent->pktlen; ++ ++ if (rtb->cc->on_pkt_lost) { ++ cc->on_pkt_lost(cc, cstat, ++ ngtcp2_cc_pkt_init(&pkt, ent->hd.pkt_num, ent->pktlen, ++ pktns->id, ent->ts, ent->rst.lost, ++ ent->rst.tx_in_flight, ++ ent->rst.is_app_limited), ++ ts); ++ } ++ } + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED) { +- ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_LDC, +- "pkn=%" PRId64 " has already been reclaimed on PTO", +- ent->hd.pkt_num); ++ ngtcp2_log_infof(rtb->log, NGTCP2_LOG_EVENT_LDC, ++ "pkn=%" PRId64 " has already been reclaimed on PTO", ++ ent->hd.pkt_num); + assert(!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED)); + assert(UINT64_MAX == ent->lost_ts); + } else { +@@ -520,9 +541,10 @@ static void rtb_remove(ngtcp2_rtb *rtb, ngtcp2_ksl_it *it, + int rv; + (void)rv; + ++ rtb_on_remove(rtb, ent, cstat); ++ + rv = ngtcp2_ksl_remove_hint(&rtb->ents, it, it, &ent->hd.pkt_num); + assert(0 == rv); +- rtb_on_remove(rtb, ent, cstat); + + assert(ent->next == NULL); + +@@ -591,7 +613,7 @@ static int process_acked_pkt(ngtcp2_rtb_entry *ent, ngtcp2_conn *conn, + frc->binder->flags |= NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK; + } + +- switch (frc->fr.type) { ++ switch (frc->fr.hd.type) { + case NGTCP2_FRAME_STREAM: + strm = ngtcp2_conn_find_stream(conn, frc->fr.stream.stream_id); + if (strm == NULL) { +@@ -704,6 +726,8 @@ static void rtb_on_pkt_acked(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, + ngtcp2_cc *cc = rtb->cc; + ngtcp2_cc_pkt pkt; + ++ assert(ent->hd.pkt_num >= rtb->cc_pkt_num); ++ + ngtcp2_rst_update_rate_sample(rtb->rst, ent, ts); + + if (cc->on_pkt_acked) { +@@ -724,8 +748,7 @@ static void rtb_on_pkt_acked(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, + static void conn_verify_ecn(ngtcp2_conn *conn, ngtcp2_pktns *pktns, + ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_ack *fr, size_t ecn_acked, +- ngtcp2_tstamp largest_pkt_sent_ts, +- ngtcp2_tstamp ts) { ++ const ngtcp2_cc_ack *cc_ack, ngtcp2_tstamp ts) { + if (conn->tx.ecn.state == NGTCP2_ECN_STATE_FAILED) { + return; + } +@@ -752,9 +775,9 @@ static void conn_verify_ecn(ngtcp2_conn *conn, ngtcp2_pktns *pktns, + } + + if (fr->type == NGTCP2_FRAME_ACK_ECN) { +- if (cc->congestion_event && largest_pkt_sent_ts != UINT64_MAX && ++ if (cc->congestion_event && cc_ack->largest_pkt_sent_ts != UINT64_MAX && + fr->ecn.ce > pktns->acktr.ecn.ack.ce) { +- cc->congestion_event(cc, cstat, largest_pkt_sent_ts, 0, ts); ++ cc->congestion_event(cc, cstat, cc_ack->largest_pkt_sent_ts, cc_ack, ts); + } + + pktns->acktr.ecn.ack.ect0 = fr->ecn.ect0; +@@ -763,7 +786,7 @@ static void conn_verify_ecn(ngtcp2_conn *conn, ngtcp2_pktns *pktns, + } + } + +-static int rtb_detect_lost_pkt(ngtcp2_rtb *rtb, uint64_t *ppkt_lost, ++static int rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_cc_ack *cc_ack, + ngtcp2_conn *conn, ngtcp2_pktns *pktns, + ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts); + +@@ -777,24 +800,23 @@ ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr, + int rv; + ngtcp2_ksl_it it; + size_t num_acked = 0; +- ngtcp2_tstamp largest_pkt_sent_ts = UINT64_MAX; + int64_t pkt_num; + ngtcp2_cc *cc = rtb->cc; + ngtcp2_rtb_entry *acked_ent = NULL; + int ack_eliciting_pkt_acked = 0; + size_t ecn_acked = 0; + int verify_ecn = 0; +- ngtcp2_cc_ack cc_ack = {0}; ++ ngtcp2_cc_ack cc_ack = { ++ .largest_pkt_sent_ts = UINT64_MAX, ++ .rtt = UINT64_MAX, ++ }; + size_t num_lost_pkts = rtb->num_lost_pkts - rtb->num_lost_ignore_pkts; + +- cc_ack.prior_bytes_in_flight = cstat->bytes_in_flight; +- cc_ack.rtt = UINT64_MAX; +- + if (conn && (conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED) && + (conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR) && + largest_ack >= conn->pktns.crypto.tx.ckm->pkt_num) { +- conn->flags &= (uint32_t) ~(NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED | +- NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR); ++ conn->flags &= (uint32_t)~(NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED | ++ NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR); + conn->crypto.key_update.confirmed_ts = ts; + + ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_CRY, "key update confirmed"); +@@ -805,12 +827,17 @@ ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr, + verify_ecn = 1; + } + ++ ngtcp2_rst_reset_rate_sample(rtb->rst, cstat); ++ + /* Assume that ngtcp2_pkt_validate_ack(fr) returns 0 */ + it = ngtcp2_ksl_lower_bound(&rtb->ents, &largest_ack); + if (ngtcp2_ksl_it_end(&it)) { + if (conn && verify_ecn) { +- conn_verify_ecn(conn, pktns, rtb->cc, cstat, fr, ecn_acked, +- largest_pkt_sent_ts, ts); ++ conn_verify_ecn(conn, pktns, rtb->cc, cstat, fr, ecn_acked, &cc_ack, ts); ++ } ++ ++ if (cc->on_ack_recv) { ++ cc->on_ack_recv(cc, cstat, &cc_ack, ts); + } + + return 0; +@@ -834,8 +861,8 @@ ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr, + goto fail; + } + +- if (largest_ack == pkt_num) { +- largest_pkt_sent_ts = ent->ts; ++ if (rtb->largest_acked_tx_pkt_num == pkt_num) { ++ cc_ack.largest_pkt_sent_ts = ent->ts; + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) { +@@ -843,7 +870,6 @@ ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr, + } + + rtb_remove(rtb, &it, &acked_ent, ent, cstat); +- ++num_acked; + } + + for (i = 0; i < fr->rangecnt; ++i) { +@@ -873,18 +899,14 @@ ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr, + } + + rtb_remove(rtb, &it, &acked_ent, ent, cstat); +- ++num_acked; + } + } + +- if (largest_pkt_sent_ts != UINT64_MAX && ack_eliciting_pkt_acked) { +- cc_ack.rtt = +- ngtcp2_max_uint64(pkt_ts - largest_pkt_sent_ts, NGTCP2_NANOSECONDS); ++ if (cc_ack.largest_pkt_sent_ts != UINT64_MAX && ack_eliciting_pkt_acked) { ++ cc_ack.rtt = ngtcp2_max_uint64(pkt_ts - cc_ack.largest_pkt_sent_ts, ++ NGTCP2_NANOSECONDS); + +- rv = ngtcp2_conn_update_rtt(conn, cc_ack.rtt, fr->ack_delay_unscaled, ts); +- if (rv == 0 && cc->new_rtt_sample) { +- cc->new_rtt_sample(cc, cstat, ts); +- } ++ ngtcp2_conn_update_rtt(conn, cc_ack.rtt, fr->ack_delay_unscaled, ts); + } + + if (conn) { +@@ -904,23 +926,22 @@ ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr, + + cc_ack.bytes_delivered += ent->pktlen; + cc_ack.pkt_delivered = ent->rst.delivered; ++ ++ rtb_on_pkt_acked(rtb, ent, cstat, pktns, ts); ++ ++ ++num_acked; + } + +- rtb_on_pkt_acked(rtb, ent, cstat, pktns, ts); + acked_ent = ent->next; + ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc, + rtb->frc_objalloc, rtb->mem); + } +- +- if (verify_ecn) { +- conn_verify_ecn(conn, pktns, rtb->cc, cstat, fr, ecn_acked, +- largest_pkt_sent_ts, ts); +- } + } else { + /* For unit tests */ + for (ent = acked_ent; ent; ent = acked_ent) { + rtb_on_pkt_acked(rtb, ent, cstat, pktns, ts); + acked_ent = ent->next; ++ ++num_acked; + ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc, + rtb->frc_objalloc, rtb->mem); + } +@@ -935,17 +956,18 @@ ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr, + ngtcp2_rst_on_ack_recv(rtb->rst, cstat); + + if (conn) { +- rv = rtb_detect_lost_pkt(rtb, &cc_ack.bytes_lost, conn, pktns, cstat, ts); ++ rv = rtb_detect_lost_pkt(rtb, &cc_ack, conn, pktns, cstat, ts); + if (rv != 0) { + return rv; + } + } + } + +- rtb->rst->lost += cc_ack.bytes_lost; ++ if (conn && verify_ecn) { ++ conn_verify_ecn(conn, pktns, rtb->cc, cstat, fr, ecn_acked, &cc_ack, ts); ++ } + +- cc_ack.largest_pkt_sent_ts = largest_pkt_sent_ts; +- if (num_acked && cc->on_ack_recv) { ++ if (cc->on_ack_recv) { + cc->on_ack_recv(cc, cstat, &cc_ack, ts); + } + +@@ -1011,7 +1033,11 @@ static int conn_all_ecn_pkt_lost(ngtcp2_conn *conn) { + pktns->tx.ecn.validation_pkt_sent == pktns->tx.ecn.validation_pkt_lost; + } + +-static int rtb_detect_lost_pkt(ngtcp2_rtb *rtb, uint64_t *ppkt_lost, ++/* ++ * This function assigns the number of bytes lost to ++ * |cc_ack|->bytes_lost if any. ++ */ ++static int rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_cc_ack *cc_ack, + ngtcp2_conn *conn, ngtcp2_pktns *pktns, + ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts) { + ngtcp2_rtb_entry *ent; +@@ -1129,8 +1155,10 @@ static int rtb_detect_lost_pkt(ngtcp2_rtb *rtb, uint64_t *ppkt_lost, + break; + } + ++ cc_ack->bytes_lost = bytes_lost; ++ + if (cc->congestion_event) { +- cc->congestion_event(cc, cstat, latest_ts, bytes_lost, ts); ++ cc->congestion_event(cc, cstat, latest_ts, cc_ack, ts); + } + + loss_window = latest_ts - oldest_ts; +@@ -1143,10 +1171,10 @@ static int rtb_detect_lost_pkt(ngtcp2_rtb *rtb, uint64_t *ppkt_lost, + */ + if (pktns->id == NGTCP2_PKTNS_ID_APPLICATION && loss_window && + loss_window >= congestion_period) { +- ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_LDC, +- "persistent congestion loss_window=%" PRIu64 +- " congestion_period=%" PRIu64, +- loss_window, congestion_period); ++ ngtcp2_log_infof(rtb->log, NGTCP2_LOG_EVENT_LDC, ++ "persistent congestion loss_window=%" PRIu64 ++ " congestion_period=%" PRIu64, ++ loss_window, congestion_period); + + /* Reset min_rtt, srtt, and rttvar here. Next new RTT + sample will be used to recalculate these values. */ +@@ -1166,18 +1194,18 @@ static int rtb_detect_lost_pkt(ngtcp2_rtb *rtb, uint64_t *ppkt_lost, + + ngtcp2_rtb_remove_excessive_lost_pkt(rtb, (size_t)pkt_thres); + +- if (ppkt_lost) { +- *ppkt_lost = bytes_lost; +- } +- + return 0; + } + + int ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_conn *conn, + ngtcp2_pktns *pktns, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { +- return rtb_detect_lost_pkt(rtb, /* ppkt_lost = */ NULL, conn, pktns, cstat, +- ts); ++ return rtb_detect_lost_pkt(rtb, ++ &(ngtcp2_cc_ack){ ++ .largest_pkt_sent_ts = UINT64_MAX, ++ .rtt = UINT64_MAX, ++ }, ++ conn, pktns, cstat, ts); + } + + void ngtcp2_rtb_remove_excessive_lost_pkt(ngtcp2_rtb *rtb, size_t n) { +@@ -1193,8 +1221,8 @@ void ngtcp2_rtb_remove_excessive_lost_pkt(ngtcp2_rtb *rtb, size_t n) { + + assert(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED); + +- ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_LDC, +- "removing stale lost pkn=%" PRId64, ent->hd.pkt_num); ++ ngtcp2_log_infof(rtb->log, NGTCP2_LOG_EVENT_LDC, ++ "removing stale lost pkn=%" PRId64, ent->hd.pkt_num); + + --rtb->num_lost_pkts; + +@@ -1230,13 +1258,14 @@ void ngtcp2_rtb_remove_expired_lost_pkt(ngtcp2_rtb *rtb, + ngtcp2_ksl_it_prev(&it); + ent = ngtcp2_ksl_it_get(&it); + +- if (!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED) || +- ts - ent->lost_ts < timeout) { ++ assert(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED); ++ ++ if (ts - ent->lost_ts < timeout) { + return; + } + +- ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_LDC, +- "removing stale lost pkn=%" PRId64, ent->hd.pkt_num); ++ ngtcp2_log_infof(rtb->log, NGTCP2_LOG_EVENT_LDC, ++ "removing stale lost pkn=%" PRId64, ent->hd.pkt_num); + + --rtb->num_lost_pkts; + +@@ -1280,7 +1309,7 @@ static int rtb_reclaim_frame_on_retry(ngtcp2_rtb *rtb, ngtcp2_conn *conn, + int rv; + + for (; *pfrc;) { +- switch ((*pfrc)->fr.type) { ++ switch ((*pfrc)->fr.hd.type) { + case NGTCP2_FRAME_STREAM: + frc = *pfrc; + +@@ -1388,15 +1417,22 @@ int ngtcp2_rtb_reclaim_on_retry(ngtcp2_rtb *rtb, ngtcp2_conn *conn, + assert(!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE)); + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED) { +- ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_LDC, +- "pkn=%" PRId64 " has already been reclaimed on PTO", +- ent->hd.pkt_num); ++ ngtcp2_log_infof(rtb->log, NGTCP2_LOG_EVENT_LDC, ++ "pkn=%" PRId64 " has already been reclaimed on PTO", ++ ent->hd.pkt_num); ++ ++ ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc, ++ rtb->frc_objalloc, rtb->mem); ++ + continue; + } + + if (!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE) && + (!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_DATAGRAM) || + !conn->callbacks.lost_datagram)) { ++ ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc, ++ rtb->frc_objalloc, rtb->mem); ++ + continue; + } + +diff --git a/third_party/ngtcp2/lib/ngtcp2_rtb.h b/third_party/ngtcp2/lib/ngtcp2_rtb.h +index 14684a458a6..62bcb381fa1 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_rtb.h ++++ b/third_party/ngtcp2/lib/ngtcp2_rtb.h +@@ -48,41 +48,41 @@ typedef struct ngtcp2_conn_stat ngtcp2_conn_stat; + typedef struct ngtcp2_frame_chain ngtcp2_frame_chain; + + /* NGTCP2_RTB_ENTRY_FLAG_NONE indicates that no flag is set. */ +-#define NGTCP2_RTB_ENTRY_FLAG_NONE 0x00u ++#define NGTCP2_RTB_ENTRY_FLAG_NONE 0x00U + /* NGTCP2_RTB_ENTRY_FLAG_PROBE indicates that the entry includes a + probe packet. */ +-#define NGTCP2_RTB_ENTRY_FLAG_PROBE 0x01u ++#define NGTCP2_RTB_ENTRY_FLAG_PROBE 0x01U + /* NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE indicates that the entry + includes a frame which must be retransmitted until it is + acknowledged. In most cases, this flag is used along with + NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING and + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING. */ +-#define NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE 0x02u ++#define NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE 0x02U + /* NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING indicates that the entry + elicits acknowledgement. */ +-#define NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING 0x04u ++#define NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING 0x04U + /* NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED indicates that the packet has + been reclaimed on PTO. It is not marked lost yet and still + consumes congestion window. */ +-#define NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED 0x08u ++#define NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED 0x08U + /* NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED indicates that the entry + has been marked lost and, optionally, scheduled to retransmit. */ +-#define NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED 0x10u ++#define NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED 0x10U + /* NGTCP2_RTB_ENTRY_FLAG_ECN indicates that the entry is included in a + UDP datagram with ECN marking. */ +-#define NGTCP2_RTB_ENTRY_FLAG_ECN 0x20u ++#define NGTCP2_RTB_ENTRY_FLAG_ECN 0x20U + /* NGTCP2_RTB_ENTRY_FLAG_DATAGRAM indicates that the entry includes + DATAGRAM frame. */ +-#define NGTCP2_RTB_ENTRY_FLAG_DATAGRAM 0x40u ++#define NGTCP2_RTB_ENTRY_FLAG_DATAGRAM 0x40U + /* NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE indicates that the entry includes + a PMTUD probe packet. */ +-#define NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE 0x80u ++#define NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE 0x80U + /* NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING indicates that the entry + includes a packet which elicits PTO probe packets. */ +-#define NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING 0x100u ++#define NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING 0x100U + /* NGTCP2_RTB_ENTRY_FLAG_SKIP indicates that the entry has the skipped + packet number. */ +-#define NGTCP2_RTB_ENTRY_FLAG_SKIP 0x200u ++#define NGTCP2_RTB_ENTRY_FLAG_SKIP 0x200U + + typedef struct ngtcp2_rtb_entry ngtcp2_rtb_entry; + +diff --git a/third_party/ngtcp2/lib/ngtcp2_settings.c b/third_party/ngtcp2/lib/ngtcp2_settings.c +index 77a68bd112e..f774504282e 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_settings.c ++++ b/third_party/ngtcp2/lib/ngtcp2_settings.c +@@ -37,6 +37,10 @@ void ngtcp2_settings_default_versioned(int settings_version, + + switch (settings_version) { + case NGTCP2_SETTINGS_VERSION: ++ settings->glitch_ratelim_burst = NGTCP2_DEFAULT_GLITCH_RATELIM_BURST; ++ settings->glitch_ratelim_rate = NGTCP2_DEFAULT_GLITCH_RATELIM_RATE; ++ /* fall through */ ++ case NGTCP2_SETTINGS_V2: + case NGTCP2_SETTINGS_V1: + settings->cc_algo = NGTCP2_CC_ALGO_CUBIC; + settings->initial_rtt = NGTCP2_DEFAULT_INITIAL_RTT; +@@ -82,6 +86,9 @@ size_t ngtcp2_settingslen_version(int settings_version) { + switch (settings_version) { + case NGTCP2_SETTINGS_VERSION: + return sizeof(settings); ++ case NGTCP2_SETTINGS_V2: ++ return offsetof(ngtcp2_settings, pmtud_probeslen) + ++ sizeof(settings.pmtud_probeslen); + case NGTCP2_SETTINGS_V1: + return offsetof(ngtcp2_settings, initial_pkt_num) + + sizeof(settings.initial_pkt_num); +diff --git a/third_party/ngtcp2/lib/ngtcp2_settings.h b/third_party/ngtcp2/lib/ngtcp2_settings.h +index 80466d43e47..b40b6c4a895 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_settings.h ++++ b/third_party/ngtcp2/lib/ngtcp2_settings.h +@@ -31,6 +31,13 @@ + + #include + ++/* NGTCP2_DEFAULT_GLITCH_RATELIM_BURST is the maximum number of tokens ++ in glitch rate limiter. It is also the initial value. */ ++#define NGTCP2_DEFAULT_GLITCH_RATELIM_BURST 10000 ++/* NGTCP2_DEFAULT_GLITCH_RATELIM_RATE is the rate of tokens generated ++ per second for glitch rate limiter. */ ++#define NGTCP2_DEFAULT_GLITCH_RATELIM_RATE 330 ++ + /* + * ngtcp2_settings_convert_to_latest converts |src| of version + * |settings_version| to the latest version NGTCP2_SETTINGS_VERSION. +diff --git a/third_party/ngtcp2/lib/ngtcp2_str.c b/third_party/ngtcp2/lib/ngtcp2_str.c +index a61636d188f..2726571a504 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_str.c ++++ b/third_party/ngtcp2/lib/ngtcp2_str.c +@@ -47,27 +47,32 @@ const void *ngtcp2_get_bytes(void *dest, const void *src, size_t n) { + + uint8_t *ngtcp2_encode_hex(uint8_t *dest, const uint8_t *data, size_t len) { + size_t i; +- uint8_t *p = dest; + + for (i = 0; i < len; ++i) { +- *p++ = (uint8_t)LOWER_XDIGITS[data[i] >> 4]; +- *p++ = (uint8_t)LOWER_XDIGITS[data[i] & 0xf]; ++ *dest++ = (uint8_t)LOWER_XDIGITS[data[i] >> 4]; ++ *dest++ = (uint8_t)LOWER_XDIGITS[data[i] & 0xF]; + } + ++ return dest; ++} ++ ++char *ngtcp2_encode_hex_cstr(char *dest, const uint8_t *data, size_t len) { ++ uint8_t *p = ngtcp2_encode_hex((uint8_t *)dest, data, len); ++ + *p = '\0'; + + return dest; + } + +-char *ngtcp2_encode_printable_ascii(char *dest, const uint8_t *data, +- size_t len) { ++char *ngtcp2_encode_printable_ascii_cstr(char *dest, const uint8_t *data, ++ size_t len) { + size_t i; + char *p = dest; + uint8_t c; + + for (i = 0; i < len; ++i) { + c = data[i]; +- if (0x20 <= c && c <= 0x7e) { ++ if (0x20 <= c && c <= 0x7E) { + *p++ = (char)c; + } else { + *p++ = '.'; +@@ -79,40 +84,15 @@ char *ngtcp2_encode_printable_ascii(char *dest, const uint8_t *data, + return dest; + } + +-/* +- * write_uint writes |n| to the buffer pointed by |p| in decimal +- * representation. It returns |p| plus the number of bytes written. +- * The function assumes that the buffer has enough capacity to contain +- * a string. +- */ +-static uint8_t *write_uint(uint8_t *p, uint64_t n) { +- size_t nlen = 0; +- uint64_t t; +- uint8_t *res; +- +- if (n == 0) { +- *p++ = '0'; +- return p; +- } +- for (t = n; t; t /= 10, ++nlen) +- ; +- p += nlen; +- res = p; +- for (; n; n /= 10) { +- *--p = (uint8_t)((n % 10) + '0'); +- } +- return res; +-} +- +-uint8_t *ngtcp2_encode_ipv4(uint8_t *dest, const uint8_t *addr) { ++char *ngtcp2_encode_ipv4_cstr(char *dest, const uint8_t *addr) { + size_t i; +- uint8_t *p = dest; ++ char *p = dest; + +- p = write_uint(p, addr[0]); ++ p = (char *)ngtcp2_encode_uint((uint8_t *)p, addr[0]); + + for (i = 1; i < 4; ++i) { + *p++ = '.'; +- p = write_uint(p, addr[i]); ++ p = (char *)ngtcp2_encode_uint((uint8_t *)p, addr[i]); + } + + *p = '\0'; +@@ -125,9 +105,9 @@ uint8_t *ngtcp2_encode_ipv4(uint8_t *dest, const uint8_t *addr) { + * length |len| to |dest| in hex string. Any leading zeros are + * suppressed. It returns |dest| plus the number of bytes written. + */ +-static uint8_t *write_hex_zsup(uint8_t *dest, const uint8_t *data, size_t len) { ++static char *write_hex_zsup(char *dest, const uint8_t *data, size_t len) { + size_t i; +- uint8_t *p = dest; ++ char *p = dest; + uint8_t d; + + for (i = 0; i < len; ++i) { +@@ -136,10 +116,10 @@ static uint8_t *write_hex_zsup(uint8_t *dest, const uint8_t *data, size_t len) { + break; + } + +- d &= 0xf; ++ d &= 0xF; + + if (d) { +- *p++ = (uint8_t)LOWER_XDIGITS[d]; ++ *p++ = LOWER_XDIGITS[d]; + ++i; + break; + } +@@ -152,19 +132,19 @@ static uint8_t *write_hex_zsup(uint8_t *dest, const uint8_t *data, size_t len) { + + for (; i < len; ++i) { + d = data[i]; +- *p++ = (uint8_t)LOWER_XDIGITS[d >> 4]; +- *p++ = (uint8_t)LOWER_XDIGITS[d & 0xf]; ++ *p++ = LOWER_XDIGITS[d >> 4]; ++ *p++ = LOWER_XDIGITS[d & 0xF]; + } + + return p; + } + +-uint8_t *ngtcp2_encode_ipv6(uint8_t *dest, const uint8_t *addr) { ++char *ngtcp2_encode_ipv6_cstr(char *dest, const uint8_t *addr) { + uint16_t blks[8]; + size_t i; + size_t zlen, zoff; + size_t max_zlen = 0, max_zoff = 8; +- uint8_t *p = dest; ++ char *p = dest; + + for (i = 0; i < 16; i += sizeof(uint16_t)) { + /* Copy in network byte order. */ +@@ -231,3 +211,124 @@ int ngtcp2_cmemeq(const uint8_t *a, const uint8_t *b, size_t n) { + + return rv == 0; + } ++ ++/* countl_zero counts the number of leading zeros in |x|. It is ++ undefined if |x| is 0. */ ++static int countl_zero(uint64_t x) { ++#ifdef __GNUC__ ++ return __builtin_clzll(x); ++#else /* !defined(__GNUC__) */ ++ /* This is the same implementation of Go's LeadingZeros64 in ++ math/bits package. */ ++ static const uint8_t len8tab[] = { ++ 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, ++ 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, ++ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, ++ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, ++ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, ++ 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, ++ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, ++ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, ++ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, ++ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, ++ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, ++ }; ++ int n = 0; ++ ++ if (x >= 1ULL << 32) { ++ x >>= 32; ++ n += 32; ++ } ++ ++ if (x >= 1 << 16) { ++ x >>= 16; ++ n += 16; ++ } ++ ++ if (x >= 1 << 8) { ++ x >>= 8; ++ n += 8; ++ } ++ ++ return 64 - (n + len8tab[x]); ++#endif /* !defined(__GNUC__) */ ++} ++ ++/* ++ * count_digit returns the minimum number of digits to represent |x| ++ * in base 10. ++ * ++ * credit: ++ * https://lemire.me/blog/2025/01/07/counting-the-digits-of-64-bit-integers/ ++ */ ++static size_t count_digit(uint64_t x) { ++ static const uint64_t count_digit_tbl[] = { ++ 9ULL, ++ 99ULL, ++ 999ULL, ++ 9999ULL, ++ 99999ULL, ++ 999999ULL, ++ 9999999ULL, ++ 99999999ULL, ++ 999999999ULL, ++ 9999999999ULL, ++ 99999999999ULL, ++ 999999999999ULL, ++ 9999999999999ULL, ++ 99999999999999ULL, ++ 999999999999999ULL, ++ 9999999999999999ULL, ++ 99999999999999999ULL, ++ 999999999999999999ULL, ++ 9999999999999999999ULL, ++ }; ++ size_t y = (size_t)(19 * (63 - countl_zero(x | 1)) >> 6); ++ ++ y += x > count_digit_tbl[y]; ++ ++ return y + 1; ++} ++ ++uint8_t *ngtcp2_encode_uint(uint8_t *dest, uint64_t n) { ++ static const uint8_t uint_digits[] = ++ "00010203040506070809101112131415161718192021222324252627282930313233343536" ++ "37383940414243444546474849505152535455565758596061626364656667686970717273" ++ "7475767778798081828384858687888990919293949596979899"; ++ uint8_t *p; ++ const uint8_t *tp; ++ ++ if (n < 10) { ++ *dest++ = (uint8_t)('0' + n); ++ return dest; ++ } ++ ++ if (n < 100) { ++ tp = &uint_digits[n * 2]; ++ *dest++ = *tp++; ++ *dest++ = *tp; ++ return dest; ++ } ++ ++ dest += count_digit(n); ++ p = dest; ++ ++ for (; n >= 100; n /= 100) { ++ p -= 2; ++ tp = &uint_digits[(n % 100) * 2]; ++ p[0] = *tp++; ++ p[1] = *tp; ++ } ++ ++ if (n < 10) { ++ *--p = (uint8_t)('0' + n); ++ return dest; ++ } ++ ++ p -= 2; ++ tp = &uint_digits[n * 2]; ++ p[0] = *tp++; ++ p[1] = *tp; ++ ++ return dest; ++} +diff --git a/third_party/ngtcp2/lib/ngtcp2_str.h b/third_party/ngtcp2/lib/ngtcp2_str.h +index f970c153e80..051053d75b7 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_str.h ++++ b/third_party/ngtcp2/lib/ngtcp2_str.h +@@ -46,24 +46,31 @@ uint8_t *ngtcp2_setmem(uint8_t *dest, uint8_t b, size_t n); + const void *ngtcp2_get_bytes(void *dest, const void *src, size_t n); + + /* +- * ngtcp2_encode_hex encodes |data| of length |len| in hex string. It +- * writes additional NULL bytes at the end of the buffer. The buffer +- * pointed by |dest| must have at least |len| * 2 + 1 bytes space. +- * This function returns |dest|. ++ * ngtcp2_encode_hex encodes |data| of length |len| in hex string. ++ * The buffer pointed by |dest| must have at least |len| * 2 bytes ++ * space. This function returns |dest| + |len| * 2. + */ + uint8_t *ngtcp2_encode_hex(uint8_t *dest, const uint8_t *data, size_t len); + + /* +- * ngtcp2_encode_ipv4 encodes binary form IPv4 address stored in ++ * ngtcp2_encode_hex_cstr encodes |data| of length |len| in hex ++ * string. It writes additional NULL bytes at the end of the buffer. ++ * The buffer pointed by |dest| must have at least |len| * 2 + 1 bytes ++ * space. This function returns |dest|. ++ */ ++char *ngtcp2_encode_hex_cstr(char *dest, const uint8_t *data, size_t len); ++ ++/* ++ * ngtcp2_encode_ipv4_cstr encodes binary form IPv4 address stored in + * |addr| to human readable text form in the buffer pointed by |dest|. + * The capacity of buffer must have enough length to store a text form + * plus a terminating NULL byte. The resulting text form ends with + * NULL byte. The function returns |dest|. + */ +-uint8_t *ngtcp2_encode_ipv4(uint8_t *dest, const uint8_t *addr); ++char *ngtcp2_encode_ipv4_cstr(char *dest, const uint8_t *addr); + + /* +- * ngtcp2_encode_ipv6 encodes binary form IPv6 address stored in ++ * ngtcp2_encode_ipv6_cstr encodes binary form IPv6 address stored in + * |addr| to human readable text form in the buffer pointed by |dest|. + * The capacity of buffer must have enough length to store a text form + * plus a terminating NULL byte. The resulting text form ends with +@@ -72,7 +79,7 @@ uint8_t *ngtcp2_encode_ipv4(uint8_t *dest, const uint8_t *addr); + * https://tools.ietf.org/html/rfc5952#section-4. The function + * returns |dest|. + */ +-uint8_t *ngtcp2_encode_ipv6(uint8_t *dest, const uint8_t *addr); ++char *ngtcp2_encode_ipv6_cstr(char *dest, const uint8_t *addr); + + /* + * ngtcp2_encode_printable_ascii encodes |data| of length |len| in +@@ -81,8 +88,8 @@ uint8_t *ngtcp2_encode_ipv6(uint8_t *dest, const uint8_t *addr); + * writes additional NULL bytes at the end of the buffer. |dest| must + * have at least |len| + 1 bytes. This function returns |dest|. + */ +-char *ngtcp2_encode_printable_ascii(char *dest, const uint8_t *data, +- size_t len); ++char *ngtcp2_encode_printable_ascii_cstr(char *dest, const uint8_t *data, ++ size_t len); + + /* + * ngtcp2_cmemeq returns nonzero if the first |n| bytes of the buffers +@@ -91,4 +98,12 @@ char *ngtcp2_encode_printable_ascii(char *dest, const uint8_t *data, + */ + int ngtcp2_cmemeq(const uint8_t *a, const uint8_t *b, size_t n); + ++/* ++ * ngtcp2_encode_uint encodes |n| as a decimal integer to the buffer ++ * pointed by |dest|. This function assumes that the buffer contains ++ * the sufficient capacity to write the number. This function returns ++ * the pointer to the buffer past the last byte written. ++ */ ++uint8_t *ngtcp2_encode_uint(uint8_t *dest, uint64_t n); ++ + #endif /* !defined(NGTCP2_STR_H) */ +diff --git a/third_party/ngtcp2/lib/ngtcp2_strm.c b/third_party/ngtcp2/lib/ngtcp2_strm.c +index 8ea969c4add..70aa8933a3b 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_strm.c ++++ b/third_party/ngtcp2/lib/ngtcp2_strm.c +@@ -36,30 +36,27 @@ void ngtcp2_strm_init(ngtcp2_strm *strm, int64_t stream_id, uint32_t flags, + uint64_t max_rx_offset, uint64_t max_tx_offset, + void *stream_user_data, ngtcp2_objalloc *frc_objalloc, + const ngtcp2_mem *mem) { +- strm->pe.index = NGTCP2_PQ_BAD_INDEX; +- strm->cycle = 0; +- strm->frc_objalloc = frc_objalloc; +- strm->tx.acked_offset = NULL; +- strm->tx.cont_acked_offset = 0; +- strm->tx.streamfrq = NULL; +- strm->tx.offset = 0; +- strm->tx.max_offset = max_tx_offset; +- strm->tx.last_blocked_offset = UINT64_MAX; +- strm->tx.last_max_stream_data_ts = UINT64_MAX; +- strm->tx.loss_count = 0; +- strm->tx.last_lost_pkt_num = -1; +- strm->tx.stop_sending_app_error_code = 0; +- strm->tx.reset_stream_app_error_code = 0; +- strm->rx.rob = NULL; +- strm->rx.cont_offset = 0; +- strm->rx.last_offset = 0; +- strm->rx.max_offset = strm->rx.unsent_max_offset = strm->rx.window = +- max_rx_offset; +- strm->mem = mem; +- strm->stream_id = stream_id; +- strm->stream_user_data = stream_user_data; +- strm->flags = flags; +- strm->app_error_code = 0; ++ *strm = (ngtcp2_strm){ ++ .pe.index = NGTCP2_PQ_BAD_INDEX, ++ .frc_objalloc = frc_objalloc, ++ .tx = ++ { ++ .max_offset = max_tx_offset, ++ .last_blocked_offset = UINT64_MAX, ++ .last_max_stream_data_ts = UINT64_MAX, ++ .last_lost_pkt_num = -1, ++ }, ++ .rx = ++ { ++ .max_offset = max_rx_offset, ++ .unsent_max_offset = max_rx_offset, ++ .window = max_rx_offset, ++ }, ++ .mem = mem, ++ .stream_id = stream_id, ++ .stream_user_data = stream_user_data, ++ .flags = flags, ++ }; + } + + void ngtcp2_strm_free(ngtcp2_strm *strm) { +@@ -120,12 +117,13 @@ uint64_t ngtcp2_strm_rx_offset(const ngtcp2_strm *strm) { + /* strm_rob_heavily_fragmented returns nonzero if the number of gaps + in |rob| exceeds the limit. */ + static int strm_rob_heavily_fragmented(const ngtcp2_rob *rob) { +- return ngtcp2_ksl_len(&rob->gapksl) >= 5000; ++ return ngtcp2_ksl_len(&rob->gapksl) >= 4000; + } + +-int ngtcp2_strm_recv_reordering(ngtcp2_strm *strm, const uint8_t *data, +- size_t datalen, uint64_t offset) { ++ngtcp2_ssize ngtcp2_strm_recv_reordering(ngtcp2_strm *strm, const uint8_t *data, ++ size_t datalen, uint64_t offset) { + int rv; ++ ngtcp2_ssize nwrite; + + if (strm->rx.rob == NULL) { + rv = strm_rob_init(strm); +@@ -138,11 +136,16 @@ int ngtcp2_strm_recv_reordering(ngtcp2_strm *strm, const uint8_t *data, + } + } + ++ nwrite = ngtcp2_rob_push(strm->rx.rob, offset, data, datalen); ++ if (nwrite < 0) { ++ return nwrite; ++ } ++ + if (strm_rob_heavily_fragmented(strm->rx.rob)) { + return NGTCP2_ERR_INTERNAL; + } + +- return ngtcp2_rob_push(strm->rx.rob, offset, data, datalen); ++ return nwrite; + } + + void ngtcp2_strm_update_rx_offset(ngtcp2_strm *strm, uint64_t offset) { +@@ -187,8 +190,8 @@ static int strm_streamfrq_init(ngtcp2_strm *strm) { + int ngtcp2_strm_streamfrq_push(ngtcp2_strm *strm, ngtcp2_frame_chain *frc) { + int rv; + +- assert(frc->fr.type == NGTCP2_FRAME_STREAM || +- frc->fr.type == NGTCP2_FRAME_CRYPTO); ++ assert(frc->fr.hd.type == NGTCP2_FRAME_STREAM || ++ frc->fr.hd.type == NGTCP2_FRAME_CRYPTO); + assert(frc->next == NULL); + + if (strm->tx.streamfrq == NULL) { +@@ -196,6 +199,8 @@ int ngtcp2_strm_streamfrq_push(ngtcp2_strm *strm, ngtcp2_frame_chain *frc) { + if (rv != 0) { + return rv; + } ++ } else if (ngtcp2_ksl_len(strm->tx.streamfrq) >= 8000) { ++ return NGTCP2_ERR_INTERNAL; + } + + return ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &frc->fr.stream.offset, +@@ -300,8 +305,7 @@ static int strm_streamfrq_unacked_pop(ngtcp2_strm *strm, + + fr->offset = offset + base_offset; + fr->datacnt = end_idx - idx; +- fr->data[0].base += base_offset; +- fr->data[0].len -= (size_t)base_offset; ++ ngtcp2_vec_drop(&fr->data[0], (size_t)base_offset); + + *pfrc = frc; + +@@ -327,8 +331,7 @@ static int strm_streamfrq_unacked_pop(ngtcp2_strm *strm, + nfr->stream_id = fr->stream_id; + nfr->offset = end_offset + end_base_offset; + nfr->datacnt = fr->datacnt - end_idx; +- nfr->data[0].base += end_base_offset; +- nfr->data[0].len -= (size_t)end_base_offset; ++ ngtcp2_vec_drop(&nfr->data[0], (size_t)end_base_offset); + + rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &nfr->offset, nfrc); + if (rv != 0) { +@@ -356,8 +359,7 @@ static int strm_streamfrq_unacked_pop(ngtcp2_strm *strm, + fr->data[fr->datacnt - 1].len = (size_t)end_base_offset; + } + +- fr->data[0].base += base_offset; +- fr->data[0].len -= (size_t)base_offset; ++ ngtcp2_vec_drop(&fr->data[0], (size_t)base_offset); + + *pfrc = frc; + +@@ -370,13 +372,12 @@ static int strm_streamfrq_unacked_pop(ngtcp2_strm *strm, + int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc, + size_t left) { + ngtcp2_stream *fr, *nfr; +- ngtcp2_frame_chain *frc, *nfrc, *sfrc; ++ ngtcp2_frame_chain *frc, *nfrc; + int rv; + size_t nmerged; + uint64_t datalen; +- ngtcp2_vec a[NGTCP2_MAX_STREAM_DATACNT]; +- ngtcp2_vec b[NGTCP2_MAX_STREAM_DATACNT]; +- size_t acnt, bcnt; ++ ngtcp2_vec data[NGTCP2_MAX_STREAM_DATACNT]; ++ size_t datacnt; + uint64_t unacked_offset; + + if (strm->tx.streamfrq == NULL || ngtcp2_ksl_len(strm->tx.streamfrq) == 0) { +@@ -398,7 +399,11 @@ int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc, + datalen = ngtcp2_vec_len(fr->data, fr->datacnt); + + /* datalen could be zero if 0 length STREAM has been sent */ +- if (left == 0 && datalen) { ++ /* We might see more data in the queue, then left < datalen could be ++ true. We only see the first one for now. */ ++ if ((fr->type == NGTCP2_FRAME_STREAM && ++ (left < datalen && left < NGTCP2_MIN_STREAM_DATALEN)) || ++ (left == 0 && datalen)) { + rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &fr->offset, frc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); +@@ -412,17 +417,15 @@ int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc, + } + + if (datalen > left) { +- ngtcp2_vec_copy(a, fr->data, fr->datacnt); +- acnt = fr->datacnt; +- +- bcnt = 0; +- ngtcp2_vec_split(b, &bcnt, a, &acnt, left, NGTCP2_MAX_STREAM_DATACNT); ++ datacnt = 0; ++ ngtcp2_vec_split(data, &datacnt, fr->data, &fr->datacnt, left, ++ NGTCP2_MAX_STREAM_DATACNT); + +- assert(acnt > 0); +- assert(bcnt > 0); ++ assert(fr->datacnt > 0); ++ assert(datacnt > 0); + + rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new( +- &nfrc, bcnt, strm->frc_objalloc, strm->mem); ++ &nfrc, datacnt, strm->frc_objalloc, strm->mem); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); +@@ -435,8 +438,8 @@ int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc, + nfr->fin = fr->fin; + nfr->stream_id = fr->stream_id; + nfr->offset = fr->offset + left; +- nfr->datacnt = bcnt; +- ngtcp2_vec_copy(nfr->data, b, bcnt); ++ nfr->datacnt = datacnt; ++ ngtcp2_vec_copy(nfr->data, data, datacnt); + + rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &nfr->offset, nfrc); + if (rv != 0) { +@@ -447,31 +450,17 @@ int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc, + return rv; + } + +- rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new( +- &nfrc, acnt, strm->frc_objalloc, strm->mem); +- if (rv != 0) { +- assert(ngtcp2_err_is_fatal(rv)); +- ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); +- return rv; +- } +- +- nfr = &nfrc->fr.stream; +- *nfr = *fr; +- nfr->fin = 0; +- nfr->datacnt = acnt; +- ngtcp2_vec_copy(nfr->data, a, acnt); +- +- ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); ++ fr->fin = 0; + +- *pfrc = nfrc; ++ *pfrc = frc; + + return 0; + } + + left -= (size_t)datalen; + +- ngtcp2_vec_copy(a, fr->data, fr->datacnt); +- acnt = fr->datacnt; ++ ngtcp2_vec_copy(data, fr->data, fr->datacnt); ++ datacnt = fr->datacnt; + + for (; left && ngtcp2_ksl_len(strm->tx.streamfrq);) { + unacked_offset = ngtcp2_strm_streamfrq_unacked_offset(strm); +@@ -498,9 +487,7 @@ int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc, + break; + } + +- bcnt = nfr->datacnt; +- +- nmerged = ngtcp2_vec_merge(a, &acnt, nfr->data, &bcnt, left, ++ nmerged = ngtcp2_vec_merge(data, &datacnt, nfr->data, &nfr->datacnt, left, + NGTCP2_MAX_STREAM_DATACNT); + if (nmerged == 0) { + rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &nfr->offset, nfrc); +@@ -518,54 +505,27 @@ int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc, + datalen += nmerged; + left -= nmerged; + +- if (bcnt == 0) { ++ if (nfr->datacnt == 0) { + fr->fin = nfr->fin; + ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem); + continue; + } + +- if (nfr->datacnt <= NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES || +- bcnt > NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES) { +- nfr->offset += nmerged; +- nfr->datacnt = bcnt; +- +- rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &nfr->offset, nfrc); +- if (rv != 0) { +- ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem); +- ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); +- return rv; +- } +- } else { +- rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new( +- &sfrc, bcnt, strm->frc_objalloc, strm->mem); +- if (rv != 0) { +- ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem); +- ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); +- return rv; +- } +- +- sfrc->fr.stream = nfrc->fr.stream; +- sfrc->fr.stream.offset += nmerged; +- sfrc->fr.stream.datacnt = bcnt; +- ngtcp2_vec_copy(sfrc->fr.stream.data, nfrc->fr.stream.data, bcnt); ++ nfr->offset += nmerged; + ++ rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &nfr->offset, nfrc); ++ if (rv != 0) { + ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem); +- +- rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &sfrc->fr.stream.offset, +- sfrc); +- if (rv != 0) { +- ngtcp2_frame_chain_objalloc_del(sfrc, strm->frc_objalloc, strm->mem); +- ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); +- return rv; +- } ++ ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); ++ return rv; + } + + break; + } + +- if (acnt == fr->datacnt) { +- if (acnt > 0) { +- fr->data[acnt - 1] = a[acnt - 1]; ++ if (datacnt == fr->datacnt) { ++ if (datacnt > 0) { ++ fr->data[datacnt - 1] = data[datacnt - 1]; + } + + *pfrc = frc; +@@ -573,19 +533,23 @@ int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc, + return 0; + } + +- assert(acnt > fr->datacnt); ++ assert(datacnt > fr->datacnt); + + rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new( +- &nfrc, acnt, strm->frc_objalloc, strm->mem); ++ &nfrc, datacnt, strm->frc_objalloc, strm->mem); + if (rv != 0) { + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + return rv; + } + + nfr = &nfrc->fr.stream; +- *nfr = *fr; +- nfr->datacnt = acnt; +- ngtcp2_vec_copy(nfr->data, a, acnt); ++ nfr->type = fr->type; ++ nfr->flags = fr->flags; ++ nfr->fin = fr->fin; ++ nfr->stream_id = fr->stream_id; ++ nfr->offset = fr->offset; ++ nfr->datacnt = datacnt; ++ ngtcp2_vec_copy(nfr->data, data, datacnt); + + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + +@@ -682,12 +646,11 @@ int ngtcp2_strm_is_all_tx_data_fin_acked(const ngtcp2_strm *strm) { + + ngtcp2_range ngtcp2_strm_get_unacked_range_after(const ngtcp2_strm *strm, + uint64_t offset) { +- ngtcp2_range gap; +- + if (strm->tx.acked_offset == NULL) { +- gap.begin = strm->tx.cont_acked_offset; +- gap.end = UINT64_MAX; +- return gap; ++ return (ngtcp2_range){ ++ .begin = strm->tx.cont_acked_offset, ++ .end = UINT64_MAX, ++ }; + } + + return ngtcp2_gaptr_get_first_gap_after(strm->tx.acked_offset, offset); +@@ -737,7 +700,16 @@ int ngtcp2_strm_ack_data(ngtcp2_strm *strm, uint64_t offset, uint64_t len) { + } + } + +- return ngtcp2_gaptr_push(strm->tx.acked_offset, offset, len); ++ rv = ngtcp2_gaptr_push(strm->tx.acked_offset, offset, len); ++ if (rv != 0) { ++ return rv; ++ } ++ ++ if (ngtcp2_ksl_len(&strm->tx.acked_offset->gap) >= 4000) { ++ return NGTCP2_ERR_INTERNAL; ++ } ++ ++ return 0; + } + + void ngtcp2_strm_set_app_error_code(ngtcp2_strm *strm, +diff --git a/third_party/ngtcp2/lib/ngtcp2_strm.h b/third_party/ngtcp2/lib/ngtcp2_strm.h +index c72f8b9dc89..0d5d8a63a62 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_strm.h ++++ b/third_party/ngtcp2/lib/ngtcp2_strm.h +@@ -41,54 +41,54 @@ + typedef struct ngtcp2_frame_chain ngtcp2_frame_chain; + + /* NGTCP2_STRM_FLAG_NONE indicates that no flag is set. */ +-#define NGTCP2_STRM_FLAG_NONE 0x00u ++#define NGTCP2_STRM_FLAG_NONE 0x00U + /* NGTCP2_STRM_FLAG_SHUT_RD indicates that further reception of stream + data is not allowed. */ +-#define NGTCP2_STRM_FLAG_SHUT_RD 0x01u ++#define NGTCP2_STRM_FLAG_SHUT_RD 0x01U + /* NGTCP2_STRM_FLAG_SHUT_WR indicates that further transmission of + stream data is not allowed. */ +-#define NGTCP2_STRM_FLAG_SHUT_WR 0x02u ++#define NGTCP2_STRM_FLAG_SHUT_WR 0x02U + #define NGTCP2_STRM_FLAG_SHUT_RDWR \ + (NGTCP2_STRM_FLAG_SHUT_RD | NGTCP2_STRM_FLAG_SHUT_WR) + /* NGTCP2_STRM_FLAG_RESET_STREAM indicates that RESET_STREAM is sent + from the local endpoint. In this case, NGTCP2_STRM_FLAG_SHUT_WR is + also set. */ +-#define NGTCP2_STRM_FLAG_RESET_STREAM 0x04u ++#define NGTCP2_STRM_FLAG_RESET_STREAM 0x04U + /* NGTCP2_STRM_FLAG_RESET_STREAM_RECVED indicates that RESET_STREAM is + received from the remote endpoint. In this case, + NGTCP2_STRM_FLAG_SHUT_RD is also set. */ +-#define NGTCP2_STRM_FLAG_RESET_STREAM_RECVED 0x08u ++#define NGTCP2_STRM_FLAG_RESET_STREAM_RECVED 0x08U + /* NGTCP2_STRM_FLAG_STOP_SENDING indicates that STOP_SENDING is sent + from the local endpoint. */ +-#define NGTCP2_STRM_FLAG_STOP_SENDING 0x10u ++#define NGTCP2_STRM_FLAG_STOP_SENDING 0x10U + /* NGTCP2_STRM_FLAG_RESET_STREAM_ACKED indicates that the outgoing + RESET_STREAM is acknowledged by peer. */ +-#define NGTCP2_STRM_FLAG_RESET_STREAM_ACKED 0x20u ++#define NGTCP2_STRM_FLAG_RESET_STREAM_ACKED 0x20U + /* NGTCP2_STRM_FLAG_FIN_ACKED indicates that a STREAM with FIN bit set + is acknowledged by a remote endpoint. */ +-#define NGTCP2_STRM_FLAG_FIN_ACKED 0x40u ++#define NGTCP2_STRM_FLAG_FIN_ACKED 0x40U + /* NGTCP2_STRM_FLAG_ANY_ACKED indicates that any portion of stream + data, including 0 length segment, is acknowledged. */ +-#define NGTCP2_STRM_FLAG_ANY_ACKED 0x80u ++#define NGTCP2_STRM_FLAG_ANY_ACKED 0x80U + /* NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET indicates that app_error_code + field is set. This resolves the ambiguity that the initial + app_error_code value 0 might be a proper application error code. + In this case, without this flag, we are unable to distinguish + assigned value from unassigned one. */ +-#define NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET 0x100u ++#define NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET 0x100U + /* NGTCP2_STRM_FLAG_SEND_STOP_SENDING is set when STOP_SENDING frame + should be sent. */ +-#define NGTCP2_STRM_FLAG_SEND_STOP_SENDING 0x200u ++#define NGTCP2_STRM_FLAG_SEND_STOP_SENDING 0x200U + /* NGTCP2_STRM_FLAG_SEND_RESET_STREAM is set when RESET_STREAM frame + should be sent. */ +-#define NGTCP2_STRM_FLAG_SEND_RESET_STREAM 0x400u ++#define NGTCP2_STRM_FLAG_SEND_RESET_STREAM 0x400U + /* NGTCP2_STRM_FLAG_STOP_SENDING_RECVED indicates that STOP_SENDING is + received from the remote endpoint. In this case, + NGTCP2_STRM_FLAG_SHUT_WR is also set. */ +-#define NGTCP2_STRM_FLAG_STOP_SENDING_RECVED 0x800u ++#define NGTCP2_STRM_FLAG_STOP_SENDING_RECVED 0x800U + /* NGTCP2_STRM_FLAG_ANY_SENT indicates that any STREAM frame, + including empty one, has been sent. */ +-#define NGTCP2_STRM_FLAG_ANY_SENT 0x1000u ++#define NGTCP2_STRM_FLAG_ANY_SENT 0x1000U + + typedef struct ngtcp2_strm ngtcp2_strm; + +@@ -171,14 +171,14 @@ struct ngtcp2_strm { + const ngtcp2_mem *mem; + int64_t stream_id; + void *stream_user_data; +- /* flags is bit-wise OR of zero or more of NGTCP2_STRM_FLAG_*. */ +- uint32_t flags; + /* app_error_code is an error code the local endpoint sent in + RESET_STREAM or STOP_SENDING, or received from a remote endpoint + in RESET_STREAM or STOP_SENDING. First application error code is + chosen and when set, NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET flag is + set in flags field. */ + uint64_t app_error_code; ++ /* flags is bit-wise OR of zero or more of NGTCP2_STRM_FLAG_*. */ ++ uint32_t flags; + }; + + ngtcp2_opl_entry oplent; +@@ -208,14 +208,14 @@ uint64_t ngtcp2_strm_rx_offset(const ngtcp2_strm *strm); + /* + * ngtcp2_strm_recv_reordering handles reordered data. + * +- * It returns 0 if it succeeds, or one of the following negative error +- * codes: ++ * It returns the number of bytes newly buffered if it succeeds, or ++ * one of the following negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +-int ngtcp2_strm_recv_reordering(ngtcp2_strm *strm, const uint8_t *data, +- size_t datalen, uint64_t offset); ++ngtcp2_ssize ngtcp2_strm_recv_reordering(ngtcp2_strm *strm, const uint8_t *data, ++ size_t datalen, uint64_t offset); + + /* + * ngtcp2_strm_update_rx_offset tells that data up to |offset| bytes +diff --git a/third_party/ngtcp2/lib/ngtcp2_transport_params.c b/third_party/ngtcp2/lib/ngtcp2_transport_params.c +index ca517532e3a..5d378176e16 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_transport_params.c ++++ b/third_party/ngtcp2/lib/ngtcp2_transport_params.c +@@ -150,8 +150,8 @@ ngtcp2_ssize ngtcp2_transport_params_encode_versioned( + + if (params->stateless_reset_token_present) { + len += ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_STATELESS_RESET_TOKEN) + +- ngtcp2_put_uvarintlen(NGTCP2_STATELESS_RESET_TOKENLEN) + +- NGTCP2_STATELESS_RESET_TOKENLEN; ++ ngtcp2_put_uvarintlen(sizeof(params->stateless_reset_token)) + ++ sizeof(params->stateless_reset_token); + } + + if (params->preferred_addr_present) { +@@ -160,7 +160,7 @@ ngtcp2_ssize ngtcp2_transport_params_encode_versioned( + preferred_addrlen = 4 /* ipv4Address */ + 2 /* ipv4Port */ + + 16 /* ipv6Address */ + 2 /* ipv6Port */ + + 1 + params->preferred_addr.cid.datalen /* CID */ + +- NGTCP2_STATELESS_RESET_TOKENLEN; ++ sizeof(params->preferred_addr.stateless_reset_token); + len += ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_PREFERRED_ADDRESS) + + ngtcp2_put_uvarintlen(preferred_addrlen) + preferred_addrlen; + } +@@ -523,12 +523,12 @@ int ngtcp2_transport_params_decode_versioned(int transport_params_version, + } + + /* Set default values */ +- memset(params, 0, sizeof(*params)); +- params->max_udp_payload_size = NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE; +- params->ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT; +- params->max_ack_delay = NGTCP2_DEFAULT_MAX_ACK_DELAY; +- params->active_connection_id_limit = +- NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; ++ *params = (ngtcp2_transport_params){ ++ .max_udp_payload_size = NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE, ++ .ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT, ++ .max_ack_delay = NGTCP2_DEFAULT_MAX_ACK_DELAY, ++ .active_connection_id_limit = NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT, ++ }; + + p = end = data; + +@@ -626,7 +626,8 @@ int ngtcp2_transport_params_decode_versioned(int transport_params_version, + } + len = 4 /* ipv4Address */ + 2 /* ipv4Port */ + 16 /* ipv6Address */ + + 2 /* ipv6Port */ +- + 1 /* cid length */ + NGTCP2_STATELESS_RESET_TOKENLEN; ++ + 1 /* cid length */ + ++ sizeof(params->preferred_addr.stateless_reset_token); + if (valuelen < len) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } +diff --git a/third_party/ngtcp2/lib/ngtcp2_transport_params.h b/third_party/ngtcp2/lib/ngtcp2_transport_params.h +index c077f06a9dd..d98d034b3c2 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_transport_params.h ++++ b/third_party/ngtcp2/lib/ngtcp2_transport_params.h +@@ -45,16 +45,16 @@ typedef uint64_t ngtcp2_transport_param_id; + #define NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_UNI 0x07 + #define NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_BIDI 0x08 + #define NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_UNI 0x09 +-#define NGTCP2_TRANSPORT_PARAM_ACK_DELAY_EXPONENT 0x0a +-#define NGTCP2_TRANSPORT_PARAM_MAX_ACK_DELAY 0x0b +-#define NGTCP2_TRANSPORT_PARAM_DISABLE_ACTIVE_MIGRATION 0x0c +-#define NGTCP2_TRANSPORT_PARAM_PREFERRED_ADDRESS 0x0d +-#define NGTCP2_TRANSPORT_PARAM_ACTIVE_CONNECTION_ID_LIMIT 0x0e +-#define NGTCP2_TRANSPORT_PARAM_INITIAL_SOURCE_CONNECTION_ID 0x0f ++#define NGTCP2_TRANSPORT_PARAM_ACK_DELAY_EXPONENT 0x0A ++#define NGTCP2_TRANSPORT_PARAM_MAX_ACK_DELAY 0x0B ++#define NGTCP2_TRANSPORT_PARAM_DISABLE_ACTIVE_MIGRATION 0x0C ++#define NGTCP2_TRANSPORT_PARAM_PREFERRED_ADDRESS 0x0D ++#define NGTCP2_TRANSPORT_PARAM_ACTIVE_CONNECTION_ID_LIMIT 0x0E ++#define NGTCP2_TRANSPORT_PARAM_INITIAL_SOURCE_CONNECTION_ID 0x0F + #define NGTCP2_TRANSPORT_PARAM_RETRY_SOURCE_CONNECTION_ID 0x10 + /* https://datatracker.ietf.org/doc/html/rfc9221 */ + #define NGTCP2_TRANSPORT_PARAM_MAX_DATAGRAM_FRAME_SIZE 0x20 +-#define NGTCP2_TRANSPORT_PARAM_GREASE_QUIC_BIT 0x2ab2 ++#define NGTCP2_TRANSPORT_PARAM_GREASE_QUIC_BIT 0x2AB2 + /* https://datatracker.ietf.org/doc/html/rfc9368 */ + #define NGTCP2_TRANSPORT_PARAM_VERSION_INFORMATION 0x11 + +diff --git a/third_party/ngtcp2/lib/ngtcp2_vec.c b/third_party/ngtcp2/lib/ngtcp2_vec.c +index dbca8691d64..f94c6218d53 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_vec.c ++++ b/third_party/ngtcp2/lib/ngtcp2_vec.c +@@ -30,8 +30,11 @@ + #include "ngtcp2_str.h" + + ngtcp2_vec *ngtcp2_vec_init(ngtcp2_vec *vec, const uint8_t *base, size_t len) { +- vec->base = (uint8_t *)base; +- vec->len = len; ++ *vec = (ngtcp2_vec){ ++ .base = (uint8_t *)base, ++ .len = len, ++ }; ++ + return vec; + } + +@@ -102,8 +105,7 @@ ngtcp2_ssize ngtcp2_vec_split(ngtcp2_vec *dst, size_t *pdstcnt, ngtcp2_vec *src, + memcpy(dst, src + i, sizeof(ngtcp2_vec) * nmove); + } + +- dst[0].len -= left; +- dst[0].base += left; ++ ngtcp2_vec_drop(&dst[0], left); + src[i].len = left; + + if (nmove == 0) { +@@ -136,8 +138,7 @@ size_t ngtcp2_vec_merge(ngtcp2_vec *dst, size_t *pdstcnt, ngtcp2_vec *src, + a->len = left; + a->base = b->base; + +- b->len -= left; +- b->base += left; ++ ngtcp2_vec_drop(b, left); + + return left; + } +@@ -158,13 +159,14 @@ size_t ngtcp2_vec_merge(ngtcp2_vec *dst, size_t *pdstcnt, ngtcp2_vec *src, + } else if (*pdstcnt == maxcnt) { + break; + } else { +- dst[*pdstcnt].len = left; +- dst[*pdstcnt].base = b->base; ++ dst[*pdstcnt] = (ngtcp2_vec){ ++ .base = b->base, ++ .len = left, ++ }; + ++*pdstcnt; + } + +- b->len -= left; +- b->base += left; ++ ngtcp2_vec_drop(b, left); + left = 0; + + break; +@@ -199,8 +201,10 @@ size_t ngtcp2_vec_copy_at_most(ngtcp2_vec *dst, size_t dstcnt, + } + + if (src[i].len > left) { +- dst[j].base = src[i].base; +- dst[j].len = left; ++ dst[j] = (ngtcp2_vec){ ++ .base = src[i].base, ++ .len = left, ++ }; + + return j + 1; + } +@@ -217,3 +221,14 @@ size_t ngtcp2_vec_copy_at_most(ngtcp2_vec *dst, size_t dstcnt, + void ngtcp2_vec_copy(ngtcp2_vec *dst, const ngtcp2_vec *src, size_t cnt) { + memcpy(dst, src, sizeof(ngtcp2_vec) * cnt); + } ++ ++void ngtcp2_vec_split_at(ngtcp2_vec *dst, ngtcp2_vec *src, size_t offset) { ++ assert(offset < src->len); ++ ++ *dst = (ngtcp2_vec){ ++ .base = src->base + offset, ++ .len = src->len - offset, ++ }; ++ ++ src->len = offset; ++} +diff --git a/third_party/ngtcp2/lib/ngtcp2_vec.h b/third_party/ngtcp2/lib/ngtcp2_vec.h +index 55e735d164e..fe6a47a029a 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_vec.h ++++ b/third_party/ngtcp2/lib/ngtcp2_vec.h +@@ -33,13 +33,6 @@ + + #include "ngtcp2_mem.h" + +-/* +- * ngtcp2_vec_lit is a convenient macro to fill the object pointed by +- * |DEST| with the literal string |LIT|. +- */ +-#define ngtcp2_vec_lit(DEST, LIT) \ +- ((DEST)->base = (uint8_t *)(LIT), (DEST)->len = sizeof(LIT) - 1, (DEST)) +- + /* + * ngtcp2_vec_init initializes |vec| with the given parameters. It + * returns |vec|. +@@ -103,4 +96,28 @@ size_t ngtcp2_vec_copy_at_most(ngtcp2_vec *dst, size_t dstcnt, + */ + void ngtcp2_vec_copy(ngtcp2_vec *dst, const ngtcp2_vec *src, size_t cnt); + ++/* ++ * ngtcp2_vec_split_at splits |src| at the |offset|. Caller must ++ * ensure that offset < src->len. This function assigns the right ++ * part of vector into |dst|. ++ */ ++void ngtcp2_vec_split_at(ngtcp2_vec *dst, ngtcp2_vec *src, size_t offset); ++ ++/* ++ * ngtcp2_vec_end returns the one beyond the last offset of |v|. ++ */ ++static inline uint8_t *ngtcp2_vec_end(const ngtcp2_vec *v) { ++ return v->base + v->len; ++} ++ ++/* ++ * ngtcp2_vec_drop removes the first |n| bytes from |v| by adjusting ++ * its base and len fields. This function assumes |v|->len > 0 && ++ * |v|->len >= n. ++ */ ++static inline void ngtcp2_vec_drop(ngtcp2_vec *v, size_t n) { ++ v->base += n; ++ v->len -= n; ++} ++ + #endif /* !defined(NGTCP2_VEC_H) */ +diff --git a/third_party/ngtcp2/lib/ngtcp2_window_filter.c b/third_party/ngtcp2/lib/ngtcp2_window_filter.c +index 39f3d408a74..707cd570799 100644 +--- a/third_party/ngtcp2/lib/ngtcp2_window_filter.c ++++ b/third_party/ngtcp2/lib/ngtcp2_window_filter.c +@@ -39,7 +39,7 @@ + void ngtcp2_window_filter_init(ngtcp2_window_filter *wf, + uint64_t window_length) { + wf->window_length = window_length; +- memset(wf->estimates, 0xff, sizeof(wf->estimates)); ++ memset(wf->estimates, 0xFF, sizeof(wf->estimates)); + } + + void ngtcp2_window_filter_update(ngtcp2_window_filter *wf, uint64_t new_sample, +diff --git a/third_party/ngtcp2/wscript b/third_party/ngtcp2/wscript +index f4cfd1c7064..399790c50c1 100644 +--- a/third_party/ngtcp2/wscript ++++ b/third_party/ngtcp2/wscript +@@ -72,9 +72,11 @@ def build(bld): + lib/ngtcp2_balloc.c + lib/ngtcp2_bbr.c + lib/ngtcp2_buf.c ++ lib/ngtcp2_callbacks.c + lib/ngtcp2_cc.c + lib/ngtcp2_cid.c + lib/ngtcp2_conn.c ++ lib/ngtcp2_conn_info.c + lib/ngtcp2_conv.c + lib/ngtcp2_crypto.c + lib/ngtcp2_dcidtr.c +@@ -89,6 +91,7 @@ def build(bld): + lib/ngtcp2_objalloc.c + lib/ngtcp2_opl.c + lib/ngtcp2_path.c ++ lib/ngtcp2_pcg.c + lib/ngtcp2_pkt.c + lib/ngtcp2_pmtud.c + lib/ngtcp2_ppe.c +@@ -96,6 +99,7 @@ def build(bld): + lib/ngtcp2_pv.c + lib/ngtcp2_qlog.c + lib/ngtcp2_range.c ++ lib/ngtcp2_ratelim.c + lib/ngtcp2_ringbuf.c + lib/ngtcp2_rob.c + lib/ngtcp2_rst.c +-- +2.53.0 + + +From c42695f12eea3b3d113114e535be0de950333278 Mon Sep 17 00:00:00 2001 +From: Andreas Schneider +Date: Tue, 24 Mar 2026 15:00:21 +0100 +Subject: [PATCH 66/66] wafsamba: Add -D_FORTIFY_SOURCE=3 when stack protector + is enabled + +The capability check in SAMBA_CONFIG_H() already tests that the compiler +accepts both -Wp,-D_FORTIFY_SOURCE and the stack protector flag +together, but only the stack protector flag was added to EXTRA_CFLAGS on +success. + +The glibc normally silently downgrades to the supported level if the on +specified is not supported. + +Note that -Wp,-U_FORTIFY_SOURCE,-D_FORTIFY_SOURCE=3 only sets it if not +already defined. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=16040 + +Signed-off-by: Andreas Schneider +Reviewed-by: Anoop C S + +Autobuild-User(master): Andreas Schneider +Autobuild-Date(master): Fri Mar 27 08:33:09 UTC 2026 on atb-devel-224 + +(cherry picked from commit 333ac047c3fc151222e5ee6aaa75452276b0031e) +--- + buildtools/wafsamba/samba_autoconf.py | 7 ++++++- + script/autobuild.py | 6 ------ + 2 files changed, 6 insertions(+), 7 deletions(-) + +diff --git a/buildtools/wafsamba/samba_autoconf.py b/buildtools/wafsamba/samba_autoconf.py +index b777391a038..979d5a3972f 100644 +--- a/buildtools/wafsamba/samba_autoconf.py ++++ b/buildtools/wafsamba/samba_autoconf.py +@@ -733,11 +733,16 @@ def SAMBA_CONFIG_H(conf, path=None): + } + ''', + execute=0, +- cflags=[ '-Werror', '-Wp,-D_FORTIFY_SOURCE=2', stack_protect_flag], ++ cflags=[ ++ '-Werror', ++ '-Wp,-U_FORTIFY_SOURCE,-D_FORTIFY_SOURCE=3', ++ stack_protect_flag ++ ], + mandatory=False, + msg='Checking if compiler accepts %s' % (stack_protect_flag)) + if flag_supported: + conf.ADD_CFLAGS('%s' % (stack_protect_flag)) ++ conf.ADD_CFLAGS('-Wp,-U_FORTIFY_SOURCE,-D_FORTIFY_SOURCE=3') + break + + flag_supported = conf.check(fragment=''' +diff --git a/script/autobuild.py b/script/autobuild.py +index 97d44a07ee4..1324c9b8c2e 100755 +--- a/script/autobuild.py ++++ b/script/autobuild.py +@@ -206,13 +206,7 @@ try: + except ImportError: + pass + +-# on ubuntu gcc implies _FORTIFY_SOURCE +-# before 24.04 it was _FORTIFY_SOURCE=2 +-# and 24.04 has _FORTIFY_SOURCE=3 +-# so we do not specify it explicitly. + samba_o3_cflags = "-O3" +-if not is_ubuntu: +- samba_o3_cflags += " -Wp,-D_FORTIFY_SOURCE=2" + + def format_option(name, value=None): + """Format option as str list.""" +-- +2.53.0 + diff --git a/samba.spec b/samba.spec index 4a58332..0c7f772 100644 --- a/samba.spec +++ b/samba.spec @@ -2,7 +2,7 @@ ## (rpmautospec version 0.6.5) ## RPMAUTOSPEC: autorelease, autochangelog %define autorelease(e:s:pb:n) %{?-p:0.}%{lua: - release_number = 8; + release_number = 10; base_release_number = tonumber(rpm.expand("%{?-b*}%{!?-b:1}")); print(release_number + base_release_number - 1); }%{?-e:.%{-e*}}%{?-s:.%{-s*}}%{!?-n:%{?dist}} @@ -4063,6 +4063,18 @@ fi %changelog ## START: Generated by rpmautospec +* Thu May 21 2026 Pavel Filipenský - 0:4.23.5-109 +- Bump build number + +* Tue May 19 2026 Pavel Filipenský - 0:4.23.5-108 +- Security release for CVE-2026-1933 CVE-2026-2340 CVE-2026-3012 + CVE-2026-4480 CVE-2026-40170 CVE-2026-4408 +- resolves: RHEL-156279 - CVE-2026-1933 CVE-2026-2340 CVE-2026-3012 samba: + various flaws +- resolves: RHEL-161644 - CVE-2026-4480 - Fix Samba printing +- resolves: RHEL-170595 - CVE-2026-40170 - Update bundled ngtcp2 +- resolves: RHEL-166871 - Add stack protection with FORTIFY_SOURCE + * Tue Apr 21 2026 Pavel Filipenský - 0:4.23.5-107 - Fix 'net ads join' AD replication race with multiple DCs - resolves: RHEL-169668